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  }