前置条件
- Ubuntu 24.04.2 LTS
内容详情
在使用Gtk开发应用程序的过程中,如果需要内嵌网页,那么使用libwebkit2gtk是个非常自然和正确的选择。
那么这里就可能原生程序代码可能需要跟网页交互的问题。
Gtk程序跟网页的交互,主要有两个方面:
1 2 3 |
1 Gtk程序需要调用网页js代码 2 网页需要调用 Gtk 程序的功能代码 |
需求1,使用 webkit2gtk 的内置 webkit_web_view_run_javascript 函数即可解决
需求2,使用 webkit2gtk 的内置的 web extendsion 扩展支持功能解决 或 window.webkit.messageHandlers..postMessage(value)
不多说看代码吧!
Gtk嵌入网页Demo程序
webviewgtk.c
webviewgtk.c
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
/** * * Copyright (C) 2020 Wei Keting<weikting@gmail.com>. All rights reserved. * @Time : 2021-04-04 12:18 * @Last Modified: 2022-12-10 17:27 * @File : webviewgtk.c * @Description : * * 依赖下载: * sudo apt install libwebkit2gtk-4.1-doc libwebkit2gtk-4.1-dev libgtk-3-dev * gcc webviewgtk.c -o webviewgtk -D_GNU_SOURCE -g3 -Wall `pkg-config --cflags --libs webkit2gtk-4.1` * */ #include <gtk/gtk.h> #include <glib.h> #include <webkit2/webkit2.h> #include <sys/types.h> #include <unistd.h> static void web_view_javascript_finished(GObject *object, GAsyncResult *result, gpointer user_data) { GError *error = NULL; JSCValue *js_result = webkit_web_view_evaluate_javascript_finish(WEBKIT_WEB_VIEW(object), result, &error); if (!js_result) { g_warning("Error running javascript: %s", error->message); g_error_free(error); return; } if (jsc_value_is_string(js_result)) { gchar *str_value = jsc_value_to_string(js_result); JSCException *exception = jsc_context_get_exception(jsc_value_get_context(js_result)); if (exception) g_warning("Error running javascript: %s", jsc_exception_get_message(exception)); else g_print("Script result: %s\n", str_value); g_free(str_value); } else { g_warning("Error running javascript: unexpected return value"); } } static gboolean on_webview_load_failed(WebKitWebView *webview, WebKitLoadEvent load_event, gchar *failing_uri, GError *error, gpointer user_data) { g_printerr("%s: %s\n", failing_uri, error->message); return FALSE; } static void handle_script_message(WebKitUserContentManager *self, WebKitJavascriptResult *js_result, gpointer user_data) { JSCValue *value = webkit_javascript_result_get_js_value(js_result); gchar *str_value = jsc_value_to_string(value); JSCException *exception = jsc_context_get_exception(jsc_value_get_context(value)); if (exception) g_warning("Error running javascript: %s", jsc_exception_get_message(exception)); else g_printerr("Script result: %s\n", str_value); g_printerr("%s: %s\n", __func__, str_value); g_free(str_value); } static gboolean handle_script_message_with_reply(WebKitUserContentManager *self, JSCValue *value, WebKitScriptMessageReply *reply, gpointer user_data) { gchar *str_value = jsc_value_to_string(value); JSCContext *context = jsc_value_get_context(value); /* It is possible to handle the reply asynchronously, * by simply calling g_object_ref() on the reply and returning TRUE. * webkit_script_message_reply_ref(reply); * * async code here * * webkit_script_message_reply_unref(reply); */ JSCValue *js_value = jsc_value_new_string(context, str_value); webkit_script_message_reply_return_value(reply, js_value); g_printerr("%s: %s\n", __func__, str_value); g_free(str_value); return TRUE; // TRUE to stop other handlers from being invoked for the event. FALSE to propagate the event further. } static void on_button_clicked(GtkButton *button, WebKitWebView *webview) { static gint t = 0; gchar buf[128] = {0}; g_snprintf(buf, sizeof(buf) - 1, "change_span_id('_n%d')", t); t += 1; // 在webview当前的html页面中直接运行js代码 webkit_web_view_evaluate_javascript(webview, buf, -1, NULL, NULL, NULL, web_view_javascript_finished, NULL); } static void webkit_web_extension_initialize(WebKitWebContext *context, gpointer user_data) { g_printerr("%s: %d\n", __FUNCTION__, getpid()); // 设置web extendsion扩张.so文件的搜索目录 webkit_web_context_set_web_extensions_directory(context, "."); } /** ** 创建window,添加webkit控件 ** **/ static void on_activate(GtkApplication *app) { g_assert(GTK_IS_APPLICATION(app)); GtkWindow *window = gtk_application_get_active_window(app); if (window == NULL) window = g_object_new(GTK_TYPE_WINDOW, "application", app, "default-width", 600, "default-height", 300, NULL); // 注册处理web extensions的初始化函数 g_signal_connect(webkit_web_context_get_default(), "initialize-web-extensions", G_CALLBACK(webkit_web_extension_initialize), NULL); GtkWidget *webview = webkit_web_view_new(); g_signal_connect(webview, "load-failed", G_CALLBACK(on_webview_load_failed), NULL); // 加载网页 GFile *file = g_file_new_for_path("webview.html"); gchar *uri = g_file_get_uri(file); webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), uri); g_free(uri); g_object_unref(file); // 注册 window.webkit.messageHandlers.msgToNative.postMessage(value) 的回调函数 WebKitUserContentManager *manager = webkit_web_view_get_user_content_manager( WEBKIT_WEB_VIEW(webview)); g_signal_connect(manager, "script-message-received::msgToNative", G_CALLBACK(handle_script_message), NULL); webkit_user_content_manager_register_script_message_handler(manager, "msgToNative"); // 注册 window.webkit.messageHandlers.msgToNativeReply.postMessage(value) 的回调函数 // 函数调用有返回值 g_signal_connect(manager, "script-message-with-reply-received::msgToNativeReply", G_CALLBACK(handle_script_message_with_reply), NULL); webkit_user_content_manager_register_script_message_handler_with_reply(manager, "msgToNativeReply", NULL); /* Enable the developer extras */ WebKitSettings *setting = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview)); g_object_set(G_OBJECT(settings), "enable-developer-extras", TRUE, NULL); GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); GtkWidget *button = gtk_button_new_with_label("change span"); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(webview), TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(button), FALSE, TRUE, 0); gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(vbox)); gtk_widget_show_all(GTK_WIDGET(vbox)); gtk_window_present(window); g_signal_connect(button, "clicked", G_CALLBACK(on_button_clicked), webview); } int main(int argc, char *argv[]) { g_autoptr(GtkApplication) app = gtk_application_new("com.weiketing.webkit_webview", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL); return g_application_run(G_APPLICATION(app), argc, argv); } |
Web Extension Demo
web_exten.c
C
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
/** * * Copyright (C) 2020 Wei Keting<weikting@gmail.com>. All rights reserved. * @Time : 2021-04-04 12:18 * @File : web_exten.c * @Description : * * 依赖下载: * sudo apt install libwebkit2gtk-4.1-doc libwebkit2gtk-4.1-dev libgtk-3-dev * gcc web_exten.c -o libweb_exten.so -shared -Wl,-soname,libweb_exten.so -D_GNU_SOURCE -g3 -Wall `pkg-config --cflags --libs webkit2gtk-4.1` **/ #include <glib.h> #include <webkit2/webkit-web-extension.h> #include <sys/types.h> #include <unistd.h> static gint js_app_add(gpointer *first, gint num) { static gint N = 0; g_printerr("%s: %p\n", __FUNCTION__, first); N += num; return N; } static void window_object_cleared_callback(WebKitScriptWorld *world, WebKitWebPage *web_page, WebKitFrame *frame, gpointer user_data) { JSCContext *jsContext; jsContext = webkit_frame_get_js_context_for_script_world(frame, world); //添加一个js全局变量gtkValue jsc_context_set_value(jsContext, "gtkValue", jsc_value_new_string(jsContext, "__test_js_exten")); /* Use JSC API to add the JavaScript code you want */ //注册一个名为NativeTest的js类 JSCClass *app = jsc_context_register_class(jsContext, "NativeTest", NULL, NULL, NULL); // g_object_new(JSC_TYPE_CLASS, "name", "JSApp", "context", jsContext, NULL); //给JSCClass类添加add方法 jsc_class_add_method(app, "add", G_CALLBACK(js_app_add), NULL, NULL, G_TYPE_INT, 1, G_TYPE_INT, NULL); //创建一个obj,作为JSCClass类绑定实例,JSCClass方法回调的第一个参数就是obj GObject *obj = g_object_new(G_TYPE_OBJECT, NULL); jsc_context_set_value(jsContext, "GtkNative", jsc_value_new_object(jsContext, obj, app)); g_printerr("%s: %d %p\n", __FUNCTION__, getpid(), obj); g_object_unref(obj); } G_MODULE_EXPORT void webkit_web_extension_initialize(WebKitWebExtension *extension) { //web extension的初始化函数 g_signal_connect(webkit_script_world_get_default(), "window-object-cleared", G_CALLBACK(window_object_cleared_callback), NULL); } |
内嵌的网页示例 webview.html
webview.html
XHTML
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
<!DOCTYPE html> <html lang="zh"> <!--filename: webview.html --> <head> <meta charset="utf-8" /> <meta http-equiv="Content-Language" content="zh-CN"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> <meta name="referrer" content="always" /> <title>Webkit Webview test</title> <script type="text/javascript"> function change_span_id(v = '') { //alert("test") document.getElementById('span_id').innerHTML = 'test' + v + gtkValue return v } function native_add(num) { //调用自定义添加的js接口 i = GtkNative.add(num) document.getElementById('add').innerHTML = i } function send2Gtk() { e = document.getElementById('msg') window.webkit.messageHandlers.msgToNative.postMessage(e.value) } async function send2GtkReply() { e = document.getElementById('msgReply') // 调用自定义添加的js接口,并获取返回值 res = await window.webkit.messageHandlers.msgToNativeReply.postMessage(e.value) document.getElementById('span_id').innerHTML = res } </script> </head> <body> <span>words for test: </span><span id="span_id"></span> <br /> <button onclick="change_span_id()">change span id</button> <br /> <span>Native add: </span><span id='add'></span> <br /> <button onclick="native_add(2)">Native Add</button> <br /> <input type="text" id="msg" /> <br /> <button onclick="send2Gtk()">Send to Native</button> <br /> <input type="text" id="msgReply" /> <br /> <button onclick="send2GtkReply()">Send to Native Wait Reply</button> </body> </html> |
示例运行
安装依赖:
1 |
$ sudo apt install libwebkit2gtk-4.1-doc libwebkit2gtk-4.1-dev libgtk-3-dev |
把webviewgtk.c,web_exten.c,webview.html 放在同一目录下。
编译程序:
1 2 3 |
$ gcc web_exten.c -o libweb_exten.so -shared -Wl,-soname,libweb_exten.so -D_GNU_SOURCE -g3 -Wall `pkg-config --cflags --libs webkit2gtk-4.1` $ gcc webviewgtk.c -o webviewgtk -D_GNU_SOURCE -g3 -Wall `pkg-config --cflags --libs webkit2gtk-4.1` |
运行程序:
1 |
$ ./webviewgtk |
参考链接
- Gtk应用内嵌网页与原生代码交互方法
- 在WebKitGTK 中扩展JS
- WebKit2 > UserContentManager > register_script_message_handler_with_reply
- Integrating WPE: URI Scheme Handlers and Script Messages
- How does the iOS 14 API WKScriptMessageHandlerWithReply work for communicating with JavaScript from iOS?
- webkit2gtk的自定义协议与内容交互
- WebKitWebInspector