Files
LNXRNT/Sources/webview_linux.cpp
2026-07-15 19:37:38 -07:00

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