github.com/secoba/wails/v2@v2.6.4/internal/frontend/desktop/linux/window.c (about) 1 #include <JavaScriptCore/JavaScript.h> 2 #include <gtk/gtk.h> 3 #include <webkit2/webkit2.h> 4 #include <stdio.h> 5 #include <limits.h> 6 #include <stdint.h> 7 #include <locale.h> 8 #include "window.h" 9 10 // These are the x,y,time & button of the last mouse down event 11 // It's used for window dragging 12 static float xroot = 0.0f; 13 static float yroot = 0.0f; 14 static int dragTime = -1; 15 static uint mouseButton = 0; 16 17 // casts 18 void ExecuteOnMainThread(void *f, gpointer jscallback) 19 { 20 g_idle_add((GSourceFunc)f, (gpointer)jscallback); 21 } 22 23 GtkWidget *GTKWIDGET(void *pointer) 24 { 25 return GTK_WIDGET(pointer); 26 } 27 28 GtkWindow *GTKWINDOW(void *pointer) 29 { 30 return GTK_WINDOW(pointer); 31 } 32 33 GtkContainer *GTKCONTAINER(void *pointer) 34 { 35 return GTK_CONTAINER(pointer); 36 } 37 38 GtkBox *GTKBOX(void *pointer) 39 { 40 return GTK_BOX(pointer); 41 } 42 43 extern void processMessage(char *); 44 45 static void sendMessageToBackend(WebKitUserContentManager *contentManager, 46 WebKitJavascriptResult *result, 47 void *data) 48 { 49 #if WEBKIT_MAJOR_VERSION >= 2 && WEBKIT_MINOR_VERSION >= 22 50 JSCValue *value = webkit_javascript_result_get_js_value(result); 51 char *message = jsc_value_to_string(value); 52 #else 53 JSGlobalContextRef context = webkit_javascript_result_get_global_context(result); 54 JSValueRef value = webkit_javascript_result_get_value(result); 55 JSStringRef js = JSValueToStringCopy(context, value, NULL); 56 size_t messageSize = JSStringGetMaximumUTF8CStringSize(js); 57 char *message = g_new(char, messageSize); 58 JSStringGetUTF8CString(js, message, messageSize); 59 JSStringRelease(js); 60 #endif 61 processMessage(message); 62 g_free(message); 63 } 64 65 static bool isNULLRectangle(GdkRectangle input) 66 { 67 return input.x == -1 && input.y == -1 && input.width == -1 && input.height == -1; 68 } 69 70 static GdkMonitor *getCurrentMonitor(GtkWindow *window) 71 { 72 // Get the monitor that the window is currently on 73 GdkDisplay *display = gtk_widget_get_display(GTK_WIDGET(window)); 74 GdkWindow *gdk_window = gtk_widget_get_window(GTK_WIDGET(window)); 75 if (gdk_window == NULL) 76 { 77 return NULL; 78 } 79 GdkMonitor *monitor = gdk_display_get_monitor_at_window(display, gdk_window); 80 81 return GDK_MONITOR(monitor); 82 } 83 84 static GdkRectangle getCurrentMonitorGeometry(GtkWindow *window) 85 { 86 GdkMonitor *monitor = getCurrentMonitor(window); 87 GdkRectangle result; 88 if (monitor == NULL) 89 { 90 result.x = result.y = result.height = result.width = -1; 91 return result; 92 } 93 94 // Get the geometry of the monitor 95 gdk_monitor_get_geometry(monitor, &result); 96 return result; 97 } 98 99 static int getCurrentMonitorScaleFactor(GtkWindow *window) 100 { 101 GdkMonitor *monitor = getCurrentMonitor(window); 102 103 return gdk_monitor_get_scale_factor(monitor); 104 } 105 106 // window 107 108 ulong SetupInvokeSignal(void *contentManager) 109 { 110 return g_signal_connect((WebKitUserContentManager *)contentManager, "script-message-received::external", G_CALLBACK(sendMessageToBackend), NULL); 111 } 112 113 void SetWindowIcon(GtkWindow *window, const guchar *buf, gsize len) 114 { 115 GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); 116 if (!loader) 117 { 118 return; 119 } 120 if (gdk_pixbuf_loader_write(loader, buf, len, NULL) && gdk_pixbuf_loader_close(loader, NULL)) 121 { 122 GdkPixbuf *pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); 123 if (pixbuf) 124 { 125 gtk_window_set_icon(window, pixbuf); 126 } 127 } 128 g_object_unref(loader); 129 } 130 131 void SetWindowTransparency(GtkWidget *widget) 132 { 133 GdkScreen *screen = gtk_widget_get_screen(widget); 134 GdkVisual *visual = gdk_screen_get_rgba_visual(screen); 135 136 if (visual != NULL && gdk_screen_is_composited(screen)) 137 { 138 gtk_widget_set_app_paintable(widget, true); 139 gtk_widget_set_visual(widget, visual); 140 } 141 } 142 143 static GtkCssProvider *windowCssProvider = NULL; 144 145 void SetBackgroundColour(void *data) 146 { 147 // set webview's background color 148 RGBAOptions *options = (RGBAOptions *)data; 149 150 GdkRGBA colour = {options->r / 255.0, options->g / 255.0, options->b / 255.0, options->a / 255.0}; 151 if (options->windowIsTranslucent != NULL && options->windowIsTranslucent == TRUE) 152 { 153 colour.alpha = 0.0; 154 } 155 webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(options->webview), &colour); 156 157 // set window's background color 158 // Get the name of the current locale 159 char *old_locale, *saved_locale; 160 old_locale = setlocale(LC_ALL, NULL); 161 162 // Copy the name so it won’t be clobbered by setlocale. 163 saved_locale = strdup(old_locale); 164 if (saved_locale == NULL) 165 return; 166 167 //Now change the locale to english for so printf always converts floats with a dot decimal separator 168 setlocale(LC_ALL, "en_US.UTF-8"); 169 gchar *str = g_strdup_printf("#webview-box {background-color: rgba(%d, %d, %d, %1.1f);}", options->r, options->g, options->b, options->a / 255.0); 170 171 //Restore the original locale. 172 setlocale(LC_ALL, saved_locale); 173 free(saved_locale); 174 175 if (windowCssProvider == NULL) 176 { 177 windowCssProvider = gtk_css_provider_new(); 178 gtk_style_context_add_provider( 179 gtk_widget_get_style_context(GTK_WIDGET(options->webviewBox)), 180 GTK_STYLE_PROVIDER(windowCssProvider), 181 GTK_STYLE_PROVIDER_PRIORITY_USER); 182 g_object_unref(windowCssProvider); 183 } 184 185 gtk_css_provider_load_from_data(windowCssProvider, str, -1, NULL); 186 g_free(str); 187 } 188 189 static gboolean setTitle(gpointer data) 190 { 191 SetTitleArgs *args = (SetTitleArgs *)data; 192 gtk_window_set_title(args->window, args->title); 193 free((void *)args->title); 194 free((void *)data); 195 196 return G_SOURCE_REMOVE; 197 } 198 199 void SetTitle(GtkWindow *window, char *title) 200 { 201 SetTitleArgs *args = malloc(sizeof(SetTitleArgs)); 202 args->window = window; 203 args->title = title; 204 ExecuteOnMainThread(setTitle, (gpointer)args); 205 } 206 207 static gboolean setPosition(gpointer data) 208 { 209 SetPositionArgs *args = (SetPositionArgs *)data; 210 gtk_window_move((GtkWindow *)args->window, args->x, args->y); 211 free(args); 212 213 return G_SOURCE_REMOVE; 214 } 215 216 void SetPosition(void *window, int x, int y) 217 { 218 GdkRectangle monitorDimensions = getCurrentMonitorGeometry(window); 219 if (isNULLRectangle(monitorDimensions)) 220 { 221 return; 222 } 223 SetPositionArgs *args = malloc(sizeof(SetPositionArgs)); 224 args->window = window; 225 args->x = monitorDimensions.x + x; 226 args->y = monitorDimensions.y + y; 227 ExecuteOnMainThread(setPosition, (gpointer)args); 228 } 229 230 void SetMinMaxSize(GtkWindow *window, int min_width, int min_height, int max_width, int max_height) 231 { 232 GdkGeometry size; 233 size.min_width = size.min_height = size.max_width = size.max_height = 0; 234 235 GdkRectangle monitorSize = getCurrentMonitorGeometry(window); 236 if (isNULLRectangle(monitorSize)) 237 { 238 return; 239 } 240 int flags = GDK_HINT_MAX_SIZE | GDK_HINT_MIN_SIZE; 241 size.max_height = (max_height == 0 ? monitorSize.height : max_height); 242 size.max_width = (max_width == 0 ? monitorSize.width : max_width); 243 size.min_height = min_height; 244 size.min_width = min_width; 245 gtk_window_set_geometry_hints(window, NULL, &size, flags); 246 } 247 248 // function to disable the context menu but propogate the event 249 static gboolean disableContextMenu(GtkWidget *widget, WebKitContextMenu *context_menu, GdkEvent *event, WebKitHitTestResult *hit_test_result, gpointer data) 250 { 251 // return true to disable the context menu 252 return TRUE; 253 } 254 255 void DisableContextMenu(void *webview) 256 { 257 // Disable the context menu but propogate the event 258 g_signal_connect(WEBKIT_WEB_VIEW(webview), "context-menu", G_CALLBACK(disableContextMenu), NULL); 259 } 260 261 static gboolean buttonPress(GtkWidget *widget, GdkEventButton *event, void *dummy) 262 { 263 if (event == NULL) 264 { 265 xroot = yroot = 0.0f; 266 dragTime = -1; 267 return FALSE; 268 } 269 mouseButton = event->button; 270 if (event->button == 3) 271 { 272 return FALSE; 273 } 274 275 if (event->type == GDK_BUTTON_PRESS && event->button == 1) 276 { 277 xroot = event->x_root; 278 yroot = event->y_root; 279 dragTime = event->time; 280 } 281 282 return FALSE; 283 } 284 285 static gboolean buttonRelease(GtkWidget *widget, GdkEventButton *event, void *dummy) 286 { 287 if (event == NULL || (event->type == GDK_BUTTON_RELEASE && event->button == 1)) 288 { 289 xroot = yroot = 0.0f; 290 dragTime = -1; 291 } 292 return FALSE; 293 } 294 295 void ConnectButtons(void *webview) 296 { 297 g_signal_connect(WEBKIT_WEB_VIEW(webview), "button-press-event", G_CALLBACK(buttonPress), NULL); 298 g_signal_connect(WEBKIT_WEB_VIEW(webview), "button-release-event", G_CALLBACK(buttonRelease), NULL); 299 } 300 301 int IsFullscreen(GtkWidget *widget) 302 { 303 GdkWindow *gdkwindow = gtk_widget_get_window(widget); 304 GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow)); 305 return state & GDK_WINDOW_STATE_FULLSCREEN; 306 } 307 308 int IsMaximised(GtkWidget *widget) 309 { 310 GdkWindow *gdkwindow = gtk_widget_get_window(widget); 311 GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow)); 312 return state & GDK_WINDOW_STATE_MAXIMIZED && !(state & GDK_WINDOW_STATE_FULLSCREEN); 313 } 314 315 int IsMinimised(GtkWidget *widget) 316 { 317 GdkWindow *gdkwindow = gtk_widget_get_window(widget); 318 GdkWindowState state = gdk_window_get_state(GDK_WINDOW(gdkwindow)); 319 return state & GDK_WINDOW_STATE_ICONIFIED; 320 } 321 322 gboolean Center(gpointer data) 323 { 324 GtkWindow *window = (GtkWindow *)data; 325 326 // Get the geometry of the monitor 327 GdkRectangle m = getCurrentMonitorGeometry(window); 328 if (isNULLRectangle(m)) 329 { 330 return G_SOURCE_REMOVE; 331 } 332 333 // Get the window width/height 334 int windowWidth, windowHeight; 335 gtk_window_get_size(window, &windowWidth, &windowHeight); 336 337 int newX = ((m.width - windowWidth) / 2) + m.x; 338 int newY = ((m.height - windowHeight) / 2) + m.y; 339 340 // Place the window at the center of the monitor 341 gtk_window_move(window, newX, newY); 342 343 return G_SOURCE_REMOVE; 344 } 345 346 gboolean Show(gpointer data) 347 { 348 gtk_widget_show((GtkWidget *)data); 349 350 return G_SOURCE_REMOVE; 351 } 352 353 gboolean Hide(gpointer data) 354 { 355 gtk_widget_hide((GtkWidget *)data); 356 357 return G_SOURCE_REMOVE; 358 } 359 360 gboolean Maximise(gpointer data) 361 { 362 gtk_window_maximize((GtkWindow *)data); 363 364 return G_SOURCE_REMOVE; 365 } 366 367 gboolean UnMaximise(gpointer data) 368 { 369 gtk_window_unmaximize((GtkWindow *)data); 370 371 return G_SOURCE_REMOVE; 372 } 373 374 gboolean Minimise(gpointer data) 375 { 376 gtk_window_iconify((GtkWindow *)data); 377 378 return G_SOURCE_REMOVE; 379 } 380 381 gboolean UnMinimise(gpointer data) 382 { 383 gtk_window_present((GtkWindow *)data); 384 385 return G_SOURCE_REMOVE; 386 } 387 388 gboolean Fullscreen(gpointer data) 389 { 390 GtkWindow *window = (GtkWindow *)data; 391 392 // Get the geometry of the monitor. 393 GdkRectangle m = getCurrentMonitorGeometry(window); 394 if (isNULLRectangle(m)) 395 { 396 return G_SOURCE_REMOVE; 397 } 398 int scale = getCurrentMonitorScaleFactor(window); 399 SetMinMaxSize(window, 0, 0, m.width * scale, m.height * scale); 400 401 gtk_window_fullscreen(window); 402 403 return G_SOURCE_REMOVE; 404 } 405 406 gboolean UnFullscreen(gpointer data) 407 { 408 gtk_window_unfullscreen((GtkWindow *)data); 409 410 return G_SOURCE_REMOVE; 411 } 412 413 static void webviewLoadChanged(WebKitWebView *web_view, WebKitLoadEvent load_event, gpointer data) 414 { 415 if (load_event == WEBKIT_LOAD_FINISHED) 416 { 417 processMessage("DomReady"); 418 } 419 } 420 421 extern void processURLRequest(void *request); 422 423 // This is called when the close button on the window is pressed 424 gboolean close_button_pressed(GtkWidget *widget, GdkEvent *event, void *data) 425 { 426 processMessage("Q"); 427 // since we handle the close in processMessage tell GTK to not invoke additional handlers - see: 428 // https://docs.gtk.org/gtk3/signal.Widget.delete-event.html 429 return TRUE; 430 } 431 432 // WebView 433 GtkWidget *SetupWebview(void *contentManager, GtkWindow *window, int hideWindowOnClose, int gpuPolicy) 434 { 435 GtkWidget *webview = webkit_web_view_new_with_user_content_manager((WebKitUserContentManager *)contentManager); 436 // gtk_container_add(GTK_CONTAINER(window), webview); 437 WebKitWebContext *context = webkit_web_context_get_default(); 438 webkit_web_context_register_uri_scheme(context, "wails", (WebKitURISchemeRequestCallback)processURLRequest, NULL, NULL); 439 g_signal_connect(G_OBJECT(webview), "load-changed", G_CALLBACK(webviewLoadChanged), NULL); 440 if (hideWindowOnClose) 441 { 442 g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL); 443 } 444 else 445 { 446 g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(close_button_pressed), NULL); 447 } 448 449 WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview)); 450 webkit_settings_set_user_agent_with_application_details(settings, "wails.io", ""); 451 452 switch (gpuPolicy) 453 { 454 case 0: 455 webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ALWAYS); 456 break; 457 case 1: 458 webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND); 459 break; 460 case 2: 461 webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER); 462 break; 463 default: 464 webkit_settings_set_hardware_acceleration_policy(settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_ON_DEMAND); 465 } 466 return webview; 467 } 468 469 void DevtoolsEnabled(void *webview, int enabled, bool showInspector) 470 { 471 WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview)); 472 gboolean genabled = enabled == 1 ? true : false; 473 webkit_settings_set_enable_developer_extras(settings, genabled); 474 475 if (genabled && showInspector) 476 { 477 ShowInspector(webview); 478 } 479 } 480 481 void LoadIndex(void *webview, char *url) 482 { 483 webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url); 484 } 485 486 static gboolean startDrag(gpointer data) 487 { 488 DragOptions *options = (DragOptions *)data; 489 490 // Ignore non-toplevel widgets 491 GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(options->webview)); 492 if (!GTK_IS_WINDOW(window)) 493 { 494 free(data); 495 return G_SOURCE_REMOVE; 496 } 497 498 gtk_window_begin_move_drag(options->mainwindow, mouseButton, xroot, yroot, dragTime); 499 free(data); 500 501 return G_SOURCE_REMOVE; 502 } 503 504 void StartDrag(void *webview, GtkWindow *mainwindow) 505 { 506 DragOptions *data = malloc(sizeof(DragOptions)); 507 data->webview = webview; 508 data->mainwindow = mainwindow; 509 ExecuteOnMainThread(startDrag, (gpointer)data); 510 } 511 512 static gboolean startResize(gpointer data) 513 { 514 ResizeOptions *options = (ResizeOptions *)data; 515 516 // Ignore non-toplevel widgets 517 GtkWidget *window = gtk_widget_get_toplevel(GTK_WIDGET(options->webview)); 518 if (!GTK_IS_WINDOW(window)) 519 { 520 free(data); 521 return G_SOURCE_REMOVE; 522 } 523 524 gtk_window_begin_resize_drag(options->mainwindow, options->edge, mouseButton, xroot, yroot, dragTime); 525 free(data); 526 527 return G_SOURCE_REMOVE; 528 } 529 530 void StartResize(void *webview, GtkWindow *mainwindow, GdkWindowEdge edge) 531 { 532 ResizeOptions *data = malloc(sizeof(ResizeOptions)); 533 data->webview = webview; 534 data->mainwindow = mainwindow; 535 data->edge = edge; 536 ExecuteOnMainThread(startResize, (gpointer)data); 537 } 538 539 void ExecuteJS(void *data) 540 { 541 struct JSCallback *js = data; 542 webkit_web_view_run_javascript(js->webview, js->script, NULL, NULL, NULL); 543 free(js->script); 544 } 545 546 void extern processMessageDialogResult(char *); 547 548 void MessageDialog(void *data) 549 { 550 GtkDialogFlags flags; 551 GtkMessageType messageType; 552 MessageDialogOptions *options = (MessageDialogOptions *)data; 553 if (options->messageType == 0) 554 { 555 messageType = GTK_MESSAGE_INFO; 556 flags = GTK_BUTTONS_OK; 557 } 558 else if (options->messageType == 1) 559 { 560 messageType = GTK_MESSAGE_ERROR; 561 flags = GTK_BUTTONS_OK; 562 } 563 else if (options->messageType == 2) 564 { 565 messageType = GTK_MESSAGE_QUESTION; 566 flags = GTK_BUTTONS_YES_NO; 567 } 568 else 569 { 570 messageType = GTK_MESSAGE_WARNING; 571 flags = GTK_BUTTONS_OK; 572 } 573 574 GtkWidget *dialog; 575 dialog = gtk_message_dialog_new(GTK_WINDOW(options->window), 576 GTK_DIALOG_DESTROY_WITH_PARENT, 577 messageType, 578 flags, 579 options->message, NULL); 580 gtk_window_set_title(GTK_WINDOW(dialog), options->title); 581 GtkResponseType result = gtk_dialog_run(GTK_DIALOG(dialog)); 582 if (result == GTK_RESPONSE_YES) 583 { 584 processMessageDialogResult("Yes"); 585 } 586 else if (result == GTK_RESPONSE_NO) 587 { 588 processMessageDialogResult("No"); 589 } 590 else if (result == GTK_RESPONSE_OK) 591 { 592 processMessageDialogResult("OK"); 593 } 594 else if (result == GTK_RESPONSE_CANCEL) 595 { 596 processMessageDialogResult("Cancel"); 597 } 598 else 599 { 600 processMessageDialogResult(""); 601 } 602 603 gtk_widget_destroy(dialog); 604 free(options->title); 605 free(options->message); 606 } 607 608 void extern processOpenFileResult(void *); 609 610 GtkFileFilter **AllocFileFilterArray(size_t ln) 611 { 612 return (GtkFileFilter **)malloc(ln * sizeof(GtkFileFilter *)); 613 } 614 615 void freeFileFilterArray(GtkFileFilter **filters) 616 { 617 free(filters); 618 } 619 620 void Opendialog(void *data) 621 { 622 struct OpenFileDialogOptions *options = data; 623 char *label = "_Open"; 624 if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE) 625 { 626 label = "_Save"; 627 } 628 GtkWidget *dlgWidget = gtk_file_chooser_dialog_new(options->title, options->window, options->action, 629 "_Cancel", GTK_RESPONSE_CANCEL, 630 label, GTK_RESPONSE_ACCEPT, 631 NULL); 632 633 GtkFileChooser *fc = GTK_FILE_CHOOSER(dlgWidget); 634 // filters 635 if (options->filters != 0) 636 { 637 int index = 0; 638 GtkFileFilter *thisFilter; 639 while (options->filters[index] != NULL) 640 { 641 thisFilter = options->filters[index]; 642 gtk_file_chooser_add_filter(fc, thisFilter); 643 index++; 644 } 645 } 646 647 gtk_file_chooser_set_local_only(fc, FALSE); 648 649 if (options->multipleFiles == 1) 650 { 651 gtk_file_chooser_set_select_multiple(fc, TRUE); 652 } 653 gtk_file_chooser_set_do_overwrite_confirmation(fc, TRUE); 654 if (options->createDirectories == 1) 655 { 656 gtk_file_chooser_set_create_folders(fc, TRUE); 657 } 658 if (options->showHiddenFiles == 1) 659 { 660 gtk_file_chooser_set_show_hidden(fc, TRUE); 661 } 662 663 if (options->defaultDirectory != NULL) 664 { 665 gtk_file_chooser_set_current_folder(fc, options->defaultDirectory); 666 free(options->defaultDirectory); 667 } 668 669 if (options->action == GTK_FILE_CHOOSER_ACTION_SAVE) 670 { 671 if (options->defaultFilename != NULL) 672 { 673 gtk_file_chooser_set_current_name(fc, options->defaultFilename); 674 free(options->defaultFilename); 675 } 676 } 677 678 gint response = gtk_dialog_run(GTK_DIALOG(dlgWidget)); 679 680 // Max 1024 files to select 681 char **result = calloc(1024, sizeof(char *)); 682 int resultIndex = 0; 683 684 if (response == GTK_RESPONSE_ACCEPT) 685 { 686 GSList *filenames = gtk_file_chooser_get_filenames(fc); 687 GSList *iter = filenames; 688 while (iter) 689 { 690 result[resultIndex++] = (char *)iter->data; 691 iter = g_slist_next(iter); 692 if (resultIndex == 1024) 693 { 694 break; 695 } 696 } 697 processOpenFileResult(result); 698 iter = filenames; 699 while (iter) 700 { 701 g_free(iter->data); 702 iter = g_slist_next(iter); 703 } 704 } 705 else 706 { 707 processOpenFileResult(result); 708 } 709 free(result); 710 711 // Release filters 712 if (options->filters != NULL) 713 { 714 int index = 0; 715 GtkFileFilter *thisFilter; 716 while (options->filters[index] != 0) 717 { 718 thisFilter = options->filters[index]; 719 g_object_unref(thisFilter); 720 index++; 721 } 722 freeFileFilterArray(options->filters); 723 } 724 gtk_widget_destroy(dlgWidget); 725 free(options->title); 726 } 727 728 GtkFileFilter *newFileFilter() 729 { 730 GtkFileFilter *result = gtk_file_filter_new(); 731 g_object_ref(result); 732 return result; 733 } 734 735 void ShowInspector(void *webview) { 736 WebKitWebInspector *inspector = webkit_web_view_get_inspector(WEBKIT_WEB_VIEW(webview)); 737 webkit_web_inspector_show(WEBKIT_WEB_INSPECTOR(inspector)); 738 } 739 740 void sendShowInspectorMessage() { 741 processMessage("wails:showInspector"); 742 } 743 744 void InstallF12Hotkey(void *window) 745 { 746 // When the user presses Ctrl+Shift+F12, call ShowInspector 747 GtkAccelGroup *accel_group = gtk_accel_group_new(); 748 gtk_window_add_accel_group(GTK_WINDOW(window), accel_group); 749 GClosure *closure = g_cclosure_new(G_CALLBACK(sendShowInspectorMessage), window, NULL); 750 gtk_accel_group_connect(accel_group, GDK_KEY_F12, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE, closure); 751 }