tauri-gtk.c 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. #include "tauri-gtk-webview.h"
  2. #include "tauri.h"
  3. static void external_message_received_cb(WebKitUserContentManager *m,
  4. WebKitJavascriptResult *r,
  5. gpointer arg) {
  6. (void)m;
  7. struct webview *w = (struct webview *)arg;
  8. if (w->external_invoke_cb == NULL) {
  9. return;
  10. }
  11. JSGlobalContextRef context = webkit_javascript_result_get_global_context(r);
  12. JSValueRef value = webkit_javascript_result_get_value(r);
  13. JSStringRef js = JSValueToStringCopy(context, value, NULL);
  14. size_t n = JSStringGetMaximumUTF8CStringSize(js);
  15. char *s = g_new(char, n);
  16. JSStringGetUTF8CString(js, s, n);
  17. w->external_invoke_cb(w, s);
  18. JSStringRelease(js);
  19. g_free(s);
  20. }
  21. static void webview_load_changed_cb(WebKitWebView *webview,
  22. WebKitLoadEvent event, gpointer arg) {
  23. (void)webview;
  24. struct webview *w = (struct webview *)arg;
  25. if (event == WEBKIT_LOAD_FINISHED) {
  26. w->priv.ready = 1;
  27. }
  28. }
  29. static void webview_destroy_cb(GtkWidget *widget, gpointer arg) {
  30. (void)widget;
  31. struct webview *w = (struct webview *)arg;
  32. webview_terminate(w);
  33. }
  34. static gboolean webview_context_menu_cb(WebKitWebView *webview,
  35. GtkWidget *default_menu,
  36. WebKitHitTestResult *hit_test_result,
  37. gboolean triggered_with_keyboard,
  38. gpointer userdata) {
  39. (void)webview;
  40. (void)default_menu;
  41. (void)hit_test_result;
  42. (void)triggered_with_keyboard;
  43. (void)userdata;
  44. return TRUE;
  45. }
  46. WEBVIEW_API int webview_init(struct webview *w) {
  47. if (gtk_init_check(0, NULL) == FALSE) {
  48. return -1;
  49. }
  50. w->priv.ready = 0;
  51. w->priv.should_exit = 0;
  52. w->priv.queue = g_async_queue_new();
  53. w->priv.window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  54. gtk_window_set_title(GTK_WINDOW(w->priv.window), w->title);
  55. if (w->resizable) {
  56. gtk_window_set_default_size(GTK_WINDOW(w->priv.window), w->width,
  57. w->height);
  58. } else {
  59. gtk_widget_set_size_request(w->priv.window, w->width, w->height);
  60. }
  61. gtk_window_set_resizable(GTK_WINDOW(w->priv.window), !!w->resizable);
  62. gtk_window_set_position(GTK_WINDOW(w->priv.window), GTK_WIN_POS_CENTER);
  63. w->priv.scroller = gtk_scrolled_window_new(NULL, NULL);
  64. gtk_container_add(GTK_CONTAINER(w->priv.window), w->priv.scroller);
  65. WebKitUserContentManager *m = webkit_user_content_manager_new();
  66. webkit_user_content_manager_register_script_message_handler(m, "external");
  67. g_signal_connect(m, "script-message-received::external",
  68. G_CALLBACK(external_message_received_cb), w);
  69. w->priv.webview = webkit_web_view_new_with_user_content_manager(m);
  70. webkit_web_view_load_uri(WEBKIT_WEB_VIEW(w->priv.webview),
  71. webview_check_url(w->url));
  72. g_signal_connect(G_OBJECT(w->priv.webview), "load-changed",
  73. G_CALLBACK(webview_load_changed_cb), w);
  74. gtk_container_add(GTK_CONTAINER(w->priv.scroller), w->priv.webview);
  75. WebKitSettings *settings =
  76. webkit_web_view_get_settings(WEBKIT_WEB_VIEW(w->priv.webview));
  77. // Enable webgl and canvas features.
  78. webkit_settings_set_enable_webgl(settings, true);
  79. webkit_settings_set_enable_accelerated_2d_canvas(settings, true);
  80. if (w->debug) {
  81. WebKitSettings *settings =
  82. webkit_web_view_get_settings(WEBKIT_WEB_VIEW(w->priv.webview));
  83. webkit_settings_set_enable_write_console_messages_to_stdout(settings, true);
  84. webkit_settings_set_enable_developer_extras(settings, true);
  85. } else {
  86. g_signal_connect(G_OBJECT(w->priv.webview), "context-menu",
  87. G_CALLBACK(webview_context_menu_cb), w);
  88. }
  89. gtk_widget_show_all(w->priv.window);
  90. webkit_web_view_run_javascript(
  91. WEBKIT_WEB_VIEW(w->priv.webview),
  92. "window.external={invoke:function(x){"
  93. "window.webkit.messageHandlers.external.postMessage(x);}}",
  94. NULL, NULL, NULL);
  95. g_signal_connect(G_OBJECT(w->priv.window), "destroy",
  96. G_CALLBACK(webview_destroy_cb), w);
  97. return 0;
  98. }
  99. WEBVIEW_API int webview_loop(struct webview *w, int blocking) {
  100. gtk_main_iteration_do(blocking);
  101. return w->priv.should_exit;
  102. }
  103. WEBVIEW_API void webview_set_title(struct webview *w, const char *title) {
  104. gtk_window_set_title(GTK_WINDOW(w->priv.window), title);
  105. }
  106. WEBVIEW_API void webview_set_fullscreen(struct webview *w, int fullscreen) {
  107. if (fullscreen) {
  108. gtk_window_fullscreen(GTK_WINDOW(w->priv.window));
  109. } else {
  110. gtk_window_unfullscreen(GTK_WINDOW(w->priv.window));
  111. }
  112. }
  113. WEBVIEW_API void webview_set_color(struct webview *w, uint8_t r, uint8_t g,
  114. uint8_t b, uint8_t a) {
  115. GdkRGBA color = {r / 255.0, g / 255.0, b / 255.0, a / 255.0};
  116. webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(w->priv.webview),
  117. &color);
  118. }
  119. WEBVIEW_API void webview_dialog(struct webview *w,
  120. enum webview_dialog_type dlgtype, int flags,
  121. const char *title, const char *arg,
  122. char *result, size_t resultsz) {
  123. GtkWidget *dlg;
  124. if (result != NULL) {
  125. result[0] = '\0';
  126. }
  127. if (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ||
  128. dlgtype == WEBVIEW_DIALOG_TYPE_SAVE) {
  129. dlg = gtk_file_chooser_dialog_new(
  130. title, GTK_WINDOW(w->priv.window),
  131. (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN
  132. ? (flags & WEBVIEW_DIALOG_FLAG_DIRECTORY
  133. ? GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER
  134. : GTK_FILE_CHOOSER_ACTION_OPEN)
  135. : GTK_FILE_CHOOSER_ACTION_SAVE),
  136. "_Cancel", GTK_RESPONSE_CANCEL,
  137. (dlgtype == WEBVIEW_DIALOG_TYPE_OPEN ? "_Open" : "_Save"),
  138. GTK_RESPONSE_ACCEPT, NULL);
  139. gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(dlg), FALSE);
  140. gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dlg), FALSE);
  141. gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dlg), TRUE);
  142. gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dlg), TRUE);
  143. gtk_file_chooser_set_create_folders(GTK_FILE_CHOOSER(dlg), TRUE);
  144. gint response = gtk_dialog_run(GTK_DIALOG(dlg));
  145. if (response == GTK_RESPONSE_ACCEPT) {
  146. gchar *filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg));
  147. g_strlcpy(result, filename, resultsz);
  148. g_free(filename);
  149. }
  150. gtk_widget_destroy(dlg);
  151. } else if (dlgtype == WEBVIEW_DIALOG_TYPE_ALERT) {
  152. GtkMessageType type = GTK_MESSAGE_OTHER;
  153. switch (flags & WEBVIEW_DIALOG_FLAG_ALERT_MASK) {
  154. case WEBVIEW_DIALOG_FLAG_INFO:
  155. type = GTK_MESSAGE_INFO;
  156. break;
  157. case WEBVIEW_DIALOG_FLAG_WARNING:
  158. type = GTK_MESSAGE_WARNING;
  159. break;
  160. case WEBVIEW_DIALOG_FLAG_ERROR:
  161. type = GTK_MESSAGE_ERROR;
  162. break;
  163. }
  164. dlg = gtk_message_dialog_new(GTK_WINDOW(w->priv.window), GTK_DIALOG_MODAL,
  165. type, GTK_BUTTONS_OK, "%s", title);
  166. gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dlg), "%s",
  167. arg);
  168. gtk_dialog_run(GTK_DIALOG(dlg));
  169. gtk_widget_destroy(dlg);
  170. }
  171. }
  172. static void webview_eval_finished(GObject *object, GAsyncResult *result,
  173. gpointer userdata) {
  174. (void)object;
  175. (void)result;
  176. struct webview *w = (struct webview *)userdata;
  177. w->priv.js_busy = 0;
  178. }
  179. WEBVIEW_API int webview_eval(struct webview *w, const char *js) {
  180. while (w->priv.ready == 0) {
  181. g_main_context_iteration(NULL, TRUE);
  182. }
  183. w->priv.js_busy = 1;
  184. webkit_web_view_run_javascript(WEBKIT_WEB_VIEW(w->priv.webview), js, NULL,
  185. webview_eval_finished, w);
  186. while (w->priv.js_busy) {
  187. g_main_context_iteration(NULL, TRUE);
  188. }
  189. return 0;
  190. }
  191. static gboolean webview_dispatch_wrapper(gpointer userdata) {
  192. struct webview *w = (struct webview *)userdata;
  193. for (;;) {
  194. struct webview_dispatch_arg *arg =
  195. (struct webview_dispatch_arg *)g_async_queue_try_pop(w->priv.queue);
  196. if (arg == NULL) {
  197. break;
  198. }
  199. (arg->fn)(w, arg->arg);
  200. g_free(arg);
  201. }
  202. return FALSE;
  203. }
  204. WEBVIEW_API void webview_dispatch(struct webview *w, webview_dispatch_fn fn,
  205. void *arg) {
  206. struct webview_dispatch_arg *context =
  207. (struct webview_dispatch_arg *)g_new(struct webview_dispatch_arg, 1);
  208. context->w = w;
  209. context->arg = arg;
  210. context->fn = fn;
  211. g_async_queue_lock(w->priv.queue);
  212. g_async_queue_push_unlocked(w->priv.queue, context);
  213. if (g_async_queue_length_unlocked(w->priv.queue) == 1) {
  214. gdk_threads_add_idle(webview_dispatch_wrapper, w);
  215. }
  216. g_async_queue_unlock(w->priv.queue);
  217. }
  218. WEBVIEW_API void webview_terminate(struct webview *w) {
  219. w->priv.should_exit = 1;
  220. }
  221. WEBVIEW_API void webview_exit(struct webview *w) { (void)w; }
  222. WEBVIEW_API void webview_print_log(const char *s) {
  223. fprintf(stderr, "%s\n", s);
  224. }