diff --git a/Android.mk b/Android.mk index 38a9ee6c..7091f917 100644 --- a/Android.mk +++ b/Android.mk @@ -57,6 +57,7 @@ LOCAL_SRC_FILES := \ src/thread.c \ src/tlocal.c \ src/version.c \ + src/webview.c \ src/gfx/gl/gl.c \ src/gfx/gl/gl-ui.c \ src/gfx/vk/vk.c \ diff --git a/GNUmakefile b/GNUmakefile index 4357f03e..62c18010 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -38,6 +38,7 @@ OBJS = \ src/thread.o \ src/tlocal.o \ src/version.o \ + src/webview.o \ src/hid/utils.o \ src/unix/compress.o \ src/unix/file.o \ diff --git a/makefile b/makefile index cd4789d4..9b7b6c53 100644 --- a/makefile +++ b/makefile @@ -19,7 +19,7 @@ NAME = matoya !IFDEF STEAM WEBVIEW_OBJ = src\swebview.obj !ELSE -WEBVIEW_OBJ = src\windows\webview.obj +WEBVIEW_OBJ = src\windows\webview-win.obj !ENDIF OBJS = \ @@ -41,6 +41,7 @@ OBJS = \ src\thread.obj \ src\tlocal.obj \ src\version.obj \ + src\webview.obj \ src\gfx\vk\vk.obj \ src\gfx\vk\vk-ctx.obj \ src\gfx\vk\vk-ui.obj \ diff --git a/src/gfx/vk/vkproc.h b/src/gfx/vk/vkproc.h index bd86cc3f..c89dbdcd 100644 --- a/src/gfx/vk/vkproc.h +++ b/src/gfx/vk/vkproc.h @@ -19,6 +19,7 @@ ((e) == VK_ERROR_OUT_OF_DATE_KHR || (e) == VK_SUBOPTIMAL_KHR) #elif defined(MTY_VK_XLIB) + #define LIBX11_NO_SYMBOLS #include "dl/libx11.h" #include "vulkan/vulkan_xlib.h" diff --git a/src/swebview.c b/src/swebview.c index eb9b9b26..6192fe7a 100644 --- a/src/swebview.c +++ b/src/swebview.c @@ -35,21 +35,14 @@ struct sbase { }; struct webview { - MTY_App *app; - MTY_Window window; - WEBVIEW_READY ready_func; - WEBVIEW_TEXT text_func; - WEBVIEW_KEY key_func; - MTY_Queue *pushq; + struct webview_base base; + MTY_Mutex *mutex; ISteamHTMLSurface *surface; EHTMLKeyModifiers smods; HHTMLBrowser browser; char *source; - bool debug; - bool ready; bool visible; - bool passthrough; void *bgra; bool bgra_dirty; @@ -68,7 +61,7 @@ struct webview { static void webview_update_size(struct webview *ctx) { - MTY_Size size = MTY_WindowGetSize(ctx->app, ctx->window); + MTY_Size size = MTY_WindowGetSize(ctx->base.app, ctx->base.window); SteamAPI_ISteamHTMLSurface_SetSize(ctx->surface, ctx->browser, size.w, size.h); } @@ -87,7 +80,7 @@ static void webview_on_browser_ready(struct webview *ctx, HTML_BrowserReady_t *p if (ctx->source) SteamAPI_ISteamHTMLSurface_LoadURL(ctx->surface, ctx->browser, ctx->source, NULL); - if (ctx->debug) + if (ctx->base.debug) SteamAPI_ISteamHTMLSurface_OpenDeveloperTools(ctx->surface, ctx->browser); } @@ -180,30 +173,10 @@ static void finished_request_run0(void *This, void *pvParam) struct webview *ctx = base->ctx; HTML_FinishedRequest_t *params = pvParam; - const char *script = - "const __MTY_MSGS = [];" - - "let __MTY_WEBVIEW = b64 => {" - "__MTY_MSGS.push(b64);" - "};" - - "window.MTY_NativeSendText = text => {" - "alert('T' + text);" - "};" - - "alert('R');" - - "const __MTY_INTERVAL = setInterval(() => {" - "if (window.MTY_NativeListener) {" - "__MTY_WEBVIEW = b64 => {window.MTY_NativeListener(atob(b64));};" - - "for (let msg = __MTY_MSGS.shift(); msg; msg = __MTY_MSGS.shift())" - "__MTY_WEBVIEW(msg);" - - "clearInterval(__MTY_INTERVAL);" - "}" - "}, 100);"; - + const char *script = "window.native = {" + "postMessage: (message) => alert(message)," + "addEventListener: (listener) => window.addEventListener('message', listener)," + "}"; SteamAPI_ISteamHTMLSurface_ExecuteJavascript(ctx->surface, params->unBrowserHandle, script); } @@ -235,26 +208,7 @@ static void js_alert_run0(void *This, void *pvParam) SteamAPI_ISteamHTMLSurface_JSDialogResponse(ctx->surface, ctx->browser, true); const char *str = params->pchMessage; - - switch (str[0]) { - // MTY_EVENT_WEBVIEW_READY - case 'R': - ctx->ready = true; - - // Send any queued messages before the WebView became ready - for (char *msg = NULL; MTY_QueuePopPtr(ctx->pushq, 0, (void **) &msg, NULL);) { - mty_webview_send_text(ctx, msg); - MTY_Free(msg); - } - - ctx->ready_func(ctx->app, ctx->window); - break; - - // MTY_EVENT_WEBVIEW_TEXT - case 'T': - ctx->text_func(ctx->app, ctx->window, str + 1); - break; - } + mty_webview_base_handle_event(&ctx->base, str); } static void js_alert_run1(void *This, void *pvParam, bool _dummy0, SteamAPICall_t _dummy1) @@ -283,10 +237,10 @@ static void set_cursor_run0(void *This, void *pvParam) HTML_SetCursor_t *params = pvParam; switch (params->eMouseCursor) { - case dc_hand: MTY_AppSetCursor(ctx->app, MTY_CURSOR_HAND); break; - case dc_ibeam: MTY_AppSetCursor(ctx->app, MTY_CURSOR_IBEAM); break; + case dc_hand: MTY_AppSetCursor(ctx->base.app, MTY_CURSOR_HAND); break; + case dc_ibeam: MTY_AppSetCursor(ctx->base.app, MTY_CURSOR_IBEAM); break; default: - MTY_AppSetCursor(ctx->app, MTY_CURSOR_ARROW); + MTY_AppSetCursor(ctx->base.app, MTY_CURSOR_ARROW); break; } } @@ -330,17 +284,12 @@ struct webview *mty_webview_create(MTY_App *app, MTY_Window window, const char * bool debug, WEBVIEW_READY ready_func, WEBVIEW_TEXT text_func, WEBVIEW_KEY key_func) { struct webview *ctx = MTY_Alloc(1, sizeof(struct webview)); - ctx->app = app; - ctx->window = window; - ctx->mutex = MTY_MutexCreate(); - ctx->ready_func = ready_func; - ctx->text_func = text_func; - ctx->key_func = key_func; - ctx->debug = debug; + + mty_webview_base_create(&ctx->base, app, window, dir, debug, ready_func, text_func, key_func); steam_global_init(dir ? dir : "."); - ctx->pushq = MTY_QueueCreate(50, 0); + ctx->mutex = MTY_MutexCreate(); bool r = SteamAPI_InitSafe(); if (!r) { @@ -393,16 +342,14 @@ void mty_webview_destroy(struct webview **webview) SteamAPI_Shutdown(); - if (ctx->pushq) - MTY_QueueFlush(ctx->pushq, MTY_Free); - - MTY_QueueDestroy(&ctx->pushq); MTY_MutexDestroy(&ctx->mutex); MTY_Free(ctx->source); MTY_Free(ctx->bgra); steam_global_destroy(); + mty_webview_base_destroy(&ctx->base); + MTY_Free(ctx); *webview = NULL; } @@ -426,10 +373,10 @@ void mty_webview_show(struct webview *ctx, bool show) ctx->visible = show; if (show) { - MTY_AppShowCursor(ctx->app, true); + MTY_AppShowCursor(ctx->base.app, true); } else { - MTY_AppSetCursor(ctx->app, MTY_CURSOR_NONE); + MTY_AppSetCursor(ctx->base.app, MTY_CURSOR_NONE); } SteamAPI_ISteamHTMLSurface_SetKeyFocus(ctx->surface, ctx->browser, show); @@ -443,24 +390,14 @@ bool mty_webview_is_visible(struct webview *ctx) void mty_webview_send_text(struct webview *ctx, const char *msg) { - if (!ctx->ready) { - MTY_QueuePushPtr(ctx->pushq, MTY_Strdup(msg), 0); + if (!ctx->base.ready) { + MTY_QueuePushPtr(ctx->base.pushq, MTY_Strdup(msg), 0); } else { if (ctx->browser == 0) return; - size_t msg_size = strlen(msg); - size_t size = msg_size * 4 + 64; - char *script = MTY_Alloc(size, 1); - - memcpy(script, "__MTY_WEBVIEW('", 15); - - MTY_BytesToBase64(msg, msg_size, script + 15, size - 15); - - size_t end = strlen(script); - memcpy(script + end, "');", 3); - + char *script = mty_webview_base_format_text(msg); SteamAPI_ISteamHTMLSurface_ExecuteJavascript(ctx->surface, ctx->browser, script); MTY_Free(script); } @@ -476,7 +413,7 @@ void mty_webview_reload(struct webview *ctx) void mty_webview_set_input_passthrough(struct webview *ctx, bool passthrough) { - ctx->passthrough = passthrough; + ctx->base.passthrough = passthrough; } static EHTMLKeyModifiers webview_mods(MTY_Mod mods) @@ -547,10 +484,10 @@ bool mty_webview_event(struct webview *ctx, MTY_Event *evt) evt->key.vkey, ctx->smods); } - if (ctx->passthrough) + if (ctx->base.passthrough) evt->type = MTY_EVENT_WEBVIEW_KEY; - return !ctx->passthrough; + return !ctx->base.passthrough; } case MTY_EVENT_TEXT: { wchar_t codepoint[8] = {0}; @@ -568,7 +505,7 @@ bool mty_webview_event(struct webview *ctx, MTY_Event *evt) void mty_webview_run(struct webview *ctx) { - if (!ctx->ready || ctx->visible) + if (!ctx->base.ready || ctx->visible) SteamAPI_RunCallbacks(); } @@ -585,7 +522,7 @@ void mty_webview_render(struct webview *ctx) ctx->desc.format = MTY_COLOR_FORMAT_UNKNOWN; } - MTY_WindowDrawQuad(ctx->app, ctx->window, ctx->bgra, &ctx->desc); + MTY_WindowDrawQuad(ctx->base.app, ctx->base.window, ctx->bgra, &ctx->desc); MTY_MutexUnlock(ctx->mutex); } diff --git a/src/unix/apple/webview.m b/src/unix/apple/webview.m index cea9189f..03d38f9c 100644 --- a/src/unix/apple/webview.m +++ b/src/unix/apple/webview.m @@ -7,20 +7,12 @@ #include #include "objc.h" -#include "web/keymap.h" struct webview { - MTY_App *app; - MTY_Window window; - MTY_Hash *keys; - MTY_Time ts; - WEBVIEW_READY ready_func; - WEBVIEW_TEXT text_func; - WEBVIEW_KEY key_func; - MTY_Queue *pushq; + struct webview_base base; + WKWebView *webview; - bool ready; - bool passthrough; + MTY_Time ts; }; @@ -55,56 +47,7 @@ static void msg_handler_userContentController_didReceiveScriptMessage(id self, S struct webview *ctx = OBJC_CTX(); const char *str = [message.body UTF8String]; - MTY_JSON *j = NULL; - - switch (str[0]) { - // MTY_EVENT_WEBVIEW_READY - case 'R': - ctx->ready = true; - - // Send any queued messages before the WebView became ready - for (char *msg = NULL; MTY_QueuePopPtr(ctx->pushq, 0, (void **) &msg, NULL);) { - mty_webview_send_text(ctx, msg); - MTY_Free(msg); - } - - ctx->ready_func(ctx->app, ctx->window); - break; - - // MTY_EVENT_WEBVIEW_TEXT - case 'T': - ctx->text_func(ctx->app, ctx->window, str + 1); - break; - - // MTY_EVENT_KEY - case 'D': - case 'U': - if (!ctx->passthrough) - break; - - j = MTY_JSONParse(str + 1); - if (!j) - break; - - const char *code = MTY_JSONObjGetStringPtr(j, "code"); - if (!code) - break; - - uint32_t jmods = 0; - if (!MTY_JSONObjGetInt(j, "mods", (int32_t *) &jmods)) - break; - - MTY_Key key = (MTY_Key) (uintptr_t) MTY_HashGet(ctx->keys, code) & 0xFFFF; - if (key == MTY_KEY_NONE) - break; - - MTY_Mod mods = web_keymap_mods(jmods); - - ctx->key_func(ctx->app, ctx->window, str[0] == 'D', key, mods); - break; - } - - MTY_JSONDestroy(&j); + mty_webview_base_handle_event(&ctx->base, str); } static Class msg_handler_class(void) @@ -145,14 +88,7 @@ static bool mty_webview_supported(void) struct webview *ctx = MTY_Alloc(1, sizeof(struct webview)); - ctx->app = app; - ctx->window = window; - ctx->ready_func = ready_func; - ctx->text_func = text_func; - ctx->key_func = key_func; - - ctx->keys = web_keymap_hash(); - ctx->pushq = MTY_QueueCreate(50, 0); + mty_webview_base_create(&ctx->base, app, window, dir, debug, ready_func, text_func, key_func); // WKWebView creation, start hidden #if TARGET_OS_OSX @@ -186,52 +122,12 @@ static bool mty_webview_supported(void) [ctx->webview.configuration.userContentController addScriptMessageHandler:handler name:@"native"]; // MTY javascript shim - WKUserScript *script = [[WKUserScript alloc] initWithSource: - @"const __MTY_MSGS = [];" - - @"let __MTY_WEBVIEW = b64 => {" - @"__MTY_MSGS.push(b64);" - @"};" - - @"window.MTY_NativeSendText = text => {" - @"window.webkit.messageHandlers.native.postMessage('T' + text);" - @"};" - - @"window.webkit.messageHandlers.native.postMessage('R');" - - @"const __MTY_INTERVAL = setInterval(() => {" - @"if (window.MTY_NativeListener) {" - @"__MTY_WEBVIEW = b64 => {window.MTY_NativeListener(atob(b64));};" - - @"for (let msg = __MTY_MSGS.shift(); msg; msg = __MTY_MSGS.shift())" - @"__MTY_WEBVIEW(msg);" - - @"clearInterval(__MTY_INTERVAL);" - @"}" - @"}, 100);" - - @"function __mty_key_to_json(evt) {" - @"let mods = 0;" - - @"if (evt.shiftKey) mods |= 0x01;" - @"if (evt.ctrlKey) mods |= 0x02;" - @"if (evt.altKey) mods |= 0x04;" - @"if (evt.metaKey) mods |= 0x08;" - - @"if (evt.getModifierState('CapsLock')) mods |= 0x10;" - @"if (evt.getModifierState('NumLock')) mods |= 0x20;" - - @"let cmd = evt.type == 'keydown' ? 'D' : 'U';" - @"let json = JSON.stringify({'code':evt.code,'mods':mods});" - - @"window.webkit.messageHandlers.native.postMessage(cmd + json);" - @"}" - - @"document.addEventListener('keydown', __mty_key_to_json);" - @"document.addEventListener('keyup', __mty_key_to_json);" - - injectionTime:WKUserScriptInjectionTimeAtDocumentStart - forMainFrameOnly:YES + const char *javascript = "window.native = {" + "postMessage: (message) => window.webkit.messageHandlers.native.postMessage(message)," + "addEventListener: (listener) => window.addEventListener('message', listener)," + "}"; + WKUserScript *script = [[WKUserScript alloc] initWithSource:[NSString stringWithUTF8String:javascript] + injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES ]; [ctx->webview.configuration.userContentController addUserScript:script]; @@ -251,11 +147,7 @@ void mty_webview_destroy(struct webview **webview) ctx->webview = nil; } - if (ctx->pushq) - MTY_QueueFlush(ctx->pushq, MTY_Free); - - MTY_QueueDestroy(&ctx->pushq); - MTY_HashDestroy(&ctx->keys, NULL); + mty_webview_base_destroy(&ctx->base); MTY_Free(ctx); *webview = NULL; @@ -305,19 +197,16 @@ bool mty_webview_is_visible(struct webview *ctx) void mty_webview_send_text(struct webview *ctx, const char *msg) { - if (!ctx->ready) { - MTY_QueuePushPtr(ctx->pushq, MTY_Strdup(msg), 0); + if (!ctx->base.ready) { + MTY_QueuePushPtr(ctx->base.pushq, MTY_Strdup(msg), 0); } else { - __block NSString *omsg = [NSString stringWithUTF8String:msg]; - - NSString *b64 = [[omsg dataUsingEncoding:NSUTF8StringEncoding] base64EncodedStringWithOptions:0]; - NSString *wrapped = [NSString stringWithFormat:@"__MTY_WEBVIEW('%@');", b64]; - - [ctx->webview evaluateJavaScript:wrapped completionHandler:^(id object, NSError *error) { + char *script = mty_webview_base_format_text(msg); + [ctx->webview evaluateJavaScript:[NSString stringWithUTF8String:script] completionHandler:^(id object, NSError *error) { if (error) - NSLog(@"'WKWebView:evaluateJavaScript' failed to evaluate string '%@'", omsg); + MTY_Log("'WKWebView:evaluateJavaScript' failed to evaluate string '%s'", script); }]; + MTY_Free(script); } } @@ -328,7 +217,7 @@ void mty_webview_reload(struct webview *ctx) void mty_webview_set_input_passthrough(struct webview *ctx, bool passthrough) { - ctx->passthrough = passthrough; + ctx->base.passthrough = passthrough; } bool mty_webview_event(struct webview *ctx, MTY_Event *evt) diff --git a/src/unix/linux/android/Matoya.java b/src/unix/linux/android/Matoya.java index 165dd495..b756469d 100644 --- a/src/unix/linux/android/Matoya.java +++ b/src/unix/linux/android/Matoya.java @@ -19,9 +19,14 @@ import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.webkit.JavascriptInterface; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.webkit.WebMessage; import android.text.InputType; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Color; import android.content.Context; import android.content.Intent; import android.content.ClipData; @@ -32,9 +37,11 @@ import android.util.Log; import android.util.DisplayMetrics; import android.util.Base64; +import android.widget.FrameLayout; import android.widget.Scroller; import android.os.Vibrator; import android.net.Uri; +import java.nio.charset.StandardCharsets; public class Matoya extends SurfaceView implements SurfaceHolder.Callback, @@ -46,11 +53,13 @@ public class Matoya extends SurfaceView implements ClipboardManager.OnPrimaryClipChangedListener { Activity activity; + FrameLayout layout; KeyCharacterMap kbmap; PointerIcon cursor; PointerIcon invisCursor; GestureDetector detector; ScaleGestureDetector sdetector; + WebView webview; Scroller scroller; Vibrator vibrator; boolean hiddenCursor; @@ -79,6 +88,7 @@ public class Matoya extends SurfaceView implements native void app_axis(int deviceId, float hatX, float hatY, float lX, float lY, float rX, float rY, float lT, float rT, float lTalt, float rTalt); native void app_unhandled_touch(int action, float x, float y, int fingers); + native void webview_handle_event(long ctx, String str); static { System.loadLibrary("main"); @@ -106,7 +116,9 @@ public Matoya(Activity activity) { Bitmap bm = BitmapFactory.decodeByteArray(iCursorData, 0, iCursorData.length, null); this.invisCursor = PointerIcon.create(bm, 0, 0); - activity.setContentView(this); + this.layout = new FrameLayout(this.activity); + this.layout.addView(this); + activity.setContentView(this.layout); ClipboardManager clipboard = (ClipboardManager) this.activity.getSystemService(Context.CLIPBOARD_SERVICE); clipboard.addPrimaryClipChangedListener(this); @@ -582,6 +594,114 @@ public boolean getRelativeMouse() { return this.hasPointerCapture(); } + // Webview + + public void webviewCreate(final long ctx, final boolean debug) + { + final Matoya self = this; + this.activity.runOnUiThread(new Runnable() { + @Override + public void run() { + WebView webview = new WebView(self.activity); + webview.setBackgroundColor(Color.TRANSPARENT); + webview.getSettings().setDomStorageEnabled(true); + webview.getSettings().setJavaScriptEnabled(true); + + if (debug) + webview.setWebContentsDebuggingEnabled(true); + + webview.addJavascriptInterface(new Object() { + @JavascriptInterface + public void postMessage(String text) { + webview_handle_event(ctx, text); + } + }, "webview"); + + webview.setWebViewClient(new WebViewClient() { + @Override + public void onPageStarted(WebView webview, String url, Bitmap favicon) { + String script = String.join("\n", + "window.native = {", + "postMessage: (message) => webview.postMessage(message),", + "addEventListener: (listener) => window.addEventListener('message', listener),", + "}" + ); + webview.evaluateJavascript(script, null); + } + }); + + self.layout.addView(webview); + self.webview = webview; + } + }); + } + + public void webviewDestroy() + { + final Matoya self = this; + this.activity.runOnUiThread(new Runnable() { + @Override + public void run() { + self.webview.destroy(); + self.webview = null; + } + }); + } + + public void webviewNavigate(final String source, final boolean url) + { + final Matoya self = this; + this.activity.runOnUiThread(new Runnable() { + @Override + public void run() { + if (url) { + self.webview.loadUrl(source, null); + + } else { + String encodedHtml = Base64.encodeToString(source.getBytes(StandardCharsets.US_ASCII), Base64.DEFAULT); + self.webview.loadData(encodedHtml, "text/html; charset=utf-8", "base64"); + } + } + }); + } + + public void webviewShow(final boolean show) + { + final Matoya self = this; + this.activity.runOnUiThread(new Runnable() { + @Override + public void run() { + self.webview.setVisibility(show ? View.VISIBLE : View.GONE); + } + }); + } + + public boolean webviewIsVisible() + { + return this.webview.getVisibility() == View.VISIBLE; + } + + public void webviewSendText(final String msg) + { + final Matoya self = this; + this.activity.runOnUiThread(new Runnable() { + @Override + public void run() { + self.webview.postWebMessage(new WebMessage(msg), Uri.parse("*")); + } + }); + } + + public void webviewReload() + { + final Matoya self = this; + this.activity.runOnUiThread(new Runnable() { + @Override + public void run() { + self.webview.reload(); + } + }); + } // Misc diff --git a/src/unix/linux/android/webview.c b/src/unix/linux/android/webview.c index 34bde07a..24c836bc 100644 --- a/src/unix/linux/android/webview.c +++ b/src/unix/linux/android/webview.c @@ -4,39 +4,79 @@ #include "webview.h" +#include +#include + +#include "matoya.h" +#include "app-os.h" +#include "jnih.h" + +struct webview { + struct webview_base base; +}; + struct webview *mty_webview_create(MTY_App *app, MTY_Window window, const char *dir, bool debug, WEBVIEW_READY ready_func, WEBVIEW_TEXT text_func, WEBVIEW_KEY key_func) { - return NULL; + struct webview *ctx = MTY_Alloc(1, sizeof(struct webview)); + + mty_webview_base_create(&ctx->base, app, window, dir, debug, ready_func, text_func, key_func); + mty_jni_void(MTY_GetJNIEnv(), mty_app_get_obj(), "webviewCreate", "(JZ)V", (long) ctx, debug); + + return ctx; } void mty_webview_destroy(struct webview **webview) { + if (!webview || !*webview) + return; + + struct webview *ctx = *webview; + *webview = NULL; + + mty_jni_void(MTY_GetJNIEnv(), mty_app_get_obj(), "webviewDestroy", "()V"); + mty_webview_base_destroy(&ctx->base); + + MTY_Free(ctx); } void mty_webview_navigate(struct webview *ctx, const char *source, bool url) { + jstring jsource = mty_jni_strdup(MTY_GetJNIEnv(), source); + mty_jni_void(MTY_GetJNIEnv(), mty_app_get_obj(), "webviewNavigate", "(Ljava/lang/String;Z)V", jsource, url); + mty_jni_free(MTY_GetJNIEnv(), jsource); } void mty_webview_show(struct webview *ctx, bool show) { + mty_jni_void(MTY_GetJNIEnv(), mty_app_get_obj(), "webviewShow", "(Z)V", show); } bool mty_webview_is_visible(struct webview *ctx) { - return false; + return mty_jni_bool(MTY_GetJNIEnv(), mty_app_get_obj(), "webviewIsVisible", "()Z"); } void mty_webview_send_text(struct webview *ctx, const char *msg) { + if (!ctx->base.ready) { + MTY_QueuePushPtr(ctx->base.pushq, MTY_Strdup(msg), 0); + + } else { + jstring jmsg = mty_jni_strdup(MTY_GetJNIEnv(), msg); + mty_jni_void(MTY_GetJNIEnv(), mty_app_get_obj(), "webviewSendText", "(Ljava/lang/String;)V", jmsg); + mty_jni_free(MTY_GetJNIEnv(), jmsg); + } } void mty_webview_reload(struct webview *ctx) { + mty_jni_void(MTY_GetJNIEnv(), mty_app_get_obj(), "webviewReload", "()V"); } void mty_webview_set_input_passthrough(struct webview *ctx, bool passthrough) { + ctx->base.passthrough = passthrough; } bool mty_webview_event(struct webview *ctx, MTY_Event *evt) @@ -46,15 +86,17 @@ bool mty_webview_event(struct webview *ctx, MTY_Event *evt) void mty_webview_run(struct webview *ctx) { + } void mty_webview_render(struct webview *ctx) { + } bool mty_webview_is_focussed(struct webview *ctx) { - return false; + return true; } bool mty_webview_is_steam(void) @@ -64,5 +106,13 @@ bool mty_webview_is_steam(void) bool mty_webview_is_available(void) { - return false; + return true; +} + +JNIEXPORT void JNICALL Java_group_matoya_lib_Matoya_webview_1handle_1event(JNIEnv *env, jobject obj, jlong jctx, jstring jstr) +{ + struct webview *ctx = (struct webview *) jctx; + char *str = mty_jni_cstrmov(MTY_GetJNIEnv(), jstr); + mty_webview_base_handle_event(&ctx->base, str); + MTY_Free(str); } diff --git a/src/unix/linux/x11/app.c b/src/unix/linux/x11/app.c index 86c87392..84b4c021 100644 --- a/src/unix/linux/x11/app.c +++ b/src/unix/linux/x11/app.c @@ -14,7 +14,7 @@ #include -#include "dl/libx11.c" +#include "dl/libx11.h" #include "hid/utils.h" #include "evdev.h" #include "keymap.h" diff --git a/src/unix/linux/x11/dl/libgtk.h b/src/unix/linux/x11/dl/libgtk.h new file mode 100644 index 00000000..7009153d --- /dev/null +++ b/src/unix/linux/x11/dl/libgtk.h @@ -0,0 +1,233 @@ +#pragma once + +#include "dl/sym.h" +#include "matoya.h" + +#include "dl/libx11.h" + + +// glib-2.0 + +#define G_SOURCE_FUNC(f) ((GSourceFunc)(void (*)(void))(f)) + +typedef char gchar; +typedef int gint; +typedef unsigned int guint; +typedef unsigned long gulong; +typedef double gdouble; +typedef signed long gssize; +typedef void *gpointer; +typedef bool gboolean; +typedef void GObject; +typedef void GClosure; +typedef void GCancellable; +typedef void GAsyncResult; + +typedef void (*GCallback)(void); +typedef gboolean (*GSourceFunc)(gpointer user_data); +typedef void (*GClosureNotify)(gpointer data, GClosure *closure); +typedef void (*GAsyncReadyCallback)(GObject *source_object, GAsyncResult *res, gpointer user_data); + +typedef enum { + G_CONNECT_AFTER = 1 << 0, + G_CONNECT_SWAPPED = 1 << 1 +} GConnectFlags; + +static guint (*g_idle_add)(GSourceFunc function, gpointer data); +static gulong (*g_signal_connect_data)(gpointer instance, const gchar *detailed_signal, GCallback c_handler, + gpointer data, GClosureNotify destroy_data, GConnectFlags connect_flags); +static gboolean (*g_setenv)(const gchar *variable, const gchar *value, gboolean overwrite); + + +// gdk-3 + +typedef void GdkDisplay; +typedef void GdkWindow; + +typedef struct { + gdouble red; + gdouble green; + gdouble blue; + gdouble alpha; +} GdkRGBA; + +static Display *(*gdk_x11_display_get_xdisplay)(GdkDisplay *display); +static GdkDisplay *(*gdk_window_get_display)(GdkWindow *window); +static Window (*gdk_x11_window_get_xid)(GdkWindow *window); +static void (*gdk_window_show)(GdkWindow *window); +static void (*gdk_window_hide)(GdkWindow *window); +static gboolean (*gdk_window_is_visible)(GdkWindow *window); + + +// gtk-3 + +typedef void GtkContainer; +typedef void GtkWidget; +typedef void GtkWindow; + +typedef enum { GTK_WINDOW_TOPLEVEL, GTK_WINDOW_POPUP } GtkWindowType; + +static GtkWidget *(*gtk_window_new)(GtkWindowType type); +static void (*gtk_widget_realize)(GtkWidget *widget); +static GdkWindow *(*gtk_widget_get_window)(GtkWidget *widget); +static void (*gtk_window_get_size)(GtkWindow *window, gint *width, gint *height); +static void (*gtk_window_resize)(GtkWindow *window, gint width, gint height); +static void (*gtk_container_add)(GtkContainer *container, GtkWidget *widget); +static void (*gtk_widget_set_app_paintable)(GtkWidget *widget, gboolean app_paintable); +static void (*gtk_widget_show_all)(GtkWidget *widget); +static gboolean (*gtk_init_check)(int *argc, char ***argv); +static void (*gtk_main)(void); +static void (*gtk_window_close)(GtkWindow *window); +static void (*gtk_widget_destroy)(GtkWidget *widget); +static void (*gtk_main_quit)(void); + + +// libjavascriptcoregtk-4.0 + +typedef void JSCValue; + +static char *(*jsc_value_to_string)(JSCValue *value); + + +// libwebkit2gtk-4.0 + +typedef void WebKitSettings; +typedef void WebKitWebView; +typedef void WebKitUserContentManager; +typedef void WebKitJavascriptResult; +typedef void WebKitUserScript; + +typedef enum { + WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, + WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, +} WebKitUserContentInjectedFrames; + +typedef enum { + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_END, +} WebKitUserScriptInjectionTime; + +static JSCValue *(*webkit_javascript_result_get_js_value)(WebKitJavascriptResult *js_result); +static GtkWidget *(*webkit_web_view_new)(void); +static void (*webkit_web_view_set_background_color)(WebKitWebView *web_view, const GdkRGBA *rgba); +static WebKitUserContentManager *(*webkit_web_view_get_user_content_manager)(WebKitWebView *web_view); +static gboolean (*webkit_user_content_manager_register_script_message_handler)(WebKitUserContentManager *manager, const gchar *name); +static WebKitUserScript *(*webkit_user_script_new)(const gchar *source, WebKitUserContentInjectedFrames injected_frames, + WebKitUserScriptInjectionTime injection_time, const gchar *const *allow_list, const gchar *const *block_list); +static void (*webkit_user_content_manager_add_script)(WebKitUserContentManager *manager, WebKitUserScript *script); +static WebKitSettings *(*webkit_web_view_get_settings)(WebKitWebView *web_view); +static void (*webkit_settings_set_enable_developer_extras)(WebKitSettings *settings, gboolean enabled); +static void (*webkit_web_view_load_uri)(WebKitWebView *web_view, const gchar *uri); +static void (*webkit_web_view_load_html)(WebKitWebView *web_view, const gchar *content, const gchar *base_uri); +static void (*webkit_web_view_evaluate_javascript)(WebKitWebView *web_view, const char *script, gssize length, const char *world_name, + const char *source_uri, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data); +static void (*webkit_web_view_reload)(WebKitWebView *web_view); + + +// Runtime open + +static MTY_Atomic32 LIBGTK_LOCK; +static MTY_SO *LIBGOBJECT_SO; +static MTY_SO *LIBGLIB_SO; +static MTY_SO *LIBDGK_SO; +static MTY_SO *LIBGTK_SO; +static MTY_SO *LIBJAVASCRIPT_SO; +static MTY_SO *LIBWEBKIT2_SO; +static bool LIBGTK_INIT; + +static void libgtk_global_destroy_lockfree(void) { + MTY_SOUnload(&LIBWEBKIT2_SO); + MTY_SOUnload(&LIBJAVASCRIPT_SO); + MTY_SOUnload(&LIBGTK_SO); + MTY_SOUnload(&LIBDGK_SO); + MTY_SOUnload(&LIBGLIB_SO); + MTY_SOUnload(&LIBGOBJECT_SO); + LIBGTK_INIT = false; +} + +static void __attribute__((destructor)) libgtk_global_destroy(void) { + MTY_GlobalLock(&LIBGTK_LOCK); + + libgtk_global_destroy_lockfree(); + + MTY_GlobalUnlock(&LIBGTK_LOCK); +} + +static bool libgtk_global_init(void) { + MTY_GlobalLock(&LIBGTK_LOCK); + + if (!LIBGTK_INIT) { + bool r = true; + + LIBGOBJECT_SO = MTY_SOLoad("libgobject-2.0.so"); + LIBGLIB_SO = MTY_SOLoad("libglib-2.0.so"); + LIBDGK_SO = MTY_SOLoad("libgdk-3.so"); + LIBGTK_SO = MTY_SOLoad("libgtk-3.so"); + + LIBJAVASCRIPT_SO = MTY_SOLoad("libjavascriptcoregtk-4.0.so"); + if (LIBJAVASCRIPT_SO == NULL) + LIBJAVASCRIPT_SO = MTY_SOLoad("libjavascriptcoregtk-4.1.so"); + + LIBWEBKIT2_SO = MTY_SOLoad("libwebkit2gtk-4.0.so"); + if (LIBWEBKIT2_SO == NULL) + LIBWEBKIT2_SO = MTY_SOLoad("libwebkit2gtk-4.1.so"); + + if (!LIBGOBJECT_SO || !LIBGLIB_SO || !LIBDGK_SO || !LIBGTK_SO || !LIBJAVASCRIPT_SO || !LIBWEBKIT2_SO) { + r = false; + goto except; + } + + LOAD_SYM(LIBGOBJECT_SO, g_signal_connect_data); + + LOAD_SYM(LIBGLIB_SO, g_idle_add); + LOAD_SYM(LIBGLIB_SO, g_setenv); + + LOAD_SYM(LIBDGK_SO, gdk_x11_display_get_xdisplay); + LOAD_SYM(LIBDGK_SO, gdk_window_get_display); + LOAD_SYM(LIBDGK_SO, gdk_x11_window_get_xid); + LOAD_SYM(LIBDGK_SO, gdk_window_show); + LOAD_SYM(LIBDGK_SO, gdk_window_hide); + LOAD_SYM(LIBDGK_SO, gdk_window_is_visible); + + LOAD_SYM(LIBGTK_SO, gtk_window_new); + LOAD_SYM(LIBGTK_SO, gtk_widget_realize); + LOAD_SYM(LIBGTK_SO, gtk_widget_get_window); + LOAD_SYM(LIBGTK_SO, gtk_window_get_size); + LOAD_SYM(LIBGTK_SO, gtk_window_resize); + LOAD_SYM(LIBGTK_SO, gtk_container_add); + LOAD_SYM(LIBGTK_SO, gtk_widget_set_app_paintable); + LOAD_SYM(LIBGTK_SO, gtk_widget_show_all); + LOAD_SYM(LIBGTK_SO, gtk_init_check); + LOAD_SYM(LIBGTK_SO, gtk_main); + LOAD_SYM(LIBGTK_SO, gtk_window_close); + LOAD_SYM(LIBGTK_SO, gtk_widget_destroy); + LOAD_SYM(LIBGTK_SO, gtk_main_quit); + + LOAD_SYM(LIBJAVASCRIPT_SO, jsc_value_to_string); + + LOAD_SYM(LIBWEBKIT2_SO, webkit_javascript_result_get_js_value); + LOAD_SYM(LIBWEBKIT2_SO, webkit_web_view_new); + LOAD_SYM(LIBWEBKIT2_SO, webkit_web_view_set_background_color); + LOAD_SYM(LIBWEBKIT2_SO, webkit_web_view_get_user_content_manager); + LOAD_SYM(LIBWEBKIT2_SO, webkit_user_content_manager_register_script_message_handler); + LOAD_SYM(LIBWEBKIT2_SO, webkit_user_script_new); + LOAD_SYM(LIBWEBKIT2_SO, webkit_user_content_manager_add_script); + LOAD_SYM(LIBWEBKIT2_SO, webkit_web_view_get_settings); + LOAD_SYM(LIBWEBKIT2_SO, webkit_settings_set_enable_developer_extras); + LOAD_SYM(LIBWEBKIT2_SO, webkit_web_view_load_uri); + LOAD_SYM(LIBWEBKIT2_SO, webkit_web_view_load_html); + LOAD_SYM(LIBWEBKIT2_SO, webkit_web_view_evaluate_javascript); + LOAD_SYM(LIBWEBKIT2_SO, webkit_web_view_reload); + + except: + + if (!r) + libgtk_global_destroy_lockfree(); + + LIBGTK_INIT = r; + } + + MTY_GlobalUnlock(&LIBGTK_LOCK); + + return LIBGTK_INIT; +} diff --git a/src/unix/linux/x11/dl/libx11.c b/src/unix/linux/x11/dl/libx11.c deleted file mode 100644 index be92e31e..00000000 --- a/src/unix/linux/x11/dl/libx11.c +++ /dev/null @@ -1,268 +0,0 @@ -// This Source Code Form is subject to the terms of the MIT License. -// If a copy of the MIT License was not distributed with this file, -// You can obtain one at https://spdx.org/licenses/MIT.html. - -#pragma once - -#include "matoya.h" -#include "libx11.h" - -#include "sym.h" - - -// X interface - -// Reference: https://code.woboq.org/qt5/include/X11/ - -static Display *(*XOpenDisplay)(const char *display_name); -static Screen *(*XScreenOfDisplay)(Display *display, int screen_number); -static Screen *(*XDefaultScreenOfDisplay)(Display *display); -static int (*XScreenNumberOfScreen)(Screen *screen); -static int (*XCloseDisplay)(Display *display); -static Window (*XDefaultRootWindow)(Display *display); -static Window (*XRootWindowOfScreen)(Screen *screen); -static Colormap (*XCreateColormap)(Display *display, Window w, Visual *visual, int alloc); -static Window (*XCreateWindow)(Display *display, Window parent, int x, int y, unsigned int width, - unsigned int height, unsigned int border_width, int depth, unsigned int class, Visual *visual, - unsigned long valuemask, XSetWindowAttributes *attributes); -static int (*XWithdrawWindow)(Display *display, Window w); -static int (*XMapRaised)(Display *display, Window w); -static int (*XSetInputFocus)(Display *display, Window focus, int revert_to, Time time); -static int (*XStoreName)(Display *display, Window w, const char *window_name); -static Status (*XGetWindowAttributes)(Display *display, Window w, XWindowAttributes *window_attributes_return); -static Bool (*XTranslateCoordinates)(Display *display, Window src_w, Window dest_w, int src_x, int src_y, - int *dest_x_return, int *dest_y_return, Window *child_return); -static KeySym (*XLookupKeysym)(XKeyEvent *key_event, int index); -static Status (*XSetWMProtocols)(Display *display, Window w, Atom *protocols, int count); -static Atom (*XInternAtom)(Display *display, const char *atom_name, Bool only_if_exists); -static int (*XNextEvent)(Display *display, XEvent *event_return); -static int (*XEventsQueued)(Display *display, int mode); -static int (*XMoveWindow)(Display *display, Window w, int x, int y); -static int (*XMoveResizeWindow)(Display *display, Window w, int x, int y, unsigned int width, unsigned int height); -static int (*XChangeProperty)(Display *display, Window w, Atom property, Atom type, int format, int mode, const unsigned char *data, int nelements); -static int (*XGetInputFocus)(Display *display, Window *focus_return, int *revert_to_return); -static char *(*XGetDefault)(Display *display, const char *program, const char *option); -static int (*XWidthOfScreen)(Screen *screen); -static int (*XHeightOfScreen)(Screen *screen); -static int (*XDestroyWindow)(Display *display, Window w); -static int (*XFree)(void *data); -static Status (*XInitThreads)(void); -static int (*Xutf8LookupString)(XIC ic, XKeyPressedEvent *event, char *buffer_return, int bytes_buffer, - KeySym *keysym_return, Status *status_return); -static XIM (*XOpenIM)(Display *dpy, struct _XrmHashBucketRec *rdb, char *res_name, char *res_class); -static Status (*XCloseIM)(XIM im); -static XIC (*XCreateIC)(XIM im, ...); -static void (*XDestroyIC)(XIC ic); -static Bool (*XGetEventData)(Display *dpy, XGenericEventCookie *cookie); -static int (*XGrabPointer)(Display *display, Window grab_window, Bool owner_events, unsigned int event_mask, int pointer_mode, - int keyboard_mode, Window confine_to, Cursor cursor, Time time); -static int (*XUngrabPointer)(Display *display, Time time); -static int (*XGrabKeyboard)(Display *display, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode, Time time); -static int (*XUngrabKeyboard)(Display *display, Time time); -static int (*XWarpPointer)(Display *display, Window src_w, Window dest_w, int src_x, int src_y, unsigned int src_width, - unsigned int src_height, int dest_x, int dest_y); -static int (*XSync)(Display *display, Bool discard); -static Pixmap (*XCreateBitmapFromData)(Display *display, Drawable d, _Xconst char *data, unsigned int width, unsigned int height); -static Cursor (*XCreatePixmapCursor)(Display *display, Pixmap source, Pixmap mask, XColor *foreground_color, - XColor *background_color, unsigned int x, unsigned int y); -static Cursor (*XCreateFontCursor)(Display *display, unsigned int shape); -static int (*XFreePixmap)(Display *display, Pixmap pixmap); -static int (*XDefineCursor)(Display *display, Window w, Cursor cursor); -static int (*XFreeCursor)(Display *display, Cursor cursor); -static Window (*XGetSelectionOwner)(Display *display, Atom selection); -static int (*XSetSelectionOwner)(Display *display, Atom selection, Window owner, Time time); -static char *(*XKeysymToString)(KeySym keysym); -static void (*XConvertCase)(KeySym keysym, KeySym *lower_return, KeySym *upper_return); -static Bool (*XQueryPointer)(Display *display, Window w, Window *root_return, Window *child_return, - int *root_x_return, int *root_y_return, int *win_x_return, int *win_y_return, unsigned int *mask_return); -static int (*XGetWindowProperty)(Display *display, Window w, Atom property, long long_offset, long long_length, - Bool delete, Atom req_type, Atom *actual_type_return, int *actual_format_return, unsigned long *nitems_return, - unsigned long *bytes_after_return, unsigned char **prop_return); -static Status (*XSendEvent)(Display *display, Window w, Bool propagate, long event_mask, XEvent *event_send); -static int (*XConvertSelection)(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); -static void (*XSetWMProperties)(Display *display, Window w, XTextProperty *window_name, XTextProperty *icon_name, char **argv, - int argc, XSizeHints *normal_hints, XWMHints *wm_hints, XClassHint *class_hints); -static XSizeHints *(*XAllocSizeHints)(void); -static XWMHints *(*XAllocWMHints)(void); -static XClassHint *(*XAllocClassHint)(void); -static int (*XResetScreenSaver)(Display *display); - -// Xfixes interface - -// Reference: https://code.woboq.org/kde/include/X11/extensions/ - -static Bool (*XFixesQueryExtension)(Display *dpy, int *event_base_return, int *error_base_return); -static void (*XFixesSelectSelectionInput)(Display *dpy, Window win, Atom selection, unsigned long eventMask); - - -// XKB interface (part of libX11 in modern times) - -static Bool (*XkbSetDetectableAutoRepeat)(Display *dpy, Bool detectable, Bool *supported); - - -// XI2 interface - -// Reference: https://code.woboq.org/kde/include/X11/extensions/ - -static int (*XISelectEvents)(Display *dpy, Window win, XIEventMask *masks, int num_masks); - - -// Xcursor interface - -static XcursorImage *(*XcursorImageCreate)(int width, int height); -static Cursor (*XcursorImageLoadCursor)(Display *dpy, const XcursorImage *image); -static void (*XcursorImageDestroy)(XcursorImage *image); - - -// GLX interface - -// Reference: https://code.woboq.org/qt5/include/GL/ - -static void *(*glXGetProcAddress)(const GLubyte *procName); -static void (*glXSwapBuffers)(Display *dpy, GLXDrawable drawable); -static XVisualInfo *(*glXChooseVisual)(Display *dpy, int screen, int *attribList); -static GLXContext (*glXCreateContext)(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct); -static Bool (*glXMakeCurrent)(Display *dpy, GLXDrawable drawable, GLXContext ctx); -static void (*glXDestroyContext)(Display *dpy, GLXContext ctx); -static GLXContext (*glXGetCurrentContext)(void); - -static MTY_Atomic32 LIBX11_LOCK; -static MTY_SO *LIBX11_SO; -static MTY_SO *LIBXFIXES_SO; -static MTY_SO *LIBXI_SO; -static MTY_SO *LIBXCURSOR_SO; -static MTY_SO *LIBGL_SO; -static bool LIBX11_INIT; - -static void libX11_global_destroy_lockfree(void) -{ - MTY_SOUnload(&LIBGL_SO); - MTY_SOUnload(&LIBXCURSOR_SO); - MTY_SOUnload(&LIBXI_SO); - MTY_SOUnload(&LIBXFIXES_SO); - MTY_SOUnload(&LIBX11_SO); - LIBX11_INIT = false; -} - -static void __attribute__((destructor)) libX11_global_destroy(void) -{ - MTY_GlobalLock(&LIBX11_LOCK); - - libX11_global_destroy_lockfree(); - - MTY_GlobalUnlock(&LIBX11_LOCK); -} - -static bool libX11_global_init(void) -{ - MTY_GlobalLock(&LIBX11_LOCK); - - if (!LIBX11_INIT) { - bool r = true; - - LIBX11_SO = MTY_SOLoad("libX11.so.6"); - LIBXFIXES_SO = MTY_SOLoad("libXfixes.so.3"); - LIBXI_SO = MTY_SOLoad("libXi.so.6"); - LIBXCURSOR_SO = MTY_SOLoad("libXcursor.so.1"); - LIBGL_SO = MTY_SOLoad("libGL.so.1"); - - if (!LIBX11_SO || !LIBGL_SO || !LIBXI_SO || !LIBXCURSOR_SO) { - r = false; - goto except; - } - - LOAD_SYM(LIBX11_SO, XOpenDisplay); - LOAD_SYM(LIBX11_SO, XScreenOfDisplay); - LOAD_SYM(LIBX11_SO, XDefaultScreenOfDisplay); - LOAD_SYM(LIBX11_SO, XScreenNumberOfScreen); - LOAD_SYM(LIBX11_SO, XCloseDisplay); - LOAD_SYM(LIBX11_SO, XDefaultRootWindow); - LOAD_SYM(LIBX11_SO, XRootWindowOfScreen); - LOAD_SYM(LIBX11_SO, XCreateColormap); - LOAD_SYM(LIBX11_SO, XCreateWindow); - LOAD_SYM(LIBX11_SO, XWithdrawWindow); - LOAD_SYM(LIBX11_SO, XMapRaised); - LOAD_SYM(LIBX11_SO, XSetInputFocus); - LOAD_SYM(LIBX11_SO, XStoreName); - LOAD_SYM(LIBX11_SO, XGetWindowAttributes); - LOAD_SYM(LIBX11_SO, XTranslateCoordinates); - LOAD_SYM(LIBX11_SO, XLookupKeysym); - LOAD_SYM(LIBX11_SO, XSetWMProtocols); - LOAD_SYM(LIBX11_SO, XInternAtom); - LOAD_SYM(LIBX11_SO, XNextEvent); - LOAD_SYM(LIBX11_SO, XEventsQueued); - LOAD_SYM(LIBX11_SO, XMoveWindow); - LOAD_SYM(LIBX11_SO, XMoveResizeWindow); - LOAD_SYM(LIBX11_SO, XChangeProperty); - LOAD_SYM(LIBX11_SO, XGetInputFocus); - LOAD_SYM(LIBX11_SO, XGetDefault); - LOAD_SYM(LIBX11_SO, XWidthOfScreen); - LOAD_SYM(LIBX11_SO, XHeightOfScreen); - LOAD_SYM(LIBX11_SO, XDestroyWindow); - LOAD_SYM(LIBX11_SO, XFree); - LOAD_SYM(LIBX11_SO, XInitThreads); - LOAD_SYM(LIBX11_SO, Xutf8LookupString); - LOAD_SYM(LIBX11_SO, XOpenIM); - LOAD_SYM(LIBX11_SO, XCloseIM); - LOAD_SYM(LIBX11_SO, XCreateIC); - LOAD_SYM(LIBX11_SO, XDestroyIC); - LOAD_SYM(LIBX11_SO, XGetEventData); - LOAD_SYM(LIBX11_SO, XGrabPointer); - LOAD_SYM(LIBX11_SO, XUngrabPointer); - LOAD_SYM(LIBX11_SO, XGrabKeyboard); - LOAD_SYM(LIBX11_SO, XUngrabKeyboard); - LOAD_SYM(LIBX11_SO, XWarpPointer); - LOAD_SYM(LIBX11_SO, XSync); - LOAD_SYM(LIBX11_SO, XCreateBitmapFromData); - LOAD_SYM(LIBX11_SO, XCreatePixmapCursor); - LOAD_SYM(LIBX11_SO, XCreateFontCursor); - LOAD_SYM(LIBX11_SO, XFreePixmap); - LOAD_SYM(LIBX11_SO, XDefineCursor); - LOAD_SYM(LIBX11_SO, XFreeCursor); - LOAD_SYM(LIBX11_SO, XGetSelectionOwner); - LOAD_SYM(LIBX11_SO, XSetSelectionOwner); - LOAD_SYM(LIBX11_SO, XKeysymToString); - LOAD_SYM(LIBX11_SO, XConvertCase); - LOAD_SYM(LIBX11_SO, XQueryPointer); - LOAD_SYM(LIBX11_SO, XGetWindowProperty); - LOAD_SYM(LIBX11_SO, XSendEvent); - LOAD_SYM(LIBX11_SO, XConvertSelection); - LOAD_SYM(LIBX11_SO, XSetWMProperties); - LOAD_SYM(LIBX11_SO, XAllocSizeHints); - LOAD_SYM(LIBX11_SO, XAllocWMHints); - LOAD_SYM(LIBX11_SO, XAllocClassHint); - LOAD_SYM(LIBX11_SO, XResetScreenSaver); - - if (LIBXFIXES_SO) { - LOAD_SYM_OPT(LIBXFIXES_SO, XFixesQueryExtension); - LOAD_SYM_OPT(LIBXFIXES_SO, XFixesSelectSelectionInput); - } - - LOAD_SYM_OPT(LIBX11_SO, XkbSetDetectableAutoRepeat); - - LOAD_SYM(LIBXI_SO, XISelectEvents); - - LOAD_SYM(LIBXCURSOR_SO, XcursorImageCreate); - LOAD_SYM(LIBXCURSOR_SO, XcursorImageLoadCursor); - LOAD_SYM(LIBXCURSOR_SO, XcursorImageDestroy); - - LOAD_SYM(LIBGL_SO, glXGetProcAddress); - LOAD_SYM(LIBGL_SO, glXSwapBuffers); - LOAD_SYM(LIBGL_SO, glXChooseVisual); - LOAD_SYM(LIBGL_SO, glXCreateContext); - LOAD_SYM(LIBGL_SO, glXMakeCurrent); - LOAD_SYM(LIBGL_SO, glXDestroyContext); - LOAD_SYM(LIBGL_SO, glXGetCurrentContext); - - except: - - if (!r) - libX11_global_destroy_lockfree(); - - LIBX11_INIT = r; - } - - MTY_GlobalUnlock(&LIBX11_LOCK); - - return LIBX11_INIT; -} diff --git a/src/unix/linux/x11/dl/libx11.h b/src/unix/linux/x11/dl/libx11.h index 33bf1aad..4373d3d7 100644 --- a/src/unix/linux/x11/dl/libx11.h +++ b/src/unix/linux/x11/dl/libx11.h @@ -4,8 +4,11 @@ #pragma once +#include "matoya.h" #include "glcorearb.h" +#include "sym.h" + // X interface @@ -568,3 +571,267 @@ struct xinfo { XVisualInfo *vis; Window window; }; + + +#if !defined(LIBX11_NO_SYMBOLS) + +// X interface + +// Reference: https://code.woboq.org/qt5/include/X11/ + +static Display *(*XOpenDisplay)(const char *display_name); +static Screen *(*XScreenOfDisplay)(Display *display, int screen_number); +static Screen *(*XDefaultScreenOfDisplay)(Display *display); +static int (*XScreenNumberOfScreen)(Screen *screen); +static int (*XCloseDisplay)(Display *display); +static Window (*XDefaultRootWindow)(Display *display); +static Window (*XRootWindowOfScreen)(Screen *screen); +static Colormap (*XCreateColormap)(Display *display, Window w, Visual *visual, int alloc); +static Window (*XCreateWindow)(Display *display, Window parent, int x, int y, unsigned int width, + unsigned int height, unsigned int border_width, int depth, unsigned int class, Visual *visual, + unsigned long valuemask, XSetWindowAttributes *attributes); +static int (*XWithdrawWindow)(Display *display, Window w); +static int (*XMapRaised)(Display *display, Window w); +static int (*XSetInputFocus)(Display *display, Window focus, int revert_to, Time time); +static int (*XStoreName)(Display *display, Window w, const char *window_name); +static Status (*XGetWindowAttributes)(Display *display, Window w, XWindowAttributes *window_attributes_return); +static Bool (*XTranslateCoordinates)(Display *display, Window src_w, Window dest_w, int src_x, int src_y, + int *dest_x_return, int *dest_y_return, Window *child_return); +static KeySym (*XLookupKeysym)(XKeyEvent *key_event, int index); +static Status (*XSetWMProtocols)(Display *display, Window w, Atom *protocols, int count); +static Atom (*XInternAtom)(Display *display, const char *atom_name, Bool only_if_exists); +static int (*XNextEvent)(Display *display, XEvent *event_return); +static int (*XEventsQueued)(Display *display, int mode); +static int (*XMoveWindow)(Display *display, Window w, int x, int y); +static int (*XMoveResizeWindow)(Display *display, Window w, int x, int y, unsigned int width, unsigned int height); +static int (*XChangeProperty)(Display *display, Window w, Atom property, Atom type, int format, int mode, const unsigned char *data, int nelements); +static int (*XGetInputFocus)(Display *display, Window *focus_return, int *revert_to_return); +static char *(*XGetDefault)(Display *display, const char *program, const char *option); +static int (*XWidthOfScreen)(Screen *screen); +static int (*XHeightOfScreen)(Screen *screen); +static int (*XDestroyWindow)(Display *display, Window w); +static int (*XFree)(void *data); +static Status (*XInitThreads)(void); +static int (*Xutf8LookupString)(XIC ic, XKeyPressedEvent *event, char *buffer_return, int bytes_buffer, + KeySym *keysym_return, Status *status_return); +static XIM (*XOpenIM)(Display *dpy, struct _XrmHashBucketRec *rdb, char *res_name, char *res_class); +static Status (*XCloseIM)(XIM im); +static XIC (*XCreateIC)(XIM im, ...); +static void (*XDestroyIC)(XIC ic); +static Bool (*XGetEventData)(Display *dpy, XGenericEventCookie *cookie); +static int (*XGrabPointer)(Display *display, Window grab_window, Bool owner_events, unsigned int event_mask, int pointer_mode, + int keyboard_mode, Window confine_to, Cursor cursor, Time time); +static int (*XUngrabPointer)(Display *display, Time time); +static int (*XGrabKeyboard)(Display *display, Window grab_window, Bool owner_events, int pointer_mode, int keyboard_mode, Time time); +static int (*XUngrabKeyboard)(Display *display, Time time); +static int (*XWarpPointer)(Display *display, Window src_w, Window dest_w, int src_x, int src_y, unsigned int src_width, + unsigned int src_height, int dest_x, int dest_y); +static int (*XSync)(Display *display, Bool discard); +static Pixmap (*XCreateBitmapFromData)(Display *display, Drawable d, _Xconst char *data, unsigned int width, unsigned int height); +static Cursor (*XCreatePixmapCursor)(Display *display, Pixmap source, Pixmap mask, XColor *foreground_color, + XColor *background_color, unsigned int x, unsigned int y); +static Cursor (*XCreateFontCursor)(Display *display, unsigned int shape); +static int (*XFreePixmap)(Display *display, Pixmap pixmap); +static int (*XDefineCursor)(Display *display, Window w, Cursor cursor); +static int (*XFreeCursor)(Display *display, Cursor cursor); +static Window (*XGetSelectionOwner)(Display *display, Atom selection); +static int (*XSetSelectionOwner)(Display *display, Atom selection, Window owner, Time time); +static char *(*XKeysymToString)(KeySym keysym); +static void (*XConvertCase)(KeySym keysym, KeySym *lower_return, KeySym *upper_return); +static Bool (*XQueryPointer)(Display *display, Window w, Window *root_return, Window *child_return, + int *root_x_return, int *root_y_return, int *win_x_return, int *win_y_return, unsigned int *mask_return); +static int (*XGetWindowProperty)(Display *display, Window w, Atom property, long long_offset, long long_length, + Bool delete, Atom req_type, Atom *actual_type_return, int *actual_format_return, unsigned long *nitems_return, + unsigned long *bytes_after_return, unsigned char **prop_return); +static Status (*XSendEvent)(Display *display, Window w, Bool propagate, long event_mask, XEvent *event_send); +static int (*XConvertSelection)(Display *display, Atom selection, Atom target, Atom property, Window requestor, Time time); +static void (*XSetWMProperties)(Display *display, Window w, XTextProperty *window_name, XTextProperty *icon_name, char **argv, + int argc, XSizeHints *normal_hints, XWMHints *wm_hints, XClassHint *class_hints); +static XSizeHints *(*XAllocSizeHints)(void); +static XWMHints *(*XAllocWMHints)(void); +static XClassHint *(*XAllocClassHint)(void); +static int (*XResetScreenSaver)(Display *display); +static int (*XReparentWindow)(Display *display, Window w, Window parent, int x, int y); + +// Xfixes interface + +// Reference: https://code.woboq.org/kde/include/X11/extensions/ + +static Bool (*XFixesQueryExtension)(Display *dpy, int *event_base_return, int *error_base_return); +static void (*XFixesSelectSelectionInput)(Display *dpy, Window win, Atom selection, unsigned long eventMask); + + +// XKB interface (part of libX11 in modern times) + +static Bool (*XkbSetDetectableAutoRepeat)(Display *dpy, Bool detectable, Bool *supported); + + +// XI2 interface + +// Reference: https://code.woboq.org/kde/include/X11/extensions/ + +static int (*XISelectEvents)(Display *dpy, Window win, XIEventMask *masks, int num_masks); + + +// Xcursor interface + +static XcursorImage *(*XcursorImageCreate)(int width, int height); +static Cursor (*XcursorImageLoadCursor)(Display *dpy, const XcursorImage *image); +static void (*XcursorImageDestroy)(XcursorImage *image); + + +// GLX interface + +// Reference: https://code.woboq.org/qt5/include/GL/ + +static void *(*glXGetProcAddress)(const GLubyte *procName); +static void (*glXSwapBuffers)(Display *dpy, GLXDrawable drawable); +static XVisualInfo *(*glXChooseVisual)(Display *dpy, int screen, int *attribList); +static GLXContext (*glXCreateContext)(Display *dpy, XVisualInfo *vis, GLXContext shareList, Bool direct); +static Bool (*glXMakeCurrent)(Display *dpy, GLXDrawable drawable, GLXContext ctx); +static void (*glXDestroyContext)(Display *dpy, GLXContext ctx); +static GLXContext (*glXGetCurrentContext)(void); + +static MTY_Atomic32 LIBX11_LOCK; +static MTY_SO *LIBX11_SO; +static MTY_SO *LIBXFIXES_SO; +static MTY_SO *LIBXI_SO; +static MTY_SO *LIBXCURSOR_SO; +static MTY_SO *LIBGL_SO; +static bool LIBX11_INIT; + +static void libX11_global_destroy_lockfree(void) +{ + MTY_SOUnload(&LIBGL_SO); + MTY_SOUnload(&LIBXCURSOR_SO); + MTY_SOUnload(&LIBXI_SO); + MTY_SOUnload(&LIBXFIXES_SO); + MTY_SOUnload(&LIBX11_SO); + LIBX11_INIT = false; +} + +static void __attribute__((destructor)) libX11_global_destroy(void) +{ + MTY_GlobalLock(&LIBX11_LOCK); + + libX11_global_destroy_lockfree(); + + MTY_GlobalUnlock(&LIBX11_LOCK); +} + +static bool libX11_global_init(void) +{ + MTY_GlobalLock(&LIBX11_LOCK); + + if (!LIBX11_INIT) { + bool r = true; + + LIBX11_SO = MTY_SOLoad("libX11.so.6"); + LIBXFIXES_SO = MTY_SOLoad("libXfixes.so.3"); + LIBXI_SO = MTY_SOLoad("libXi.so.6"); + LIBXCURSOR_SO = MTY_SOLoad("libXcursor.so.1"); + LIBGL_SO = MTY_SOLoad("libGL.so.1"); + + if (!LIBX11_SO || !LIBGL_SO || !LIBXI_SO || !LIBXCURSOR_SO) { + r = false; + goto except; + } + + LOAD_SYM(LIBX11_SO, XOpenDisplay); + LOAD_SYM(LIBX11_SO, XScreenOfDisplay); + LOAD_SYM(LIBX11_SO, XDefaultScreenOfDisplay); + LOAD_SYM(LIBX11_SO, XScreenNumberOfScreen); + LOAD_SYM(LIBX11_SO, XCloseDisplay); + LOAD_SYM(LIBX11_SO, XDefaultRootWindow); + LOAD_SYM(LIBX11_SO, XRootWindowOfScreen); + LOAD_SYM(LIBX11_SO, XCreateColormap); + LOAD_SYM(LIBX11_SO, XCreateWindow); + LOAD_SYM(LIBX11_SO, XWithdrawWindow); + LOAD_SYM(LIBX11_SO, XMapRaised); + LOAD_SYM(LIBX11_SO, XSetInputFocus); + LOAD_SYM(LIBX11_SO, XStoreName); + LOAD_SYM(LIBX11_SO, XGetWindowAttributes); + LOAD_SYM(LIBX11_SO, XTranslateCoordinates); + LOAD_SYM(LIBX11_SO, XLookupKeysym); + LOAD_SYM(LIBX11_SO, XSetWMProtocols); + LOAD_SYM(LIBX11_SO, XInternAtom); + LOAD_SYM(LIBX11_SO, XNextEvent); + LOAD_SYM(LIBX11_SO, XEventsQueued); + LOAD_SYM(LIBX11_SO, XMoveWindow); + LOAD_SYM(LIBX11_SO, XMoveResizeWindow); + LOAD_SYM(LIBX11_SO, XChangeProperty); + LOAD_SYM(LIBX11_SO, XGetInputFocus); + LOAD_SYM(LIBX11_SO, XGetDefault); + LOAD_SYM(LIBX11_SO, XWidthOfScreen); + LOAD_SYM(LIBX11_SO, XHeightOfScreen); + LOAD_SYM(LIBX11_SO, XDestroyWindow); + LOAD_SYM(LIBX11_SO, XFree); + LOAD_SYM(LIBX11_SO, XInitThreads); + LOAD_SYM(LIBX11_SO, Xutf8LookupString); + LOAD_SYM(LIBX11_SO, XOpenIM); + LOAD_SYM(LIBX11_SO, XCloseIM); + LOAD_SYM(LIBX11_SO, XCreateIC); + LOAD_SYM(LIBX11_SO, XDestroyIC); + LOAD_SYM(LIBX11_SO, XGetEventData); + LOAD_SYM(LIBX11_SO, XGrabPointer); + LOAD_SYM(LIBX11_SO, XUngrabPointer); + LOAD_SYM(LIBX11_SO, XGrabKeyboard); + LOAD_SYM(LIBX11_SO, XUngrabKeyboard); + LOAD_SYM(LIBX11_SO, XWarpPointer); + LOAD_SYM(LIBX11_SO, XSync); + LOAD_SYM(LIBX11_SO, XCreateBitmapFromData); + LOAD_SYM(LIBX11_SO, XCreatePixmapCursor); + LOAD_SYM(LIBX11_SO, XCreateFontCursor); + LOAD_SYM(LIBX11_SO, XFreePixmap); + LOAD_SYM(LIBX11_SO, XDefineCursor); + LOAD_SYM(LIBX11_SO, XFreeCursor); + LOAD_SYM(LIBX11_SO, XGetSelectionOwner); + LOAD_SYM(LIBX11_SO, XSetSelectionOwner); + LOAD_SYM(LIBX11_SO, XKeysymToString); + LOAD_SYM(LIBX11_SO, XConvertCase); + LOAD_SYM(LIBX11_SO, XQueryPointer); + LOAD_SYM(LIBX11_SO, XGetWindowProperty); + LOAD_SYM(LIBX11_SO, XSendEvent); + LOAD_SYM(LIBX11_SO, XConvertSelection); + LOAD_SYM(LIBX11_SO, XSetWMProperties); + LOAD_SYM(LIBX11_SO, XAllocSizeHints); + LOAD_SYM(LIBX11_SO, XAllocWMHints); + LOAD_SYM(LIBX11_SO, XAllocClassHint); + LOAD_SYM(LIBX11_SO, XResetScreenSaver); + LOAD_SYM(LIBX11_SO, XReparentWindow); + + if (LIBXFIXES_SO) { + LOAD_SYM_OPT(LIBXFIXES_SO, XFixesQueryExtension); + LOAD_SYM_OPT(LIBXFIXES_SO, XFixesSelectSelectionInput); + } + + LOAD_SYM_OPT(LIBX11_SO, XkbSetDetectableAutoRepeat); + + LOAD_SYM(LIBXI_SO, XISelectEvents); + + LOAD_SYM(LIBXCURSOR_SO, XcursorImageCreate); + LOAD_SYM(LIBXCURSOR_SO, XcursorImageLoadCursor); + LOAD_SYM(LIBXCURSOR_SO, XcursorImageDestroy); + + LOAD_SYM(LIBGL_SO, glXGetProcAddress); + LOAD_SYM(LIBGL_SO, glXSwapBuffers); + LOAD_SYM(LIBGL_SO, glXChooseVisual); + LOAD_SYM(LIBGL_SO, glXCreateContext); + LOAD_SYM(LIBGL_SO, glXMakeCurrent); + LOAD_SYM(LIBGL_SO, glXDestroyContext); + LOAD_SYM(LIBGL_SO, glXGetCurrentContext); + + except: + + if (!r) + libX11_global_destroy_lockfree(); + + LIBX11_INIT = r; + } + + MTY_GlobalUnlock(&LIBX11_LOCK); + + return LIBX11_INIT; +} + +#endif \ No newline at end of file diff --git a/src/unix/linux/x11/gfx/gl-ctx.c b/src/unix/linux/x11/gfx/gl-ctx.c index e9cab395..3dbbbdee 100644 --- a/src/unix/linux/x11/gfx/gl-ctx.c +++ b/src/unix/linux/x11/gfx/gl-ctx.c @@ -6,7 +6,7 @@ GFX_CTX_PROTOTYPES(_gl_) #include "gfx/gl/glproc.c" -#include "dl/libx11.c" +#include "dl/libx11.h" struct gl_ctx { Display *display; diff --git a/src/unix/linux/x11/webview.c b/src/unix/linux/x11/webview.c index 34bde07a..b88a222f 100644 --- a/src/unix/linux/x11/webview.c +++ b/src/unix/linux/x11/webview.c @@ -4,39 +4,272 @@ #include "webview.h" +#include +// #include +// #include + +#include "matoya.h" + +#include "dl/libgtk.h" + +#define DISPATCH(func, param, should_free) g_idle_add((GSourceFunc) func, mty_webview_create_event(ctx, (void *) (param), should_free)) + +struct webview { + struct webview_base base; + + MTY_Thread *thread; + Display *display; + Window x11_window; + GtkWindow *gtk_window; + WebKitWebView *webview; +}; + +struct mty_webview_event { + struct webview *context; + void *data; + bool should_free; +}; + +static struct mty_webview_event *mty_webview_create_event(struct webview *ctx, void *data, bool should_free) +{ + struct mty_webview_event *event = MTY_Alloc(1, sizeof(struct mty_webview_event)); + + event->context = ctx; + event->data = data; + event->should_free = should_free; + + return event; +} + +static void mty_webview_destroy_event(struct mty_webview_event **event) +{ + if (!event || !*event) + return; + + struct mty_webview_event *ev = *event; + + if (ev->should_free) + MTY_Free(ev->data); + + MTY_Free(ev); + *event = NULL; +} + +static void handle_script_message(WebKitUserContentManager *manager, WebKitJavascriptResult *result, void *opaque) +{ + struct webview *ctx = opaque; + + JSCValue *value = webkit_javascript_result_get_js_value(result); + char *str = jsc_value_to_string(value); + mty_webview_base_handle_event(&ctx->base, str); + MTY_Free(str); +} + +static bool _mty_webview_resize(void *opaque) +{ + struct webview *ctx = opaque; + + XWindowAttributes attr = {0}; + XGetWindowAttributes(ctx->display, ctx->x11_window, &attr); + + int32_t width = 0, height = 0; + gtk_window_get_size(ctx->gtk_window, &width, &height); + + if (width != attr.width || height != attr.height) + gtk_window_resize(ctx->gtk_window, attr.width, attr.height); + + return true; +} + +static bool _mty_webview_create(struct mty_webview_event *event) +{ + struct webview *ctx = event->context; + + ctx->gtk_window = (GtkWindow *) gtk_window_new(GTK_WINDOW_POPUP); + gtk_widget_realize((GtkWidget *) ctx->gtk_window); + + GdkWindow *gdk_window = gtk_widget_get_window((GtkWidget *) ctx->gtk_window); + Display *display = gdk_x11_display_get_xdisplay(gdk_window_get_display(gdk_window)); + XReparentWindow(display, gdk_x11_window_get_xid(gdk_window), ctx->x11_window, 0, 0); + + ctx->webview = (WebKitWebView *) webkit_web_view_new(); + gtk_container_add((GtkContainer *) ctx->gtk_window, (GtkWidget *) ctx->webview); + + gtk_widget_set_app_paintable((GtkWidget *) ctx->gtk_window, true); + webkit_web_view_set_background_color(ctx->webview, & (GdkRGBA) {0}); + + WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager(ctx->webview); + g_signal_connect_data(manager, "script-message-received::native", (GCallback) handle_script_message, (ctx), NULL, 0); + webkit_user_content_manager_register_script_message_handler(manager, "native"); + + const char *javascript = "window.native = {" + "postMessage: (message) => window.webkit.messageHandlers.native.postMessage(message)," + "addEventListener: (listener) => window.addEventListener('message', listener)," + "}"; + WebKitUserContentInjectedFrames injected_frames = WEBKIT_USER_CONTENT_INJECT_TOP_FRAME; + WebKitUserScriptInjectionTime injection_time = WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START; + WebKitUserScript *script = webkit_user_script_new(javascript, injected_frames, injection_time, NULL, NULL); + webkit_user_content_manager_add_script(manager, script); + + WebKitSettings *settings = webkit_web_view_get_settings(event->context->webview); + webkit_settings_set_enable_developer_extras(settings, ctx->base.debug); + + g_idle_add((GSourceFunc) _mty_webview_resize, ctx); + + gtk_widget_show_all((GtkWidget *) ctx->gtk_window); + + mty_webview_destroy_event(&event); + + return false; +} + +static void *mty_webview_thread_func(void *opaque) +{ + gtk_init_check(0, NULL); + gtk_main(); + + return NULL; +} + struct webview *mty_webview_create(MTY_App *app, MTY_Window window, const char *dir, bool debug, WEBVIEW_READY ready_func, WEBVIEW_TEXT text_func, WEBVIEW_KEY key_func) { - return NULL; + if (!libX11_global_init() || !libgtk_global_init()) + return NULL; + + struct webview *ctx = MTY_Alloc(1, sizeof(struct webview)); + + g_setenv("GDK_BACKEND", "x11", true); + + mty_webview_base_create(&ctx->base, app, window, dir, debug, ready_func, text_func, key_func); + + ctx->thread = MTY_ThreadCreate(mty_webview_thread_func, NULL); + + struct xinfo *info = MTY_WindowGetNative(ctx->base.app, ctx->base.window); + ctx->display = info->display; + ctx->x11_window = info->window; + + DISPATCH(_mty_webview_create, NULL, false); + + return ctx; +} + +static bool _mty_webview_destroy(struct mty_webview_event *event) +{ + struct webview *ctx = event->context; + + if (!ctx) + return false; + + gtk_window_close(ctx->gtk_window); + gtk_widget_destroy((GtkWidget *) ctx->webview); + gtk_widget_destroy((GtkWidget *) ctx->gtk_window); + gtk_main_quit(); + + mty_webview_destroy_event(&event); + + return false; } void mty_webview_destroy(struct webview **webview) { + if (!webview || !*webview) + return; + + struct webview *ctx = *webview; + *webview = NULL; + + DISPATCH(_mty_webview_destroy, NULL, false); + + MTY_ThreadDestroy(&ctx->thread); + + mty_webview_base_destroy(&ctx->base); + + libX11_global_destroy(); + libgtk_global_destroy(); + + MTY_Free(ctx); +} + +static bool _mty_webview_navigate_url(struct mty_webview_event *event) +{ + webkit_web_view_load_uri(event->context->webview, event->data); + mty_webview_destroy_event(&event); + + return false; +} + +static bool _mty_webview_navigate_html(struct mty_webview_event *event) +{ + webkit_web_view_load_html(event->context->webview, event->data, NULL); + mty_webview_destroy_event(&event); + + return false; +} + +static bool _mty_webview_show(struct mty_webview_event *event) +{ + GdkWindow *window = gtk_widget_get_window((GtkWidget *) event->context->gtk_window); + + if (event->data) { + gdk_window_show(window); + + } else { + gdk_window_hide(window); + } + + return false; +} + +static bool _mty_webview_send_text(struct mty_webview_event *event) +{ + char *script = mty_webview_base_format_text(event->data); + webkit_web_view_evaluate_javascript(event->context->webview, script, -1, NULL, NULL, NULL, NULL, NULL); + MTY_Free(script); + + return false; +} + +static bool _mty_webview_reload(struct mty_webview_event *event) +{ + webkit_web_view_reload(event->context->webview); + + return false; } void mty_webview_navigate(struct webview *ctx, const char *source, bool url) { + if (url) { + DISPATCH(_mty_webview_navigate_url, MTY_Strdup(source), true); + + } else { + DISPATCH(_mty_webview_navigate_html, MTY_Strdup(source), true); + } } void mty_webview_show(struct webview *ctx, bool show) { + DISPATCH(_mty_webview_show, show, false); } bool mty_webview_is_visible(struct webview *ctx) { - return false; + return gdk_window_is_visible(gtk_widget_get_window((GtkWidget *) ctx->gtk_window)); } void mty_webview_send_text(struct webview *ctx, const char *msg) { + DISPATCH(_mty_webview_send_text, MTY_Strdup(msg), true); } void mty_webview_reload(struct webview *ctx) { + DISPATCH(_mty_webview_reload, NULL, false); } void mty_webview_set_input_passthrough(struct webview *ctx, bool passthrough) { + ctx->base.passthrough = passthrough; } bool mty_webview_event(struct webview *ctx, MTY_Event *evt) @@ -54,7 +287,7 @@ void mty_webview_render(struct webview *ctx) bool mty_webview_is_focussed(struct webview *ctx) { - return false; + return true; } bool mty_webview_is_steam(void) @@ -64,5 +297,5 @@ bool mty_webview_is_steam(void) bool mty_webview_is_available(void) { - return false; + return true; } diff --git a/src/unix/web/matoya-worker.js b/src/unix/web/matoya-worker.js index db1b241a..5f429479 100644 --- a/src/unix/web/matoya-worker.js +++ b/src/unix/web/matoya-worker.js @@ -815,6 +815,31 @@ const MTY_WEB_API = { setTimeout(step, 0); throw 'MTY_RunAndYield halted execution'; }, + web_webview_create: function(ctx) { + postMessage({type: 'wv-create', ctx}); + }, + web_webview_destroy: function() { + postMessage({type: 'wv-destroy'}); + }, + web_webview_navigate: function(csource, url) { + const source = mty_str_to_js(csource); + postMessage({type: 'wv-navigate', source, url}); + }, + web_webview_show: function(show) { + postMessage({type: 'wv-show', show}); + }, + web_webview_is_visible: function() { + postMessage({type: 'wv-is-visible', sync: MTY.sync, sab: MTY.sab}); + mty_wait(MTY.sync); + return MTY.sab[0] != 0; + }, + web_webview_send_text: function(cmessage) { + const message = mty_str_to_js(cmessage); + postMessage({type: 'wv-send-text', message}); + }, + web_webview_reload: function() { + postMessage({type: 'wv-reload'}); + }, }; @@ -1235,6 +1260,11 @@ onmessage = async (ev) => { mty_free(cmem); break; } + case 'wv-event': + const buf = mty_alloc(1, msg.message.length + 1); + mty_str_to_c(msg.message, buf, msg.message.length + 1); + MTY.exports.mty_webview_handle_event(msg.ctx, buf); + break; } }; diff --git a/src/unix/web/matoya.js b/src/unix/web/matoya.js index 90229262..3c802287 100644 --- a/src/unix/web/matoya.js +++ b/src/unix/web/matoya.js @@ -1082,5 +1082,51 @@ async function mty_thread_message(ev) { mty_signal(msg.sync); break; + case 'wv-create': + MTY.webview = document.createElement('iframe'); + + MTY.webview.style.visibility = 'hidden'; + MTY.webview.style.position = 'fixed'; + MTY.webview.style.border = 'none'; + MTY.webview.style.width = '100%'; + MTY.webview.style.height = '100%'; + MTY.webview.style.inset = '0'; + + MTY.webview.onload = () => { + setTimeout(() => MTY.webview.style.visibility = 'visible', 250); + }; + + window.addEventListener('message', function (message) { + MTY.mainThread.postMessage({type: 'wv-event', ctx: msg.ctx, message: message.data}); + }); + + document.body.appendChild(MTY.webview); + break; + case 'wv-destroy': + document.removeChild(MTY.webview); + delete MTY.webview; + break; + case 'wv-navigate': + if (msg.url) { + MTY.webview.src = msg.source; + + } else { + const blob = new Blob([msg.source], { type: 'text/html' }); + MTY.webview.src = URL.createObjectURL(blob); + } + break; + case 'wv-show': + MTY.webview.style.visibility = msg.show ? 'visible' : 'hidden'; + break; + case 'wv-is-visible': + msg.sab[0] = MTY.webview.style.visibility != 'visible'; + mty_signal(msg.sync); + break; + case 'wv-send-text': + MTY.webview.contentWindow.postMessage(msg.message, '*'); + break; + case 'wv-reload': + MTY.webview.contentWindow.location.reload(); + break; } } diff --git a/src/unix/web/webview.c b/src/unix/web/webview.c index 34bde07a..72adc8c3 100644 --- a/src/unix/web/webview.c +++ b/src/unix/web/webview.c @@ -4,39 +4,81 @@ #include "webview.h" +#include +#include + +#include "matoya.h" + +struct webview { + struct webview_base base; +}; + +void web_webview_create(struct webview *ctx); +void web_webview_destroy(); +void web_webview_navigate(const char *source, bool url); +void web_webview_show(bool show); +bool web_webview_is_visible(); +void web_webview_send_text(const char *message); +void web_webview_reload(); + struct webview *mty_webview_create(MTY_App *app, MTY_Window window, const char *dir, bool debug, WEBVIEW_READY ready_func, WEBVIEW_TEXT text_func, WEBVIEW_KEY key_func) { - return NULL; + struct webview *ctx = MTY_Alloc(1, sizeof(struct webview)); + + mty_webview_base_create(&ctx->base, app, window, dir, debug, ready_func, text_func, key_func); + web_webview_create(ctx); + + return ctx; } void mty_webview_destroy(struct webview **webview) { + if (!webview || !*webview) + return; + + struct webview *ctx = *webview; + *webview = NULL; + + web_webview_destroy(); + mty_webview_base_destroy(&ctx->base); + + MTY_Free(ctx); } void mty_webview_navigate(struct webview *ctx, const char *source, bool url) { + web_webview_navigate(source, url); } void mty_webview_show(struct webview *ctx, bool show) { + web_webview_show(show); } bool mty_webview_is_visible(struct webview *ctx) { - return false; + return web_webview_is_visible(); } void mty_webview_send_text(struct webview *ctx, const char *msg) { + if (!ctx->base.ready) { + MTY_QueuePushPtr(ctx->base.pushq, MTY_Strdup(msg), 0); + + } else { + web_webview_send_text(msg); + } } void mty_webview_reload(struct webview *ctx) { + web_webview_reload(); } void mty_webview_set_input_passthrough(struct webview *ctx, bool passthrough) { + ctx->base.passthrough = passthrough; } bool mty_webview_event(struct webview *ctx, MTY_Event *evt) @@ -54,7 +96,7 @@ void mty_webview_render(struct webview *ctx) bool mty_webview_is_focussed(struct webview *ctx) { - return false; + return true; } bool mty_webview_is_steam(void) @@ -64,5 +106,12 @@ bool mty_webview_is_steam(void) bool mty_webview_is_available(void) { - return false; + return true; +} + +__attribute__((export_name("mty_webview_handle_event"))) +void mty_webview_handle_event(struct webview *ctx, char *str) +{ + mty_webview_base_handle_event(&ctx->base, str); + MTY_Free(str); } diff --git a/src/webview.c b/src/webview.c new file mode 100644 index 00000000..69a4a78c --- /dev/null +++ b/src/webview.c @@ -0,0 +1,98 @@ +#include "webview.h" + +#include +#include + +#include "unix/web/keymap.h" + +void mty_webview_base_create(struct webview_base *ctx, MTY_App *app, MTY_Window window, const char *dir, + bool debug, WEBVIEW_READY ready_func, WEBVIEW_TEXT text_func, WEBVIEW_KEY key_func) +{ + ctx->app = app; + ctx->window = window; + ctx->ready_func = ready_func; + ctx->text_func = text_func; + ctx->key_func = key_func; + ctx->keys = web_keymap_hash(); + ctx->pushq = MTY_QueueCreate(50, 0); + ctx->debug = debug; + ctx->dir = dir; +} + +void mty_webview_base_destroy(struct webview_base *ctx) +{ + if (ctx->pushq) + MTY_QueueFlush(ctx->pushq, MTY_Free); + + MTY_QueueDestroy(&ctx->pushq); + MTY_HashDestroy(&ctx->keys, NULL); +} + +void mty_webview_base_handle_event(struct webview_base *ctx, const char *str) +{ + MTY_JSON *j = NULL; + + switch (str[0]) { + // MTY_EVENT_WEBVIEW_READY + case 'R': + ctx->ready = true; + + // Send any queued messages before the WebView became ready + for (char *msg = NULL; MTY_QueuePopPtr(ctx->pushq, 0, (void **) &msg, NULL);) { + mty_webview_send_text((struct webview *) ctx, msg); + MTY_Free(msg); + } + + ctx->ready_func(ctx->app, ctx->window); + break; + + // MTY_EVENT_WEBVIEW_TEXT + case 'T': + ctx->text_func(ctx->app, ctx->window, str + 1); + break; + + // MTY_EVENT_KEY + case 'D': + case 'U': + if (!ctx->passthrough) + break; + + j = MTY_JSONParse(str + 1); + if (!j) + break; + + const char *code = MTY_JSONObjGetStringPtr(j, "code"); + if (!code) + break; + + uint32_t jmods = 0; + if (!MTY_JSONObjGetInt(j, "mods", (int32_t *) &jmods)) + break; + + MTY_Key key = (MTY_Key) (uintptr_t) MTY_HashGet(ctx->keys, code) & 0xFFFF; + if (key == MTY_KEY_NONE) + break; + + MTY_Mod mods = web_keymap_mods(jmods); + + ctx->key_func(ctx->app, ctx->window, str[0] == 'D', key, mods); + break; + } + + MTY_JSONDestroy(&j); +} + +char *mty_webview_base_format_text(const char *msg) +{ + uint32_t msg_len = strlen(msg); + uint32_t b64_len = msg_len * 4; + char *b64 = MTY_Alloc(b64_len, 1); + + MTY_BytesToBase64(msg, msg_len, b64, b64_len); + + char *script = MTY_SprintfD("window.postMessage(atob('%s'));", b64); + + MTY_Free(b64); + + return script; +} diff --git a/src/webview.h b/src/webview.h index e1bea375..cf0a2ead 100644 --- a/src/webview.h +++ b/src/webview.h @@ -12,6 +12,27 @@ typedef void (*WEBVIEW_READY)(MTY_App *app, MTY_Window window); typedef void (*WEBVIEW_TEXT)(MTY_App *app, MTY_Window window, const char *text); typedef void (*WEBVIEW_KEY)(MTY_App *app, MTY_Window window, bool pressed, MTY_Key key, MTY_Mod mods); +struct webview_base { + MTY_App *app; + MTY_Window window; + WEBVIEW_READY ready_func; + WEBVIEW_TEXT text_func; + WEBVIEW_KEY key_func; + MTY_Queue *pushq; + MTY_Hash *keys; + const char *dir; + bool ready; + bool passthrough; + bool focussed; + bool debug; +}; + +void mty_webview_base_create(struct webview_base *ctx, MTY_App *app, MTY_Window window, const char *dir, + bool debug, WEBVIEW_READY ready_func, WEBVIEW_TEXT text_func, WEBVIEW_KEY key_func); +void mty_webview_base_destroy(struct webview_base *ctx); +void mty_webview_base_handle_event(struct webview_base *ctx, const char *str); +char *mty_webview_base_format_text(const char *msg); + struct webview *mty_webview_create(MTY_App *app, MTY_Window window, const char *dir, bool debug, WEBVIEW_READY ready_func, WEBVIEW_TEXT text_func, WEBVIEW_KEY key_func); void mty_webview_destroy(struct webview **webview); diff --git a/src/windows/webview.c b/src/windows/webview-win.c similarity index 81% rename from src/windows/webview.c rename to src/windows/webview-win.c index 2d680600..57918a24 100644 --- a/src/windows/webview.c +++ b/src/windows/webview-win.c @@ -13,8 +13,6 @@ #define COBJMACROS #include "webview2.h" -#include "unix/web/keymap.h" - // https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/distribution#detect-if-a-webview2-runtime-is-already-installed #define WEBVIEW_REG_PATH L"Software\\Microsoft\\EdgeUpdate\\%s\\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" @@ -50,13 +48,8 @@ struct webview_handler3 { }; struct webview { - MTY_App *app; - MTY_Window window; - MTY_Hash *keys; - WEBVIEW_READY ready_func; - WEBVIEW_TEXT text_func; - WEBVIEW_KEY key_func; - MTY_Queue *pushq; + struct webview_base base; + HMODULE lib; ICoreWebView2Controller2 *controller; ICoreWebView2 *webview; @@ -68,10 +61,6 @@ struct webview { ICoreWebView2EnvironmentOptions opts; WCHAR *source; bool url; - bool debug; - bool passthrough; - bool focussed; - bool ready; }; @@ -112,9 +101,9 @@ static HRESULT STDMETHODCALLTYPE h3_Invoke_GotFocus(ICoreWebView2FocusChangedEve struct webview_handler3 *handler = (struct webview_handler3 *) This; struct webview *ctx = handler->opaque; - ctx->focussed = true; + ctx->base.focussed = true; if (mty_webview_is_visible(ctx)) - PostMessage(MTY_WindowGetNative(ctx->app, ctx->window), WM_SETFOCUS, 0, 0); + PostMessage(MTY_WindowGetNative(ctx->base.app, ctx->base.window), WM_SETFOCUS, 0, 0); return S_OK; } @@ -125,9 +114,9 @@ static HRESULT STDMETHODCALLTYPE h3_Invoke_LostFocus(ICoreWebView2FocusChangedEv struct webview_handler3 *handler = (struct webview_handler3 *) This; struct webview *ctx = handler->opaque; - ctx->focussed = false; + ctx->base.focussed = false; if (mty_webview_is_visible(ctx)) - PostMessage(MTY_WindowGetNative(ctx->app, ctx->window), WM_KILLFOCUS, 0, 0); + PostMessage(MTY_WindowGetNative(ctx->base.app, ctx->base.window), WM_KILLFOCUS, 0, 0); return S_OK; } @@ -157,56 +146,7 @@ static HRESULT STDMETHODCALLTYPE h2_Invoke(ICoreWebView2WebMessageReceivedEventH if (e == S_OK) { char *str = MTY_WideToMultiD(wstr); - MTY_JSON *j = NULL; - - switch (str[0]) { - // MTY_EVENT_WEBVIEW_READY - case 'R': - ctx->ready = true; - - // Send any queued messages before the WebView became ready - for (WCHAR *wmsg = NULL; MTY_QueuePopPtr(ctx->pushq, 0, &wmsg, NULL);) { - ICoreWebView2_PostWebMessageAsString(ctx->webview, wmsg); - MTY_Free(wmsg); - } - - ctx->ready_func(ctx->app, ctx->window); - break; - - // MTY_EVENT_WEBVIEW_TEXT - case 'T': - ctx->text_func(ctx->app, ctx->window, str + 1); - break; - - // MTY_EVENT_KEY - case 'D': - case 'U': - if (!ctx->passthrough) - break; - - j = MTY_JSONParse(str + 1); - if (!j) - break; - - const char *code = MTY_JSONObjGetStringPtr(j, "code"); - if (!code) - break; - - uint32_t jmods = 0; - if (!MTY_JSONObjGetInt(j, "mods", (int32_t *) &jmods)) - break; - - MTY_Key key = (MTY_Key) (uintptr_t) MTY_HashGet(ctx->keys, code) & 0xFFFF; - if (key == MTY_KEY_NONE) - break; - - MTY_Mod mods = web_keymap_mods(jmods); - - ctx->key_func(ctx->app, ctx->window, str[0] == 'D', key, mods); - break; - } - - MTY_JSONDestroy(&j); + mty_webview_base_handle_event(&ctx->base, str); MTY_Free(str); } @@ -229,7 +169,7 @@ static HRESULT STDMETHODCALLTYPE h1_query_interface(void *This, static void webview_update_size(struct webview *ctx) { - MTY_Size size = MTY_WindowGetSize(ctx->app, ctx->window); + MTY_Size size = MTY_WindowGetSize(ctx->base.app, ctx->base.window); RECT bounds = {0}; bounds.right = size.w; @@ -277,8 +217,8 @@ static HRESULT STDMETHODCALLTYPE h1_Invoke(ICoreWebView2CreateCoreWebView2Contro ICoreWebView2Settings *settings = NULL; ICoreWebView2_get_Settings(ctx->webview, &settings); - ICoreWebView2Settings_put_AreDevToolsEnabled(settings, ctx->debug); - ICoreWebView2Settings_put_AreDefaultContextMenusEnabled(settings, ctx->debug); + ICoreWebView2Settings_put_AreDevToolsEnabled(settings, ctx->base.debug); + ICoreWebView2Settings_put_AreDefaultContextMenusEnabled(settings, ctx->base.debug); ICoreWebView2Settings_put_IsZoomControlEnabled(settings, FALSE); ICoreWebView2Settings_Release(settings); @@ -291,53 +231,10 @@ static HRESULT STDMETHODCALLTYPE h1_Invoke(ICoreWebView2CreateCoreWebView2Contro ICoreWebView2_add_WebMessageReceived(ctx->webview, (ICoreWebView2WebMessageReceivedEventHandler *) &ctx->handler2, &token); - const WCHAR *script = - L"const __MTY_MSGS = [];" - - L"window.chrome.webview.addEventListener('message', evt => {" - L"if (window.MTY_NativeListener) {" - L"window.MTY_NativeListener(evt.data);" - - L"} else {" - L"__MTY_MSGS.push(evt.data);" - L"}" - L"});" - - L"window.MTY_NativeSendText = text => {" - L"window.chrome.webview.postMessage('T' + text);" - L"};" - - L"window.chrome.webview.postMessage('R');" - - L"const __MTY_INTERVAL = setInterval(() => {" - L"if (window.MTY_NativeListener) {" - L"for (let msg = __MTY_MSGS.shift(); msg; msg = __MTY_MSGS.shift())" - L"window.MTY_NativeListener(msg);" - - L"clearInterval(__MTY_INTERVAL);" - L"}" - L"}, 100);" - - L"function __mty_key_to_json(evt) {" - L"let mods = 0;" - - L"if (evt.shiftKey) mods |= 0x01;" - L"if (evt.ctrlKey) mods |= 0x02;" - L"if (evt.altKey) mods |= 0x04;" - L"if (evt.metaKey) mods |= 0x08;" - - L"if (evt.getModifierState('CapsLock')) mods |= 0x10;" - L"if (evt.getModifierState('NumLock')) mods |= 0x20;" - - L"let cmd = evt.type == 'keydown' ? 'D' : 'U';" - L"let json = JSON.stringify({'code':evt.code,'mods':mods});" - - L"window.chrome.webview.postMessage(cmd + json);" - L"}" - - L"document.addEventListener('keydown', __mty_key_to_json);" - L"document.addEventListener('keyup', __mty_key_to_json);"; - + const WCHAR *script = L"window.native = {" + L"postMessage: (message) => window.chrome.webview.postMessage(message)," + L"addEventListener: (listener) => window.chrome.webview.addEventListener('message', listener)," + L"};"; ICoreWebView2_AddScriptToExecuteOnDocumentCreated(ctx->webview, script, NULL); if (ctx->source) @@ -366,7 +263,7 @@ static HRESULT STDMETHODCALLTYPE h0_Invoke(ICoreWebView2CreateCoreWebView2Enviro struct webview_handler0 *handler = (struct webview_handler0 *) This; struct webview *ctx = handler->opaque; - HWND hwnd = MTY_WindowGetNative(ctx->app, ctx->window); + HWND hwnd = MTY_WindowGetNative(ctx->base.app, ctx->base.window); return ICoreWebView2Environment_CreateCoreWebView2Controller(env, hwnd, (ICoreWebView2CreateCoreWebView2ControllerCompletedHandler *) &ctx->handler1); @@ -603,15 +500,7 @@ struct webview *mty_webview_create(MTY_App *app, MTY_Window window, const char * { struct webview *ctx = MTY_Alloc(1, sizeof(struct webview)); - ctx->app = app; - ctx->window = window; - ctx->debug = debug; - ctx->ready_func = ready_func; - ctx->text_func = text_func; - ctx->key_func = key_func; - - ctx->keys = web_keymap_hash(); - ctx->pushq = MTY_QueueCreate(50, 0); + mty_webview_base_create(&ctx->base, app, window, dir, debug, ready_func, text_func, key_func); ctx->handler0.handler.lpVtbl = &VTBL0; ctx->handler0.opaque = ctx; @@ -661,11 +550,7 @@ void mty_webview_destroy(struct webview **webview) if (ctx->lib) FreeLibrary(ctx->lib); - if (ctx->pushq) - MTY_QueueFlush(ctx->pushq, MTY_Free); - - MTY_QueueDestroy(&ctx->pushq); - MTY_HashDestroy(&ctx->keys, NULL); + mty_webview_base_destroy(&ctx->base); MTY_Free(ctx->source); @@ -712,8 +597,8 @@ bool mty_webview_is_visible(struct webview *ctx) void mty_webview_send_text(struct webview *ctx, const char *msg) { - if (!ctx->ready) { - MTY_QueuePushPtr(ctx->pushq, MTY_MultiToWideD(msg), 0); + if (!ctx->base.ready) { + MTY_QueuePushPtr(ctx->base.pushq, MTY_Strdup(msg), 0); } else { WCHAR *wmsg = MTY_MultiToWideD(msg); @@ -732,7 +617,7 @@ void mty_webview_reload(struct webview *ctx) void mty_webview_set_input_passthrough(struct webview *ctx, bool passthrough) { - ctx->passthrough = passthrough; + ctx->base.passthrough = passthrough; } bool mty_webview_event(struct webview *ctx, MTY_Event *evt) @@ -753,7 +638,7 @@ void mty_webview_render(struct webview *ctx) bool mty_webview_is_focussed(struct webview *ctx) { - return ctx && ctx->focussed; + return ctx && ctx->base.focussed; } bool mty_webview_is_steam(void)