forked from LeenkxTeam/LNXRNT
363 lines
12 KiB
C++
363 lines
12 KiB
C++
|
|
#ifdef WITH_WEBVIEW
|
||
|
|
#ifdef KINC_LINUX
|
||
|
|
|
||
|
|
#include "webview.h"
|
||
|
|
#include <kinc/log.h>
|
||
|
|
#include <kinc/window.h>
|
||
|
|
|
||
|
|
#include <gtk/gtk.h>
|
||
|
|
#include <webkit2/webkit2.h>
|
||
|
|
|
||
|
|
struct LinuxWebViewState {
|
||
|
|
WebKitWebView* webView;
|
||
|
|
GtkWidget* detachedWindow;
|
||
|
|
bool isDetached;
|
||
|
|
gulong closeHandlerId;
|
||
|
|
gulong messageHandlerId;
|
||
|
|
gulong loadHandlerId;
|
||
|
|
int webviewId;
|
||
|
|
};
|
||
|
|
|
||
|
|
static bool g_gtkInitialized = false;
|
||
|
|
|
||
|
|
static void ensureGtkInit() {
|
||
|
|
if (g_gtkInitialized) return;
|
||
|
|
if (!gtk_init_check(nullptr, nullptr)) {
|
||
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "Failed to initialize webview for GTK");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
g_gtkInitialized = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void onMessageReceived(WebKitUserContentManager* manager,
|
||
|
|
WebKitJavascriptResult* result, gpointer userData) {
|
||
|
|
auto* state = (LinuxWebViewState*)userData;
|
||
|
|
JSCValue* value = webkit_javascript_result_get_js_value(result);
|
||
|
|
if (jsc_value_is_string(value)) {
|
||
|
|
char* str = jsc_value_to_string(value);
|
||
|
|
WebViewManager::instance().dispatchMessage(state->webviewId, std::string(str));
|
||
|
|
g_free(str);
|
||
|
|
} else {
|
||
|
|
char* str = jsc_value_to_string(value);
|
||
|
|
WebViewManager::instance().dispatchMessage(state->webviewId, std::string(str));
|
||
|
|
g_free(str);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static void onLoadChanged(WebKitWebView* webView, WebKitLoadEvent loadEvent,
|
||
|
|
gpointer userData) {
|
||
|
|
auto* state = (LinuxWebViewState*)userData;
|
||
|
|
if (loadEvent == WEBKIT_LOAD_FINISHED) {
|
||
|
|
WebViewManager::instance().dispatchLoad(state->webviewId);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
static gboolean onDetachedClose(GtkWidget* widget, GdkEvent* event, gpointer userData) {
|
||
|
|
auto* state = (LinuxWebViewState*)userData;
|
||
|
|
WebViewManager::instance().dispatchClose(state->webviewId);
|
||
|
|
gtk_widget_hide(widget);
|
||
|
|
return TRUE;
|
||
|
|
}
|
||
|
|
|
||
|
|
int platformCreate(WebViewInstance* wv) {
|
||
|
|
ensureGtkInit();
|
||
|
|
if (!g_gtkInitialized) {
|
||
|
|
kinc_log(KINC_LOG_LEVEL_ERROR, "GTK WebView not initialized");
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
auto* state = new LinuxWebViewState();
|
||
|
|
state->webView = nullptr;
|
||
|
|
state->detachedWindow = nullptr;
|
||
|
|
state->isDetached = (wv->parentWindow < 0);
|
||
|
|
state->webviewId = wv->id;
|
||
|
|
wv->platformHandle = state;
|
||
|
|
|
||
|
|
state->webView = WEBKIT_WEB_VIEW(webkit_web_view_new());
|
||
|
|
|
||
|
|
if (wv->transparent) {
|
||
|
|
GdkRGBA rgba = {0.0, 0.0, 0.0, 0.0};
|
||
|
|
webkit_web_view_set_background_color(state->webView, &rgba);
|
||
|
|
}
|
||
|
|
|
||
|
|
WebKitUserContentManager* contentManager =
|
||
|
|
webkit_web_view_get_user_content_manager(state->webView);
|
||
|
|
webkit_user_content_manager_register_script_message_handler(contentManager, "lnxrnt");
|
||
|
|
state->messageHandlerId = g_signal_connect(contentManager, "script-message-received::lnxrnt",
|
||
|
|
G_CALLBACK(onMessageReceived), state);
|
||
|
|
|
||
|
|
state->loadHandlerId = g_signal_connect(state->webView, "load-changed",
|
||
|
|
G_CALLBACK(onLoadChanged), state);
|
||
|
|
|
||
|
|
if (state->isDetached) {
|
||
|
|
state->detachedWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||
|
|
gtk_window_set_title(GTK_WINDOW(state->detachedWindow), wv->title.c_str());
|
||
|
|
gtk_window_set_default_size(GTK_WINDOW(state->detachedWindow), wv->width, wv->height);
|
||
|
|
gtk_window_move(GTK_WINDOW(state->detachedWindow), wv->x, wv->y);
|
||
|
|
|
||
|
|
GtkWidget* scrolledWindow = gtk_scrolled_window_new(nullptr, nullptr);
|
||
|
|
gtk_container_add(GTK_CONTAINER(scrolledWindow), GTK_WIDGET(state->webView));
|
||
|
|
gtk_container_add(GTK_CONTAINER(state->detachedWindow), scrolledWindow);
|
||
|
|
|
||
|
|
state->closeHandlerId = g_signal_connect(state->detachedWindow, "delete-event",
|
||
|
|
G_CALLBACK(onDetachedClose), state);
|
||
|
|
} else {
|
||
|
|
state->detachedWindow = gtk_window_new(GTK_WINDOW_POPUP);
|
||
|
|
gtk_window_set_decorated(GTK_WINDOW(state->detachedWindow), FALSE);
|
||
|
|
gtk_window_set_default_size(GTK_WINDOW(state->detachedWindow), wv->width, wv->height);
|
||
|
|
|
||
|
|
gtk_window_move(GTK_WINDOW(state->detachedWindow), wv->x, wv->y);
|
||
|
|
|
||
|
|
GtkWidget* scrolledWindow = gtk_scrolled_window_new(nullptr, nullptr);
|
||
|
|
gtk_container_add(GTK_CONTAINER(scrolledWindow), GTK_WIDGET(state->webView));
|
||
|
|
gtk_container_add(GTK_CONTAINER(state->detachedWindow), scrolledWindow);
|
||
|
|
}
|
||
|
|
|
||
|
|
return wv->id;
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformDestroy(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state) return;
|
||
|
|
|
||
|
|
if (state->messageHandlerId) {
|
||
|
|
WebKitUserContentManager* cm = webkit_web_view_get_user_content_manager(state->webView);
|
||
|
|
g_signal_handler_disconnect(cm, state->messageHandlerId);
|
||
|
|
}
|
||
|
|
if (state->loadHandlerId) {
|
||
|
|
g_signal_handler_disconnect(state->webView, state->loadHandlerId);
|
||
|
|
}
|
||
|
|
if (state->closeHandlerId && state->detachedWindow) {
|
||
|
|
g_signal_handler_disconnect(state->detachedWindow, state->closeHandlerId);
|
||
|
|
}
|
||
|
|
if (state->detachedWindow) {
|
||
|
|
gtk_widget_destroy(state->detachedWindow);
|
||
|
|
}
|
||
|
|
delete state;
|
||
|
|
wv->platformHandle = nullptr;
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformLoadHTML(WebViewInstance* wv, const std::string& html) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
webkit_web_view_load_html(state->webView, html.c_str(), nullptr);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformLoadURL(WebViewInstance* wv, const std::string& url) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
webkit_web_view_load_uri(state->webView, url.c_str());
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformEvalJS(WebViewInstance* wv, const std::string& js) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
webkit_web_view_evaluate_javascript(state->webView, js.c_str(), -1, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void platformEvalJSAsyncCallback(GObject* source, GAsyncResult* res, gpointer user_data) {
|
||
|
|
int webviewId = (int)(intptr_t)user_data;
|
||
|
|
WebKitJavascriptResult* jsResult = webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(source), res, nullptr);
|
||
|
|
std::string resultStr = "null";
|
||
|
|
if (jsResult) {
|
||
|
|
JSCValue* value = webkit_javascript_result_get_js_value(jsResult);
|
||
|
|
if (value && !jsc_value_is_undefined(value) && !jsc_value_is_null(value)) {
|
||
|
|
JSCValue* jsonStr = jsc_value_to_string(value);
|
||
|
|
gchar* str = (gchar*)jsc_value_to_string(jsonStr);
|
||
|
|
if (str) {
|
||
|
|
resultStr = std::string(str);
|
||
|
|
g_free(str);
|
||
|
|
}
|
||
|
|
g_object_unref(jsonStr);
|
||
|
|
}
|
||
|
|
webkit_javascript_result_unref(jsResult);
|
||
|
|
}
|
||
|
|
WebViewManager::instance().dispatchEvalResult(webviewId, resultStr);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformEvalJSAsync(WebViewInstance* wv, const std::string& js) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
webkit_web_view_evaluate_javascript(state->webView, js.c_str(), -1, nullptr, nullptr,
|
||
|
|
(GAsyncReadyCallback)platformEvalJSAsyncCallback, (gpointer)(intptr_t)wv->id);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformShow(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state) return;
|
||
|
|
if (state->detachedWindow) {
|
||
|
|
gtk_widget_show_all(state->detachedWindow);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformHide(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state) return;
|
||
|
|
if (state->detachedWindow) {
|
||
|
|
gtk_widget_hide(state->detachedWindow);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformResize(WebViewInstance* wv, int width, int height) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->detachedWindow) return;
|
||
|
|
gtk_window_resize(GTK_WINDOW(state->detachedWindow), width, height);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformMove(WebViewInstance* wv, int x, int y) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->detachedWindow) return;
|
||
|
|
gtk_window_move(GTK_WINDOW(state->detachedWindow), x, y);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformSetTransparent(WebViewInstance* wv, bool transparent) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
if (transparent) {
|
||
|
|
GdkRGBA rgba = {0.0, 0.0, 0.0, 0.0};
|
||
|
|
webkit_web_view_set_background_color(state->webView, &rgba);
|
||
|
|
} else {
|
||
|
|
GdkRGBA rgba = {1.0, 1.0, 1.0, 1.0};
|
||
|
|
webkit_web_view_set_background_color(state->webView, &rgba);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformSetClickThrough(WebViewInstance* wv, bool enabled) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
const char* js = enabled
|
||
|
|
? "document.body.style.pointerEvents = 'none';"
|
||
|
|
: "document.body.style.pointerEvents = 'auto';";
|
||
|
|
webkit_web_view_evaluate_javascript(state->webView, js, -1, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformSetTitle(WebViewInstance* wv, const std::string& title) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||
|
|
gtk_window_set_title(GTK_WINDOW(state->detachedWindow), title.c_str());
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformSend(WebViewInstance* wv, const std::string& message) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
std::string escaped;
|
||
|
|
for (size_t i = 0; i < message.size(); i++) {
|
||
|
|
char c = message[i];
|
||
|
|
switch (c) {
|
||
|
|
case '\\': escaped += "\\\\"; break;
|
||
|
|
case '\'': escaped += "\\'"; break;
|
||
|
|
case '\n': escaped += "\\n"; break;
|
||
|
|
case '\r': escaped += "\\r"; break;
|
||
|
|
default: escaped += c; break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
std::string js = "if(window.__lnxrntOnMessage)window.__lnxrntOnMessage('" + escaped + "');";
|
||
|
|
webkit_web_view_evaluate_javascript(state->webView, js.c_str(), -1, nullptr, nullptr, nullptr, nullptr, nullptr);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformGoBack(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
webkit_web_view_go_back(state->webView);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformGoForward(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
webkit_web_view_go_forward(state->webView);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformReload(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
webkit_web_view_reload(state->webView);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool platformCanGoBack(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return false;
|
||
|
|
return webkit_web_view_can_go_back(state->webView);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool platformCanGoForward(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return false;
|
||
|
|
return webkit_web_view_can_go_forward(state->webView);
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string platformGetURL(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return "";
|
||
|
|
const gchar* uri = webkit_web_view_get_uri(state->webView);
|
||
|
|
return uri ? std::string(uri) : "";
|
||
|
|
}
|
||
|
|
|
||
|
|
std::string platformGetTitle(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return "";
|
||
|
|
const gchar* title = webkit_web_view_get_title(state->webView);
|
||
|
|
return title ? std::string(title) : "";
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformMinimize(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||
|
|
gtk_window_iconify(GTK_WINDOW(state->detachedWindow));
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformMaximize(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||
|
|
gtk_window_maximize(GTK_WINDOW(state->detachedWindow));
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformRestore(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||
|
|
gtk_window_unmaximize(GTK_WINDOW(state->detachedWindow));
|
||
|
|
gtk_window_deiconify(GTK_WINDOW(state->detachedWindow));
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformSetFullscreen(WebViewInstance* wv, bool fullscreen) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->isDetached || !state->detachedWindow) return;
|
||
|
|
if (fullscreen) {
|
||
|
|
gtk_window_fullscreen(GTK_WINDOW(state->detachedWindow));
|
||
|
|
} else {
|
||
|
|
gtk_window_unfullscreen(GTK_WINDOW(state->detachedWindow));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
bool platformIsFullscreen(WebViewInstance* wv) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->isDetached || !state->detachedWindow) return false;
|
||
|
|
GdkWindow* gdkWin = gtk_widget_get_window(state->detachedWindow);
|
||
|
|
if (!gdkWin) return false;
|
||
|
|
GdkWindowState state2 = gdk_window_get_state(gdkWin);
|
||
|
|
return (state2 & GDK_WINDOW_STATE_FULLSCREEN) != 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformEnableDevTools(WebViewInstance* wv, bool enabled) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
WebKitSettings* settings = webkit_web_view_get_settings(state->webView);
|
||
|
|
webkit_settings_set_enable_developer_extras(settings, enabled);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformSetContextMenuEnabled(WebViewInstance* wv, bool enabled) {
|
||
|
|
auto* state = (LinuxWebViewState*)wv->platformHandle;
|
||
|
|
if (!state || !state->webView) return;
|
||
|
|
WebKitSettings* settings = webkit_web_view_get_settings(state->webView);
|
||
|
|
webkit_settings_set_enable_context_menu(settings, enabled);
|
||
|
|
}
|
||
|
|
|
||
|
|
void platformTick() {
|
||
|
|
if (!g_gtkInitialized) return;
|
||
|
|
while (gtk_events_pending()) {
|
||
|
|
gtk_main_iteration_do(FALSE);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#endif
|
||
|
|
#endif
|