mirror of https://github.com/axmolengine/axmol.git
920 lines
28 KiB
C++
920 lines
28 KiB
C++
|
/****************************************************************************
|
||
|
Copyright (c) 2017 Serge Zaitsev
|
||
|
Copyright (c) 2022 Steffen André Langnes
|
||
|
Copyright (c) 2019-present Axmol Engine contributors (see AUTHORS.md).
|
||
|
|
||
|
https://axmol.dev/
|
||
|
|
||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
of this software and associated documentation files (the "Software"), to deal
|
||
|
in the Software without restriction, including without limitation the rights
|
||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
copies of the Software, and to permit persons to whom the Software is
|
||
|
furnished to do so, subject to the following conditions:
|
||
|
|
||
|
The above copyright notice and this permission notice shall be included in
|
||
|
all copies or substantial portions of the Software.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
THE SOFTWARE.
|
||
|
****************************************************************************/
|
||
|
|
||
|
#include "UIWebViewImpl-linux.h"
|
||
|
|
||
|
#if (AX_TARGET_PLATFORM == AX_PLATFORM_LINUX)
|
||
|
|
||
|
# include <sys/stat.h>
|
||
|
# include <chrono> // chrono_literals
|
||
|
# include <thread> // this_thread::sleep_for
|
||
|
# include <gtk/gtk.h>
|
||
|
# include <webkit2/webkit2.h>
|
||
|
|
||
|
# include "UIWebView.h"
|
||
|
# include "base/Director.h"
|
||
|
# include "platform/FileUtils.h"
|
||
|
# include "platform/GLView.h"
|
||
|
# include "ui/UIHelper.h"
|
||
|
# include "UIWebViewCommon.h"
|
||
|
|
||
|
# if !defined(AX_PLATFORM_LINUX_WAYLAND)
|
||
|
# include <X11/Xlib.h>
|
||
|
# include <X11/Xatom.h>
|
||
|
# include <gdk/gdkx.h>
|
||
|
# endif
|
||
|
|
||
|
using namespace webview_common;
|
||
|
|
||
|
namespace webkit_dmabuf
|
||
|
{
|
||
|
|
||
|
// Get environment variable. Not thread-safe.
|
||
|
static inline std::string get_env(const std::string& name)
|
||
|
{
|
||
|
auto* value = std::getenv(name.c_str());
|
||
|
if (value)
|
||
|
{
|
||
|
return {value};
|
||
|
}
|
||
|
return {};
|
||
|
}
|
||
|
|
||
|
// Set environment variable. Not thread-safe.
|
||
|
static inline void set_env(const std::string& name, const std::string& value)
|
||
|
{
|
||
|
::setenv(name.c_str(), value.c_str(), 1);
|
||
|
}
|
||
|
|
||
|
// Checks whether the NVIDIA GPU driver is used based on whether the kernel
|
||
|
// module is loaded.
|
||
|
static inline bool is_using_nvidia_driver()
|
||
|
{
|
||
|
struct ::stat buffer
|
||
|
{};
|
||
|
if (::stat("/sys/module/nvidia", &buffer) != 0)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return S_ISDIR(buffer.st_mode);
|
||
|
}
|
||
|
|
||
|
// Checks whether the windowing system is Wayland.
|
||
|
static inline bool is_wayland_display()
|
||
|
{
|
||
|
if (!get_env("WAYLAND_DISPLAY").empty())
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
if (get_env("XDG_SESSION_TYPE") == "wayland")
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
if (get_env("DESKTOP_SESSION").find("wayland") != std::string::npos)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Checks whether the GDK X11 backend is used.
|
||
|
// See: https://docs.gdkWindow.org/gdk3/class.DisplayManager.html
|
||
|
static inline bool is_gdk_x11_backend()
|
||
|
{
|
||
|
# if !defined(AX_PLATFORM_LINUX_WAYLAND)
|
||
|
auto* manager = gdk_display_manager_get();
|
||
|
auto* display = gdk_display_manager_get_default_display(manager);
|
||
|
return GDK_IS_X11_DISPLAY(display);
|
||
|
# else
|
||
|
return false;
|
||
|
# endif
|
||
|
}
|
||
|
|
||
|
// Checks whether WebKit is affected by bug when using DMA-BUF renderer.
|
||
|
// Returns true if all of the following conditions are met:
|
||
|
// - WebKit version is >= 2.42 (please narrow this down when there's a fix).
|
||
|
// - Environment variables are empty or not set:
|
||
|
// - WEBKIT_DISABLE_DMABUF_RENDERER
|
||
|
// - Windowing system is not Wayland.
|
||
|
// - GDK backend is X11.
|
||
|
// - NVIDIA GPU driver is used.
|
||
|
static inline bool is_webkit_dmabuf_bugged()
|
||
|
{
|
||
|
auto wk_major = webkit_get_major_version();
|
||
|
auto wk_minor = webkit_get_minor_version();
|
||
|
// TODO: Narrow down affected WebKit version when there's a fixed version
|
||
|
auto is_affected_wk_version = wk_major == 2 && wk_minor >= 42;
|
||
|
if (!is_affected_wk_version)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (!get_env("WEBKIT_DISABLE_DMABUF_RENDERER").empty())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (is_wayland_display())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (!is_gdk_x11_backend())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
if (!is_using_nvidia_driver())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Applies workaround for WebKit DMA-BUF bug if needed.
|
||
|
// See WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=261874
|
||
|
static inline void apply_webkit_dmabuf_workaround()
|
||
|
{
|
||
|
if (!is_webkit_dmabuf_bugged())
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
set_env("WEBKIT_DISABLE_DMABUF_RENDERER", "1");
|
||
|
}
|
||
|
} // namespace webkit_dmabuf
|
||
|
|
||
|
static double getDeviceScaleFactor(GdkMonitor* monitor)
|
||
|
{
|
||
|
static double scale_factor = 1.0;
|
||
|
static GdkMonitor* last_monitor = nullptr;
|
||
|
|
||
|
if (last_monitor != monitor)
|
||
|
{
|
||
|
last_monitor = monitor;
|
||
|
return (double)gdk_monitor_get_scale_factor(last_monitor);
|
||
|
}
|
||
|
|
||
|
return scale_factor;
|
||
|
}
|
||
|
|
||
|
static inline std::string create_init_script(const std::string& post_fn)
|
||
|
{
|
||
|
auto js = std::string{} +
|
||
|
"(function() {\n\
|
||
|
'use strict';\n\
|
||
|
function generateId() {\n\
|
||
|
var crypto = window.crypto || window.msCrypto;\n\
|
||
|
var bytes = new Uint8Array(16);\n\
|
||
|
crypto.getRandomValues(bytes);\n\
|
||
|
return Array.prototype.slice.call(bytes).map(function(n) {\n\
|
||
|
return n.toString(16).padStart(2, '0');\n\
|
||
|
}).join('');\n\
|
||
|
}\n\
|
||
|
var Webview = (function() {\n\
|
||
|
var _promises = {};\n\
|
||
|
function Webview_() {}\n\
|
||
|
Webview_.prototype.post = function(message) {\n\
|
||
|
return (" +
|
||
|
post_fn +
|
||
|
")(message);\n\
|
||
|
};\n\
|
||
|
Webview_.prototype.call = function(method) {\n\
|
||
|
var _id = generateId();\n\
|
||
|
var _params = Array.prototype.slice.call(arguments, 1);\n\
|
||
|
var promise = new Promise(function(resolve, reject) {\n\
|
||
|
_promises[_id] = { resolve, reject };\n\
|
||
|
});\n\
|
||
|
this.post(JSON.stringify({\n\
|
||
|
id: _id,\n\
|
||
|
method: method,\n\
|
||
|
params: _params\n\
|
||
|
}));\n\
|
||
|
return promise;\n\
|
||
|
};\n\
|
||
|
Webview_.prototype.onReply = function(id, status, result) {\n\
|
||
|
var promise = _promises[id];\n\
|
||
|
if (result !== undefined) {\n\
|
||
|
try {\n\
|
||
|
result = JSON.parse(result);\n\
|
||
|
} catch {\n\
|
||
|
promise.reject(new Error(\"Failed to parse binding result as JSON\"));\n\
|
||
|
return;\n\
|
||
|
}\n\
|
||
|
}\n\
|
||
|
if (status === 0) {\n\
|
||
|
promise.resolve(result);\n\
|
||
|
} else {\n\
|
||
|
promise.reject(result);\n\
|
||
|
}\n\
|
||
|
};\n\
|
||
|
Webview_.prototype.onBind = function(name) {\n\
|
||
|
if (Object.hasOwn(window, name)) {\n\
|
||
|
throw new Error('Property \"' + name + '\" already exists');\n\
|
||
|
}\n\
|
||
|
window[name] = (function() {\n\
|
||
|
var params = [name].concat(Array.prototype.slice.call(arguments));\n\
|
||
|
return Webview_.prototype.call.apply(this, params);\n\
|
||
|
}).bind(this);\n\
|
||
|
};\n\
|
||
|
Webview_.prototype.onUnbind = function(name) {\n\
|
||
|
if (!Object.hasOwn(window, name)) {\n\
|
||
|
throw new Error('Property \"' + name + '\" does not exist');\n\
|
||
|
}\n\
|
||
|
delete window[name];\n\
|
||
|
};\n\
|
||
|
return Webview_;\n\
|
||
|
})();\n\
|
||
|
window.__webview__ = new Webview();\n\
|
||
|
})()";
|
||
|
return js;
|
||
|
}
|
||
|
|
||
|
static void init_gtk_platform_with_display(Display* x11Display)
|
||
|
{
|
||
|
static bool has_initiated = false;
|
||
|
if (has_initiated)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
webkit_dmabuf::apply_webkit_dmabuf_workaround();
|
||
|
|
||
|
# if !defined(AX_PLATFORM_LINUX_WAYLAND)
|
||
|
// this will instruct gdk to use XWayland layer on Wayland platforms
|
||
|
if (webkit_dmabuf::is_wayland_display())
|
||
|
{
|
||
|
gdk_set_allowed_backends("x11");
|
||
|
}
|
||
|
# endif
|
||
|
gtk_init(nullptr, nullptr);
|
||
|
|
||
|
// instructing gdk display manager to use specified x11 display as default
|
||
|
const char* display_string = DisplayString(x11Display);
|
||
|
GdkDisplay* x11_gdk_display =
|
||
|
display_string != nullptr ? gdk_display_open(display_string) : gdk_display_get_default();
|
||
|
|
||
|
auto default_display_manager = gdk_display_manager_get();
|
||
|
gdk_display_manager_set_default_display(default_display_manager, x11_gdk_display);
|
||
|
|
||
|
has_initiated = true;
|
||
|
}
|
||
|
|
||
|
using ContinueUrlCallback_t = std::function<bool(std::string_view)>;
|
||
|
using GeneralUrlCallback_t = std::function<void(std::string_view)>;
|
||
|
class GTKWebKit
|
||
|
{
|
||
|
public:
|
||
|
GTKWebKit() : isXwayland(webkit_dmabuf::is_wayland_display())
|
||
|
{
|
||
|
m_X11Display = static_cast<Display*>(ax::Director::getInstance()->getGLView()->getX11Display());
|
||
|
m_ParentX11Window = reinterpret_cast<Window>(ax::Director::getInstance()->getGLView()->getX11Window());
|
||
|
|
||
|
init_gtk_platform_with_display(m_X11Display);
|
||
|
|
||
|
m_Window = gtk_window_new(GTK_WINDOW_POPUP);
|
||
|
gtk_window_set_resizable(GTK_WINDOW(m_Window), true);
|
||
|
g_signal_connect(G_OBJECT(m_Window), "destroy", G_CALLBACK(+[](GtkWidget*, gpointer arg) {
|
||
|
auto* w = static_cast<GTKWebKit*>(arg);
|
||
|
// Widget destroyed along with window.
|
||
|
w->m_WebView = nullptr;
|
||
|
w->m_Window = nullptr;
|
||
|
}),
|
||
|
this);
|
||
|
|
||
|
# if defined(AX_PLATFORM_LINUX_WAYLAND)
|
||
|
AX_ASSERT(0 && "TODO: Implement for wayland..");
|
||
|
/*
|
||
|
* Notes:
|
||
|
*
|
||
|
* Current Wayland(2024) there is no way to embed a foreign window.
|
||
|
* Usually we need to create a native window turn it into a standalone
|
||
|
* browser, place inside of the main window region and interact with it.
|
||
|
*
|
||
|
* - Why not just create a window with no title bar and maintain it's
|
||
|
* position and size manually according to the main window? AFAIK
|
||
|
* Wayland also doesn't support it, it doesn't even allow the process
|
||
|
* which created the window to change those properties.
|
||
|
*
|
||
|
* - Why not just use XWayland layer do those and never use direct wayland
|
||
|
* through GLFW or anything?
|
||
|
* Picture quality and overall performance will not be as good as native
|
||
|
* wayland usage.
|
||
|
* Need do more research but it's just makes sense we won't be getting
|
||
|
* native Wayland performance that's for sure. But the OpenGL drawing
|
||
|
* part shouldn't be affected.
|
||
|
*
|
||
|
* Possible workarounds:
|
||
|
* 1. Ditch GLFW for Linux platform, use EGL library directly or
|
||
|
* use GTK+GDK to maintain the all the window handle stuff. Then use
|
||
|
* Wayland subcompositor concept or https://wayland-book.com/xdg-shell-in-depth/popups.html
|
||
|
* to draw the WebkitGtk surface. Some usage of subcompositor concept can be found:
|
||
|
* https://github.com/ehmry/snes9x/
|
||
|
*
|
||
|
* 1.5: Or we may be can use some other GLFW like framework for Linux, which has
|
||
|
* custom wayland compositor implemented and allows foreign parenting/reparenting.
|
||
|
* Something like this: https://github.com/fltk/fltk, Not sure if they allow it.
|
||
|
*
|
||
|
* - Why this approach is kind of better? Well will be using direct Wayland
|
||
|
* so some hdpi scaling(if not by fraction) would be kind of better than
|
||
|
* XWayland and some other stuff probably won't be much noticable, but
|
||
|
* hey we are removing one more non benificial abstraction layer(XWayland)
|
||
|
* from the userend.
|
||
|
*
|
||
|
* 2. Write custom Wayland compositor(AFAIK it's not a WM but almost
|
||
|
* acts like one) to allow draw foreign wl_surface...
|
||
|
* Yes.. No.
|
||
|
*
|
||
|
* Bonus: We can go with the direct Wayland window managing + draw the
|
||
|
* WebView using XWayland abstraction layer and place the window position
|
||
|
* according to the main window's location. We're not doing exactly the same
|
||
|
* for x11 case, X11 has something to make a window, a child window of another
|
||
|
* window so the relative positon gets handled automatically by the WM.
|
||
|
*
|
||
|
* Why not use CEF or some other WebView implementations? It would be same
|
||
|
* we would still need to move those around, which Wayland doesn't allow
|
||
|
* from process context to do it without custom compositor.
|
||
|
*
|
||
|
*/
|
||
|
# endif
|
||
|
}
|
||
|
|
||
|
~GTKWebKit()
|
||
|
{
|
||
|
if (m_WebView)
|
||
|
{
|
||
|
gtk_widget_destroy(GTK_WIDGET(m_WebView));
|
||
|
m_WebView = nullptr;
|
||
|
}
|
||
|
|
||
|
if (m_Window)
|
||
|
{
|
||
|
g_signal_handlers_disconnect_by_data(GTK_WINDOW(m_Window), this);
|
||
|
gtk_window_close(GTK_WINDOW(m_Window));
|
||
|
m_Window = nullptr;
|
||
|
}
|
||
|
|
||
|
if (m_InitScript)
|
||
|
{
|
||
|
webkit_user_script_unref(m_InitScript);
|
||
|
m_InitScript = nullptr;
|
||
|
}
|
||
|
|
||
|
depleteRunLoopEventQueue();
|
||
|
}
|
||
|
|
||
|
bool createWebView(const ContinueUrlCallback_t& shouldStartLoading,
|
||
|
const GeneralUrlCallback_t& didFinishLoading,
|
||
|
const GeneralUrlCallback_t didFailLoading,
|
||
|
const GeneralUrlCallback_t& onJsCallback)
|
||
|
{
|
||
|
if (m_Window == nullptr)
|
||
|
return false;
|
||
|
|
||
|
m_WebView = webkit_web_view_new();
|
||
|
if (m_WebView == nullptr)
|
||
|
return false;
|
||
|
|
||
|
WebKitUserContentManager* manager = m_UserContentManager =
|
||
|
webkit_web_view_get_user_content_manager(WEBKIT_WEB_VIEW(m_WebView));
|
||
|
if (manager == nullptr)
|
||
|
return false;
|
||
|
|
||
|
g_signal_connect(manager, "script-message-received::__webview__",
|
||
|
G_CALLBACK(+[](WebKitUserContentManager*, WebKitJavascriptResult* r, gpointer arg) {
|
||
|
auto* w = static_cast<GTKWebKit*>(arg);
|
||
|
// webkit2 2.22+
|
||
|
JSCValue* value = webkit_javascript_result_get_js_value(r);
|
||
|
char* s = jsc_value_to_string(value);
|
||
|
w->onMessage(s);
|
||
|
g_free(s);
|
||
|
}),
|
||
|
this);
|
||
|
webkit_user_content_manager_register_script_message_handler(manager, "__webview__");
|
||
|
initScript("function(message){return window.webkit.messageHandlers.__webview__.postMessage(message);}");
|
||
|
|
||
|
m_ShouldStartLoading = shouldStartLoading;
|
||
|
m_DidFinishLoading = didFinishLoading;
|
||
|
m_DidFailLoading = didFailLoading;
|
||
|
m_OnJsCallback = onJsCallback;
|
||
|
g_signal_connect(m_WebView, "load-changed",
|
||
|
G_CALLBACK(+[](WebKitWebView* webView, WebKitLoadEvent loadEvent, gpointer userData) {
|
||
|
auto thiz = static_cast<GTKWebKit*>(userData);
|
||
|
switch (loadEvent)
|
||
|
{
|
||
|
case WEBKIT_LOAD_COMMITTED:
|
||
|
{
|
||
|
std::string_view uri = webkit_web_view_get_uri(webView);
|
||
|
const auto scheme = uri.substr(0, uri.find_first_of(':'));
|
||
|
|
||
|
if (scheme == thiz->m_JsScheme)
|
||
|
{
|
||
|
thiz->m_OnJsCallback(uri);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!thiz->m_ShouldStartLoading(uri))
|
||
|
webkit_web_view_stop_loading(webView);
|
||
|
break;
|
||
|
}
|
||
|
case WEBKIT_LOAD_FINISHED:
|
||
|
{
|
||
|
std::string_view uri = webkit_web_view_get_uri(webView);
|
||
|
thiz->m_DidFinishLoading(uri);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}),
|
||
|
this);
|
||
|
|
||
|
g_signal_connect(m_WebView, "load-failed",
|
||
|
G_CALLBACK(+[](WebKitWebView* webView, WebKitLoadEvent /* loadEvent */, gchar* failingUri,
|
||
|
gpointer /* error */, gpointer userData) {
|
||
|
auto thiz = static_cast<GTKWebKit*>(userData);
|
||
|
thiz->m_DidFailLoading(failingUri);
|
||
|
}),
|
||
|
this);
|
||
|
gtk_container_add(GTK_CONTAINER(m_Window), GTK_WIDGET(m_WebView));
|
||
|
gtk_widget_show(GTK_WIDGET(m_WebView));
|
||
|
|
||
|
WebKitSettings* settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(m_WebView));
|
||
|
if (settings == nullptr)
|
||
|
return false;
|
||
|
|
||
|
webkit_settings_set_javascript_can_access_clipboard(settings, true);
|
||
|
|
||
|
gtk_widget_grab_focus(GTK_WIDGET(m_WebView));
|
||
|
gtk_widget_show_all(m_Window);
|
||
|
|
||
|
return embed();
|
||
|
}
|
||
|
|
||
|
void update()
|
||
|
{
|
||
|
auto director = ax::Director::getInstance();
|
||
|
if (!director->isValid() || m_WebViewHidden)
|
||
|
return;
|
||
|
|
||
|
// Xwayland child repaint issue...
|
||
|
// Another reason to support direct wayland.
|
||
|
// Related issues:
|
||
|
// https://bugzilla.redhat.com/show_bug.cgi?id=1896119
|
||
|
// https://gitlab.gnome.org/GNOME/mutter/-/issues/1577
|
||
|
if (isXwayland)
|
||
|
{
|
||
|
XReparentWindow(m_X11Display, m_ChildX11Window, m_ParentX11Window, (int)m_LastPos.x, (int)m_LastPos.y);
|
||
|
}
|
||
|
|
||
|
// Can be removed if axmol ever integrates with gtk/gdk for linux
|
||
|
while (gtk_events_pending())
|
||
|
{
|
||
|
gtk_main_iteration();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void setWebViewRect(int x, int y, int width, int height)
|
||
|
{
|
||
|
AXLOGI("Pos: {}, {}, Size: w:{}, h:{}", x, y, width, height);
|
||
|
|
||
|
gtk_window_resize(GTK_WINDOW(m_Window), width, height);
|
||
|
gtk_window_move(GTK_WINDOW(m_Window), x, y);
|
||
|
|
||
|
setScalesPageToFit(m_ScalesPageToFit);
|
||
|
|
||
|
m_LastPos = ax::Vec2((float)x, (float)y);
|
||
|
m_LastSize = ax::Vec2((float)width, (float)height);
|
||
|
}
|
||
|
|
||
|
void loadHTMLString(std::string_view html, std::string_view baseURL)
|
||
|
{
|
||
|
webkit_web_view_load_html(WEBKIT_WEB_VIEW(m_WebView), html.data(), baseURL.data());
|
||
|
}
|
||
|
|
||
|
void loadFile(std::string_view filePath)
|
||
|
{
|
||
|
auto fullPath = FileUtils::getInstance()->fullPathForFilename(filePath);
|
||
|
if (fullPath.find("file:///") != 0)
|
||
|
{
|
||
|
if (fullPath[0] == '/')
|
||
|
{
|
||
|
fullPath = "file://" + fullPath;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
fullPath = "file:///" + fullPath;
|
||
|
}
|
||
|
}
|
||
|
loadURL(fullPath, false);
|
||
|
}
|
||
|
|
||
|
void loadURL(std::string_view url, bool cleanCachedData)
|
||
|
{
|
||
|
if (cleanCachedData)
|
||
|
{
|
||
|
webkit_web_context_clear_cache(webkit_web_view_get_context(WEBKIT_WEB_VIEW(m_WebView)));
|
||
|
}
|
||
|
webkit_web_view_load_uri(WEBKIT_WEB_VIEW(m_WebView), url.data());
|
||
|
}
|
||
|
|
||
|
void stopLoading() { webkit_web_view_stop_loading(WEBKIT_WEB_VIEW(m_WebView)); }
|
||
|
|
||
|
void reload()
|
||
|
{
|
||
|
// Note: there is also `webkit_web_view_reload` which uses cached data
|
||
|
webkit_web_view_reload_bypass_cache(WEBKIT_WEB_VIEW(m_WebView));
|
||
|
}
|
||
|
|
||
|
bool canGoBack() { return webkit_web_view_can_go_back(WEBKIT_WEB_VIEW(m_WebView)); }
|
||
|
|
||
|
bool canGoForward() { return webkit_web_view_can_go_forward(WEBKIT_WEB_VIEW(m_WebView)); }
|
||
|
|
||
|
void goBack() { webkit_web_view_go_back(WEBKIT_WEB_VIEW(m_WebView)); }
|
||
|
|
||
|
void goForward() { webkit_web_view_go_forward(WEBKIT_WEB_VIEW(m_WebView)); }
|
||
|
|
||
|
void setJavascriptInterfaceScheme(std::string_view scheme) { m_JsScheme = scheme; }
|
||
|
|
||
|
void evaluateJS(std::string_view js)
|
||
|
{
|
||
|
if (webkit_web_view_get_uri(WEBKIT_WEB_VIEW(m_WebView)) == nullptr)
|
||
|
return;
|
||
|
|
||
|
// webkit2 2.40+
|
||
|
webkit_web_view_evaluate_javascript(WEBKIT_WEB_VIEW(m_WebView), js.data(), static_cast<gssize>(js.size()),
|
||
|
nullptr, nullptr, nullptr, nullptr, nullptr);
|
||
|
}
|
||
|
|
||
|
void setScalesPageToFit(bool scalesPageToFit)
|
||
|
{
|
||
|
m_ScalesPageToFit = scalesPageToFit;
|
||
|
|
||
|
// get current monitor
|
||
|
auto* manager = gdk_display_manager_get();
|
||
|
auto* display = gdk_display_manager_get_default_display(manager);
|
||
|
m_Monitor = gdk_display_get_monitor_at_window(display, gtk_widget_get_window(m_Window));
|
||
|
|
||
|
webkit_web_view_set_zoom_level(WEBKIT_WEB_VIEW(m_WebView),
|
||
|
m_ScalesPageToFit ? getDeviceScaleFactor(m_Monitor) : 1.0);
|
||
|
}
|
||
|
|
||
|
void setWebViewVisible(bool visible)
|
||
|
{
|
||
|
using namespace std::chrono_literals;
|
||
|
if (visible && m_WebViewHidden)
|
||
|
{
|
||
|
XMapWindow(m_X11Display, m_ChildX11Window);
|
||
|
XReparentWindow(m_X11Display, m_ChildX11Window, m_ParentX11Window, (int)m_LastPos.x, (int)m_LastPos.y);
|
||
|
|
||
|
std::this_thread::sleep_for(1000ms);
|
||
|
|
||
|
XSync(m_X11Display, false);
|
||
|
XFlush(m_X11Display);
|
||
|
}
|
||
|
else if (!m_WebViewHidden && !visible)
|
||
|
{
|
||
|
XUnmapWindow(m_X11Display, m_ChildX11Window);
|
||
|
|
||
|
std::this_thread::sleep_for(1000ms);
|
||
|
|
||
|
XSync(m_X11Display, false);
|
||
|
XFlush(m_X11Display);
|
||
|
}
|
||
|
|
||
|
m_WebViewHidden = !visible;
|
||
|
}
|
||
|
|
||
|
void setBounces(bool) {}
|
||
|
|
||
|
void setOpacityWebView(double opacity)
|
||
|
{
|
||
|
gtk_widget_set_opacity(GTK_WIDGET(m_WebView), opacity);
|
||
|
gtk_widget_set_opacity(GTK_WIDGET(m_Window), opacity);
|
||
|
}
|
||
|
|
||
|
double getOpacityWebView() { return gtk_widget_get_opacity(GTK_WIDGET(m_WebView)); }
|
||
|
|
||
|
void setBackgroundTransparent() {}
|
||
|
|
||
|
private:
|
||
|
void initScript(const std::string& js)
|
||
|
{
|
||
|
m_InitScript = webkit_user_script_new(js.c_str(), WEBKIT_USER_CONTENT_INJECT_TOP_FRAME,
|
||
|
WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, nullptr, nullptr);
|
||
|
|
||
|
webkit_user_content_manager_add_script(m_UserContentManager, m_InitScript);
|
||
|
}
|
||
|
|
||
|
bool embed()
|
||
|
{
|
||
|
gtk_widget_realize(m_Window);
|
||
|
|
||
|
m_ChildX11Window = gdk_x11_window_get_xid(gtk_widget_get_window(m_Window));
|
||
|
if (m_ChildX11Window == None)
|
||
|
return false;
|
||
|
|
||
|
XReparentWindow(m_X11Display, m_ChildX11Window, m_ParentX11Window, 0, 0);
|
||
|
|
||
|
XMapWindow(m_X11Display, m_ChildX11Window);
|
||
|
XFlush(m_X11Display);
|
||
|
XSync(m_X11Display, false);
|
||
|
|
||
|
while (gtk_events_pending())
|
||
|
{
|
||
|
gtk_main_iteration();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
using dispatch_fn_t = std::function<void()>;
|
||
|
void dispatch_in_gtk(std::function<void()> f)
|
||
|
{
|
||
|
g_idle_add_full(G_PRIORITY_HIGH_IDLE, (GSourceFunc)([](void* f) -> int {
|
||
|
(*static_cast<dispatch_fn_t*>(f))();
|
||
|
return G_SOURCE_REMOVE;
|
||
|
}),
|
||
|
new std::function<void()>(f), [](void* f) { delete static_cast<dispatch_fn_t*>(f); });
|
||
|
}
|
||
|
|
||
|
void onMessage(std::string_view message)
|
||
|
{
|
||
|
// if axmol UIWebView ever support bindings for different callbacks from
|
||
|
// javascript land...
|
||
|
}
|
||
|
|
||
|
void depleteRunLoopEventQueue()
|
||
|
{
|
||
|
bool done = false;
|
||
|
dispatch_in_gtk([&] { done = true; });
|
||
|
while (!done)
|
||
|
{
|
||
|
gtk_main_iteration();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
const bool isXwayland;
|
||
|
|
||
|
Display* m_X11Display;
|
||
|
Window m_ParentX11Window;
|
||
|
Window m_ChildX11Window;
|
||
|
GtkWidget* m_Window;
|
||
|
GtkWidget* m_WebView;
|
||
|
GdkMonitor* m_Monitor;
|
||
|
WebKitUserScript* m_InitScript;
|
||
|
WebKitUserContentManager* m_UserContentManager;
|
||
|
|
||
|
std::string m_JsScheme;
|
||
|
bool m_ScalesPageToFit;
|
||
|
bool m_WebViewHidden;
|
||
|
|
||
|
ContinueUrlCallback_t m_ShouldStartLoading;
|
||
|
GeneralUrlCallback_t m_DidFinishLoading;
|
||
|
GeneralUrlCallback_t m_DidFailLoading;
|
||
|
GeneralUrlCallback_t m_OnJsCallback;
|
||
|
|
||
|
ax::Vec2 m_LastPos;
|
||
|
ax::Vec2 m_LastSize;
|
||
|
};
|
||
|
|
||
|
NS_AX_BEGIN
|
||
|
|
||
|
namespace ui
|
||
|
{
|
||
|
|
||
|
WebViewImpl::WebViewImpl(WebView* webView) : _createSucceeded(false), _gtkWebKit(nullptr), _webView(webView)
|
||
|
{
|
||
|
_gtkWebKit = new GTKWebKit();
|
||
|
_createSucceeded = _gtkWebKit->createWebView(
|
||
|
[this](std::string_view url) -> bool {
|
||
|
const auto shouldStartLoading = _webView->getOnShouldStartLoading();
|
||
|
if (shouldStartLoading != nullptr)
|
||
|
{
|
||
|
return shouldStartLoading(_webView, url);
|
||
|
}
|
||
|
return true;
|
||
|
},
|
||
|
[this](std::string_view url) {
|
||
|
WebView::ccWebViewCallback didFinishLoading = _webView->getOnDidFinishLoading();
|
||
|
if (didFinishLoading != nullptr)
|
||
|
{
|
||
|
didFinishLoading(_webView, url);
|
||
|
}
|
||
|
},
|
||
|
[this](std::string_view url) {
|
||
|
WebView::ccWebViewCallback didFailLoading = _webView->getOnDidFailLoading();
|
||
|
if (didFailLoading != nullptr)
|
||
|
{
|
||
|
didFailLoading(_webView, url);
|
||
|
}
|
||
|
},
|
||
|
[this](std::string_view url) {
|
||
|
WebView::ccWebViewCallback onJsCallback = _webView->getOnJSCallback();
|
||
|
if (onJsCallback != nullptr)
|
||
|
{
|
||
|
onJsCallback(_webView, url);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
WebViewImpl::~WebViewImpl()
|
||
|
{
|
||
|
if (_gtkWebKit != nullptr)
|
||
|
{
|
||
|
delete _gtkWebKit;
|
||
|
_gtkWebKit = nullptr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::loadData(const Data& data,
|
||
|
std::string_view MIMEType,
|
||
|
std::string_view encoding,
|
||
|
std::string_view baseURL)
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
const auto url = getDataURI(data, MIMEType);
|
||
|
_gtkWebKit->loadURL(url, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::loadHTMLString(std::string_view string, std::string_view baseURL)
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
if (string.empty())
|
||
|
{
|
||
|
_gtkWebKit->loadHTMLString("data:text/html," + utils::urlEncode("<html></html>"), baseURL);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
const auto html = htmlFromUri(string);
|
||
|
if (!html.empty())
|
||
|
{
|
||
|
_gtkWebKit->loadHTMLString("data:text/html," + utils::urlEncode(html), baseURL);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_gtkWebKit->loadHTMLString(string, baseURL);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::loadURL(std::string_view url, bool cleanCachedData)
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
_gtkWebKit->loadURL(url, cleanCachedData);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::loadFile(std::string_view fileName)
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
const auto fullPath = FileUtils::getInstance()->fullPathForFilename(fileName);
|
||
|
_gtkWebKit->loadFile(fullPath);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::stopLoading()
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
_gtkWebKit->stopLoading();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::reload()
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
_gtkWebKit->reload();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
bool WebViewImpl::canGoBack()
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
return _gtkWebKit->canGoBack();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool WebViewImpl::canGoForward()
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
return _gtkWebKit->canGoForward();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::goBack()
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
_gtkWebKit->goBack();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::goForward()
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
_gtkWebKit->goForward();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::setJavascriptInterfaceScheme(std::string_view scheme)
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
_gtkWebKit->setJavascriptInterfaceScheme(scheme);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::evaluateJS(std::string_view js)
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
_gtkWebKit->evaluateJS(js);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::setScalesPageToFit(const bool scalesPageToFit)
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
_gtkWebKit->setScalesPageToFit(scalesPageToFit);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::draw(Renderer* renderer, Mat4 const& transform, uint32_t flags)
|
||
|
{
|
||
|
if (!_createSucceeded)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
if (flags & Node::FLAGS_TRANSFORM_DIRTY)
|
||
|
{
|
||
|
const auto uiRect = ax::ui::Helper::convertBoundingBoxToScreen(_webView);
|
||
|
_gtkWebKit->setWebViewRect(static_cast<int>(uiRect.origin.x), static_cast<int>(uiRect.origin.y),
|
||
|
static_cast<int>(uiRect.size.width), static_cast<int>(uiRect.size.height));
|
||
|
}
|
||
|
|
||
|
_gtkWebKit->update();
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::setVisible(bool visible)
|
||
|
{
|
||
|
if (_createSucceeded)
|
||
|
{
|
||
|
_gtkWebKit->setWebViewVisible(visible);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::setBounces(bool bounces)
|
||
|
{
|
||
|
_gtkWebKit->setBounces(bounces);
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::setOpacityWebView(float opacity)
|
||
|
{
|
||
|
_gtkWebKit->setOpacityWebView((double)opacity);
|
||
|
}
|
||
|
|
||
|
float WebViewImpl::getOpacityWebView() const
|
||
|
{
|
||
|
return (float)_gtkWebKit->getOpacityWebView();
|
||
|
}
|
||
|
|
||
|
void WebViewImpl::setBackgroundTransparent()
|
||
|
{
|
||
|
_gtkWebKit->setBackgroundTransparent();
|
||
|
}
|
||
|
|
||
|
} // namespace ui
|
||
|
|
||
|
NS_AX_END
|
||
|
|
||
|
#endif // AX_TARGET_PLATFORM == AX_PLATFORM_LINUX
|