refactor: use //ui/shell_dialogs on Linux (#42045)
		
	* refactor: use //ui/shell_dialogs on Linux * fix: add proper filtering * fix: add support for missing dialog features to //shell_dialogs * fix: parent_window could be null * chore: cleanup patch * fix: use a OnceCallback in the sync implementation * chore: remove stray debuglog * Apply suggestions from code review Co-authored-by: Charles Kerr <charles@charleskerr.com> * refactor: use settings struct * fix: show hidden file property checking * chore: changes from review * fix: multi selection for dialogs --------- Co-authored-by: Charles Kerr <charles@charleskerr.com>
This commit is contained in:
		
					parent
					
						
							
								6675f3ae65
							
						
					
				
			
			
				commit
				
					
						865b0499bb
					
				
			
		
					 8 changed files with 514 additions and 442 deletions
				
			
		
							
								
								
									
										15
									
								
								BUILD.gn
									
										
									
									
									
								
							
							
						
						
									
										15
									
								
								BUILD.gn
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -81,18 +81,11 @@ if (is_linux) {
 | 
			
		|||
    ]
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  # Generates electron_gtk_stubs.h header which contains
 | 
			
		||||
  # stubs for extracting function ptrs from the gtk library.
 | 
			
		||||
  # Function signatures for which stubs are required should be
 | 
			
		||||
  # declared in electron_gtk.sigs, currently this file contains
 | 
			
		||||
  # signatures for the functions used with native file chooser
 | 
			
		||||
  # implementation. In future, this file can be extended to contain
 | 
			
		||||
  # gtk4 stubs to switch gtk version in runtime.
 | 
			
		||||
  # Generates headers which contain stubs for extracting function ptrs
 | 
			
		||||
  # from the gtk library. Function signatures for which stubs are
 | 
			
		||||
  # required should be declared in the sig files.
 | 
			
		||||
  generate_stubs("electron_gtk_stubs") {
 | 
			
		||||
    sigs = [
 | 
			
		||||
      "shell/browser/ui/electron_gdk_pixbuf.sigs",
 | 
			
		||||
      "shell/browser/ui/electron_gtk.sigs",
 | 
			
		||||
    ]
 | 
			
		||||
    sigs = [ "shell/browser/ui/electron_gdk_pixbuf.sigs" ]
 | 
			
		||||
    extra_header = "shell/browser/ui/electron_gtk.fragment"
 | 
			
		||||
    output_name = "electron_gtk_stubs"
 | 
			
		||||
    public_deps = [ "//ui/gtk:gtk_config" ]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,7 +34,7 @@ filenames = {
 | 
			
		|||
    "shell/browser/notifications/linux/notification_presenter_linux.h",
 | 
			
		||||
    "shell/browser/relauncher_linux.cc",
 | 
			
		||||
    "shell/browser/ui/electron_desktop_window_tree_host_linux.cc",
 | 
			
		||||
    "shell/browser/ui/file_dialog_gtk.cc",
 | 
			
		||||
    "shell/browser/ui/file_dialog_linux.cc",
 | 
			
		||||
    "shell/browser/ui/gtk/menu_gtk.cc",
 | 
			
		||||
    "shell/browser/ui/gtk/menu_gtk.h",
 | 
			
		||||
    "shell/browser/ui/gtk/menu_util.cc",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -128,3 +128,4 @@ fix_getcursorscreenpoint_wrongly_returns_0_0.patch
 | 
			
		|||
fix_add_support_for_skipping_first_2_no-op_refreshes_in_thumb_cap.patch
 | 
			
		||||
refactor_expose_file_system_access_blocklist.patch
 | 
			
		||||
revert_power_update_trace_counter_in_power_monitor.patch
 | 
			
		||||
feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,243 @@
 | 
			
		|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
			
		||||
From: Shelley Vohr <shelley.vohr@gmail.com>
 | 
			
		||||
Date: Sun, 5 May 2024 09:17:17 +0000
 | 
			
		||||
Subject: feat: add support for missing dialog features to //shell_dialogs
 | 
			
		||||
 | 
			
		||||
This CL adds support for the following features to //shell_dialogs:
 | 
			
		||||
* buttonLabel - Custom label for the confirmation button.
 | 
			
		||||
* showHiddenFiles - Show hidden files in dialog.
 | 
			
		||||
* showOverwriteConfirmation - Whether the user will be presented a confirmation dialog if the user types a file name that already exists.
 | 
			
		||||
 | 
			
		||||
This may be partially upstreamed to Chromium in the future.
 | 
			
		||||
 | 
			
		||||
diff --git a/ui/gtk/select_file_dialog_linux_gtk.cc b/ui/gtk/select_file_dialog_linux_gtk.cc
 | 
			
		||||
index 698f97b23f851c02af037880585c82d4494da63f..0a3b701a701289a2124214e7421a6383ff7f0320 100644
 | 
			
		||||
--- a/ui/gtk/select_file_dialog_linux_gtk.cc
 | 
			
		||||
+++ b/ui/gtk/select_file_dialog_linux_gtk.cc
 | 
			
		||||
@@ -243,6 +243,10 @@ void SelectFileDialogLinuxGtk::SelectFileImpl(
 | 
			
		||||
 
 | 
			
		||||
   std::string title_string = base::UTF16ToUTF8(title);
 | 
			
		||||
 
 | 
			
		||||
+  ExtraSettings extra_settings;
 | 
			
		||||
+  if (params)
 | 
			
		||||
+    extra_settings = *(static_cast<ExtraSettings*>(params));
 | 
			
		||||
+
 | 
			
		||||
   set_file_type_index(file_type_index);
 | 
			
		||||
   if (file_types)
 | 
			
		||||
     set_file_types(*file_types);
 | 
			
		||||
@@ -261,23 +265,23 @@ void SelectFileDialogLinuxGtk::SelectFileImpl(
 | 
			
		||||
     case SELECT_UPLOAD_FOLDER:
 | 
			
		||||
     case SELECT_EXISTING_FOLDER:
 | 
			
		||||
       dialog = CreateSelectFolderDialog(type, title_string, default_path,
 | 
			
		||||
-                                        owning_window);
 | 
			
		||||
+                                        owning_window, extra_settings);
 | 
			
		||||
       connect("response",
 | 
			
		||||
               &SelectFileDialogLinuxGtk::OnSelectSingleFolderDialogResponse);
 | 
			
		||||
       break;
 | 
			
		||||
     case SELECT_OPEN_FILE:
 | 
			
		||||
-      dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
 | 
			
		||||
+      dialog = CreateFileOpenDialog(title_string, default_path, owning_window, extra_settings);
 | 
			
		||||
       connect("response",
 | 
			
		||||
               &SelectFileDialogLinuxGtk::OnSelectSingleFileDialogResponse);
 | 
			
		||||
       break;
 | 
			
		||||
     case SELECT_OPEN_MULTI_FILE:
 | 
			
		||||
       dialog =
 | 
			
		||||
-          CreateMultiFileOpenDialog(title_string, default_path, owning_window);
 | 
			
		||||
+          CreateMultiFileOpenDialog(title_string, default_path, owning_window, extra_settings);
 | 
			
		||||
       connect("response",
 | 
			
		||||
               &SelectFileDialogLinuxGtk::OnSelectMultiFileDialogResponse);
 | 
			
		||||
       break;
 | 
			
		||||
     case SELECT_SAVEAS_FILE:
 | 
			
		||||
-      dialog = CreateSaveAsDialog(title_string, default_path, owning_window);
 | 
			
		||||
+      dialog = CreateSaveAsDialog(title_string, default_path, owning_window, extra_settings);
 | 
			
		||||
       connect("response",
 | 
			
		||||
               &SelectFileDialogLinuxGtk::OnSelectSingleFileDialogResponse);
 | 
			
		||||
       break;
 | 
			
		||||
@@ -412,10 +416,14 @@ void SelectFileDialogLinuxGtk::FileNotSelected(GtkWidget* dialog) {
 | 
			
		||||
 GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
 | 
			
		||||
     const std::string& title,
 | 
			
		||||
     const base::FilePath& default_path,
 | 
			
		||||
-    gfx::NativeWindow parent) {
 | 
			
		||||
+    gfx::NativeWindow parent,
 | 
			
		||||
+    const ExtraSettings& settings) {
 | 
			
		||||
+  const char* button_label = settings.button_label.empty()
 | 
			
		||||
+                                 ? GetOpenLabel()
 | 
			
		||||
+                                 : settings.button_label.c_str();
 | 
			
		||||
   GtkWidget* dialog = GtkFileChooserDialogNew(
 | 
			
		||||
       title.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_OPEN, GetCancelLabel(),
 | 
			
		||||
-      GTK_RESPONSE_CANCEL, GetOpenLabel(), GTK_RESPONSE_ACCEPT);
 | 
			
		||||
+      GTK_RESPONSE_CANCEL, button_label, GTK_RESPONSE_ACCEPT);
 | 
			
		||||
   SetGtkTransientForAura(dialog, parent);
 | 
			
		||||
   AddFilters(GTK_FILE_CHOOSER(dialog));
 | 
			
		||||
 
 | 
			
		||||
@@ -431,6 +439,8 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
 | 
			
		||||
     GtkFileChooserSetCurrentFolder(GTK_FILE_CHOOSER(dialog),
 | 
			
		||||
                                    *last_opened_path());
 | 
			
		||||
   }
 | 
			
		||||
+  gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog),
 | 
			
		||||
+                                   settings.show_hidden);
 | 
			
		||||
   return dialog;
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
@@ -438,7 +448,8 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
 | 
			
		||||
     Type type,
 | 
			
		||||
     const std::string& title,
 | 
			
		||||
     const base::FilePath& default_path,
 | 
			
		||||
-    gfx::NativeWindow parent) {
 | 
			
		||||
+    gfx::NativeWindow parent,
 | 
			
		||||
+    const ExtraSettings& settings) {
 | 
			
		||||
   std::string title_string = title;
 | 
			
		||||
   if (title_string.empty()) {
 | 
			
		||||
     title_string =
 | 
			
		||||
@@ -446,11 +457,14 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
 | 
			
		||||
             ? l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE)
 | 
			
		||||
             : l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
 | 
			
		||||
   }
 | 
			
		||||
-  std::string accept_button_label =
 | 
			
		||||
-      (type == SELECT_UPLOAD_FOLDER)
 | 
			
		||||
-          ? l10n_util::GetStringUTF8(
 | 
			
		||||
-                IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON)
 | 
			
		||||
-          : GetOpenLabel();
 | 
			
		||||
+
 | 
			
		||||
+  std::string accept_button_label = settings.button_label;
 | 
			
		||||
+  if (accept_button_label.empty()) {
 | 
			
		||||
+    accept_button_label = (type == SELECT_UPLOAD_FOLDER)
 | 
			
		||||
+        ? l10n_util::GetStringUTF8(
 | 
			
		||||
+              IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON)
 | 
			
		||||
+        : GetOpenLabel();
 | 
			
		||||
+  }
 | 
			
		||||
 
 | 
			
		||||
   GtkWidget* dialog = GtkFileChooserDialogNew(
 | 
			
		||||
       title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
 | 
			
		||||
@@ -472,19 +486,21 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
 | 
			
		||||
   gtk_file_filter_add_mime_type(only_folders, "inode/directory");
 | 
			
		||||
   gtk_file_filter_add_mime_type(only_folders, "text/directory");
 | 
			
		||||
   gtk_file_chooser_add_filter(chooser, only_folders);
 | 
			
		||||
-  gtk_file_chooser_set_select_multiple(chooser, FALSE);
 | 
			
		||||
+  gtk_file_chooser_set_select_multiple(chooser, settings.allow_multiple_selection);
 | 
			
		||||
+  gtk_file_chooser_set_show_hidden(chooser, settings.show_hidden);
 | 
			
		||||
   return dialog;
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
 GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenDialog(
 | 
			
		||||
     const std::string& title,
 | 
			
		||||
     const base::FilePath& default_path,
 | 
			
		||||
-    gfx::NativeWindow parent) {
 | 
			
		||||
+    gfx::NativeWindow parent,
 | 
			
		||||
+    const ExtraSettings& settings) {
 | 
			
		||||
   std::string title_string =
 | 
			
		||||
       !title.empty() ? title
 | 
			
		||||
                      : l10n_util::GetStringUTF8(IDS_OPEN_FILE_DIALOG_TITLE);
 | 
			
		||||
 
 | 
			
		||||
-  GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
 | 
			
		||||
+  GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent, settings);
 | 
			
		||||
   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
 | 
			
		||||
   return dialog;
 | 
			
		||||
 }
 | 
			
		||||
@@ -492,12 +508,14 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenDialog(
 | 
			
		||||
 GtkWidget* SelectFileDialogLinuxGtk::CreateMultiFileOpenDialog(
 | 
			
		||||
     const std::string& title,
 | 
			
		||||
     const base::FilePath& default_path,
 | 
			
		||||
-    gfx::NativeWindow parent) {
 | 
			
		||||
+    gfx::NativeWindow parent,
 | 
			
		||||
+    const ExtraSettings& settings) {
 | 
			
		||||
   std::string title_string =
 | 
			
		||||
       !title.empty() ? title
 | 
			
		||||
                      : l10n_util::GetStringUTF8(IDS_OPEN_FILES_DIALOG_TITLE);
 | 
			
		||||
 
 | 
			
		||||
-  GtkWidget* dialog = CreateFileOpenHelper(title_string, default_path, parent);
 | 
			
		||||
+  GtkWidget* dialog =
 | 
			
		||||
+      CreateFileOpenHelper(title_string, default_path, parent, settings);
 | 
			
		||||
   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
 | 
			
		||||
   return dialog;
 | 
			
		||||
 }
 | 
			
		||||
@@ -505,14 +523,17 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateMultiFileOpenDialog(
 | 
			
		||||
 GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
 | 
			
		||||
     const std::string& title,
 | 
			
		||||
     const base::FilePath& default_path,
 | 
			
		||||
-    gfx::NativeWindow parent) {
 | 
			
		||||
+    gfx::NativeWindow parent,
 | 
			
		||||
+    const ExtraSettings& settings) {
 | 
			
		||||
   std::string title_string =
 | 
			
		||||
       !title.empty() ? title
 | 
			
		||||
                      : l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
 | 
			
		||||
-
 | 
			
		||||
+  const char* button_label = settings.button_label.empty()
 | 
			
		||||
+                                 ? GetSaveLabel()
 | 
			
		||||
+                                 : settings.button_label.c_str();
 | 
			
		||||
   GtkWidget* dialog = GtkFileChooserDialogNew(
 | 
			
		||||
       title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SAVE,
 | 
			
		||||
-      GetCancelLabel(), GTK_RESPONSE_CANCEL, GetSaveLabel(),
 | 
			
		||||
+      GetCancelLabel(), GTK_RESPONSE_CANCEL, button_label,
 | 
			
		||||
       GTK_RESPONSE_ACCEPT);
 | 
			
		||||
   SetGtkTransientForAura(dialog, parent);
 | 
			
		||||
 
 | 
			
		||||
@@ -538,9 +559,10 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
 | 
			
		||||
   gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), FALSE);
 | 
			
		||||
   // Overwrite confirmation is always enabled in GTK4.
 | 
			
		||||
   if (!GtkCheckVersion(4)) {
 | 
			
		||||
-    gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog),
 | 
			
		||||
-                                                   TRUE);
 | 
			
		||||
+    gtk_file_chooser_set_do_overwrite_confirmation(
 | 
			
		||||
+        GTK_FILE_CHOOSER(dialog), settings.show_overwrite_confirmation);
 | 
			
		||||
   }
 | 
			
		||||
+  gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), settings.show_hidden);
 | 
			
		||||
   return dialog;
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
diff --git a/ui/gtk/select_file_dialog_linux_gtk.h b/ui/gtk/select_file_dialog_linux_gtk.h
 | 
			
		||||
index 53ae15f14c45ee72abdae172fc4555c9e4b3ff9a..af181afd9db1351cd886ba24dd651c7bf2f8a716 100644
 | 
			
		||||
--- a/ui/gtk/select_file_dialog_linux_gtk.h
 | 
			
		||||
+++ b/ui/gtk/select_file_dialog_linux_gtk.h
 | 
			
		||||
@@ -15,6 +15,13 @@
 | 
			
		||||
 
 | 
			
		||||
 namespace gtk {
 | 
			
		||||
 
 | 
			
		||||
+struct ExtraSettings {
 | 
			
		||||
+  std::string button_label;
 | 
			
		||||
+  bool show_overwrite_confirmation = true;
 | 
			
		||||
+  bool show_hidden = false;
 | 
			
		||||
+  bool allow_multiple_selection = false;
 | 
			
		||||
+};
 | 
			
		||||
+
 | 
			
		||||
 // Implementation of SelectFileDialog that shows a Gtk common dialog for
 | 
			
		||||
 // choosing a file or folder. This acts as a modal dialog.
 | 
			
		||||
 class SelectFileDialogLinuxGtk : public ui::SelectFileDialogLinux,
 | 
			
		||||
@@ -90,19 +97,23 @@ class SelectFileDialogLinuxGtk : public ui::SelectFileDialogLinux,
 | 
			
		||||
   GtkWidget* CreateSelectFolderDialog(Type type,
 | 
			
		||||
                                       const std::string& title,
 | 
			
		||||
                                       const base::FilePath& default_path,
 | 
			
		||||
-                                      gfx::NativeWindow parent);
 | 
			
		||||
+                                      gfx::NativeWindow parent,
 | 
			
		||||
+                                      const ExtraSettings& settings);
 | 
			
		||||
 
 | 
			
		||||
   GtkWidget* CreateFileOpenDialog(const std::string& title,
 | 
			
		||||
                                   const base::FilePath& default_path,
 | 
			
		||||
-                                  gfx::NativeWindow parent);
 | 
			
		||||
+                                  gfx::NativeWindow parent,
 | 
			
		||||
+                                  const ExtraSettings& settings);
 | 
			
		||||
 
 | 
			
		||||
   GtkWidget* CreateMultiFileOpenDialog(const std::string& title,
 | 
			
		||||
                                        const base::FilePath& default_path,
 | 
			
		||||
-                                       gfx::NativeWindow parent);
 | 
			
		||||
+                                       gfx::NativeWindow parent,
 | 
			
		||||
+                                       const ExtraSettings& settings);
 | 
			
		||||
 
 | 
			
		||||
   GtkWidget* CreateSaveAsDialog(const std::string& title,
 | 
			
		||||
                                 const base::FilePath& default_path,
 | 
			
		||||
-                                gfx::NativeWindow parent);
 | 
			
		||||
+                                gfx::NativeWindow parent,
 | 
			
		||||
+                                const ExtraSettings& settings);
 | 
			
		||||
 
 | 
			
		||||
   // Removes and returns the |params| associated with |dialog| from
 | 
			
		||||
   // |params_map_|.
 | 
			
		||||
@@ -121,7 +132,8 @@ class SelectFileDialogLinuxGtk : public ui::SelectFileDialogLinux,
 | 
			
		||||
   // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
 | 
			
		||||
   GtkWidget* CreateFileOpenHelper(const std::string& title,
 | 
			
		||||
                                   const base::FilePath& default_path,
 | 
			
		||||
-                                  gfx::NativeWindow parent);
 | 
			
		||||
+                                  gfx::NativeWindow parent,
 | 
			
		||||
+                                  const ExtraSettings& settings);
 | 
			
		||||
 
 | 
			
		||||
   // Callback for when the user responds to a Save As or Open File dialog.
 | 
			
		||||
   void OnSelectSingleFileDialogResponse(GtkWidget* dialog, int response_id);
 | 
			
		||||
| 
						 | 
				
			
			@ -1,13 +1,13 @@
 | 
			
		|||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
 | 
			
		||||
From: deepak1556 <hop2deep@gmail.com>
 | 
			
		||||
Date: Thu, 7 Apr 2022 20:30:16 +0900
 | 
			
		||||
Subject: Make gtk::GetLibGtk and gtk::GetLibGdkPixbuf public
 | 
			
		||||
Subject: Make gtk::GetLibGdkPixbuf public
 | 
			
		||||
 | 
			
		||||
Allows embedders to get a handle to the gtk and
 | 
			
		||||
gdk_pixbuf libraries already loaded in the process.
 | 
			
		||||
Allows embedders to get a handle to the gdk_pixbuf
 | 
			
		||||
library already loaded in the process.
 | 
			
		||||
 | 
			
		||||
diff --git a/ui/gtk/gtk_compat.cc b/ui/gtk/gtk_compat.cc
 | 
			
		||||
index 3a4b856ec5c2f5c3ede6e8f6db7858498b737702..a308249c66c01856df84e64bda0411dbcbd96457 100644
 | 
			
		||||
index 3a4b856ec5c2f5c3ede6e8f6db7858498b737702..e410998c4197c98947d2e1ad8bebe12c70379358 100644
 | 
			
		||||
--- a/ui/gtk/gtk_compat.cc
 | 
			
		||||
+++ b/ui/gtk/gtk_compat.cc
 | 
			
		||||
@@ -66,11 +66,6 @@ void* GetLibGio() {
 | 
			
		||||
| 
						 | 
				
			
			@ -22,20 +22,7 @@ index 3a4b856ec5c2f5c3ede6e8f6db7858498b737702..a308249c66c01856df84e64bda0411db
 | 
			
		|||
 void* GetLibGdk3() {
 | 
			
		||||
   static void* libgdk3 = DlOpen("libgdk-3.so.0");
 | 
			
		||||
   return libgdk3;
 | 
			
		||||
@@ -86,12 +81,6 @@ void* GetLibGtk4(bool check = true) {
 | 
			
		||||
   return libgtk4;
 | 
			
		||||
 }
 | 
			
		||||
 
 | 
			
		||||
-void* GetLibGtk() {
 | 
			
		||||
-  if (GtkCheckVersion(4))
 | 
			
		||||
-    return GetLibGtk4();
 | 
			
		||||
-  return GetLibGtk3();
 | 
			
		||||
-}
 | 
			
		||||
-
 | 
			
		||||
 bool LoadGtk3() {
 | 
			
		||||
   if (!GetLibGtk3(false))
 | 
			
		||||
     return false;
 | 
			
		||||
@@ -134,6 +123,17 @@ gfx::Insets InsetsFromGtkBorder(const GtkBorder& border) {
 | 
			
		||||
@@ -134,6 +129,11 @@ gfx::Insets InsetsFromGtkBorder(const GtkBorder& border) {
 | 
			
		||||
 
 | 
			
		||||
 }  // namespace
 | 
			
		||||
 
 | 
			
		||||
| 
						 | 
				
			
			@ -43,29 +30,20 @@ index 3a4b856ec5c2f5c3ede6e8f6db7858498b737702..a308249c66c01856df84e64bda0411db
 | 
			
		|||
+  static void* libgdk_pixbuf = DlOpen("libgdk_pixbuf-2.0.so.0");
 | 
			
		||||
+  return libgdk_pixbuf;
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
+void* GetLibGtk() {
 | 
			
		||||
+  if (GtkCheckVersion(4))
 | 
			
		||||
+    return GetLibGtk4();
 | 
			
		||||
+  return GetLibGtk3();
 | 
			
		||||
+}
 | 
			
		||||
+
 | 
			
		||||
 bool LoadGtk() {
 | 
			
		||||
   static bool loaded = LoadGtkImpl();
 | 
			
		||||
   return loaded;
 | 
			
		||||
diff --git a/ui/gtk/gtk_compat.h b/ui/gtk/gtk_compat.h
 | 
			
		||||
index 19f73cc179d82a3729c5fe37883460ac05f4d0c3..cdb67c98291f145f89f76a50b31bf00318648518 100644
 | 
			
		||||
index 19f73cc179d82a3729c5fe37883460ac05f4d0c3..17aa0b95bd6158ed02c03095c1687185a057fe62 100644
 | 
			
		||||
--- a/ui/gtk/gtk_compat.h
 | 
			
		||||
+++ b/ui/gtk/gtk_compat.h
 | 
			
		||||
@@ -41,6 +41,12 @@ using SkColor = uint32_t;
 | 
			
		||||
@@ -41,6 +41,9 @@ using SkColor = uint32_t;
 | 
			
		||||
 
 | 
			
		||||
 namespace gtk {
 | 
			
		||||
 
 | 
			
		||||
+// Get handle to the currently loaded gdk_pixbuf library in the process.
 | 
			
		||||
+void* GetLibGdkPixbuf();
 | 
			
		||||
+
 | 
			
		||||
+// Get handle to the currently loaded gtk library in the process.
 | 
			
		||||
+void* GetLibGtk();
 | 
			
		||||
+
 | 
			
		||||
 // Loads libgtk and related libraries and returns true on success.
 | 
			
		||||
 bool LoadGtk();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -398,12 +398,6 @@ void ElectronBrowserMainParts::ToolkitInitialized() {
 | 
			
		|||
  CHECK(linux_ui);
 | 
			
		||||
  linux_ui_getter_ = std::make_unique<LinuxUiGetterImpl>();
 | 
			
		||||
 | 
			
		||||
  // Try loading gtk symbols used by Electron.
 | 
			
		||||
  electron::InitializeElectron_gtk(gtk::GetLibGtk());
 | 
			
		||||
  if (!electron::IsElectron_gtkInitialized()) {
 | 
			
		||||
    electron::UninitializeElectron_gtk();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  electron::InitializeElectron_gdk_pixbuf(gtk::GetLibGdkPixbuf());
 | 
			
		||||
  CHECK(electron::IsElectron_gdk_pixbufInitialized())
 | 
			
		||||
      << "Failed to initialize libgdk_pixbuf-2.0.so.0";
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,395 +0,0 @@
 | 
			
		|||
// Copyright (c) 2014 GitHub, Inc.
 | 
			
		||||
// Use of this source code is governed by the MIT license that can be
 | 
			
		||||
// found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include "base/files/file_util.h"
 | 
			
		||||
#include "base/functional/bind.h"
 | 
			
		||||
#include "base/functional/callback.h"
 | 
			
		||||
#include "base/memory/raw_ptr.h"
 | 
			
		||||
#include "base/memory/raw_ptr_exclusion.h"
 | 
			
		||||
#include "base/strings/string_util.h"
 | 
			
		||||
#include "electron/electron_gtk_stubs.h"
 | 
			
		||||
#include "shell/browser/javascript_environment.h"
 | 
			
		||||
#include "shell/browser/native_window_views.h"
 | 
			
		||||
#include "shell/browser/ui/file_dialog.h"
 | 
			
		||||
#include "shell/browser/ui/gtk_util.h"
 | 
			
		||||
#include "shell/common/gin_converters/file_path_converter.h"
 | 
			
		||||
#include "shell/common/thread_restrictions.h"
 | 
			
		||||
#include "ui/base/glib/scoped_gsignal.h"
 | 
			
		||||
#include "ui/gtk/gtk_ui.h"    // nogncheck
 | 
			
		||||
#include "ui/gtk/gtk_util.h"  // nogncheck
 | 
			
		||||
 | 
			
		||||
namespace file_dialog {
 | 
			
		||||
 | 
			
		||||
DialogSettings::DialogSettings() = default;
 | 
			
		||||
DialogSettings::DialogSettings(const DialogSettings&) = default;
 | 
			
		||||
DialogSettings::~DialogSettings() = default;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
static const int kPreviewWidth = 256;
 | 
			
		||||
static const int kPreviewHeight = 512;
 | 
			
		||||
 | 
			
		||||
std::string MakeCaseInsensitivePattern(const std::string& extension) {
 | 
			
		||||
  // If the extension is the "all files" extension, no change needed.
 | 
			
		||||
  if (extension == "*")
 | 
			
		||||
    return extension;
 | 
			
		||||
 | 
			
		||||
  std::string pattern("*.");
 | 
			
		||||
  for (char ch : extension) {
 | 
			
		||||
    if (!base::IsAsciiAlpha(ch)) {
 | 
			
		||||
      pattern.push_back(ch);
 | 
			
		||||
      continue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pattern.push_back('[');
 | 
			
		||||
    pattern.push_back(base::ToLowerASCII(ch));
 | 
			
		||||
    pattern.push_back(base::ToUpperASCII(ch));
 | 
			
		||||
    pattern.push_back(']');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return pattern;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class FileChooserDialog {
 | 
			
		||||
 public:
 | 
			
		||||
  FileChooserDialog(GtkFileChooserAction action, const DialogSettings& settings)
 | 
			
		||||
      : parent_(
 | 
			
		||||
            static_cast<electron::NativeWindowViews*>(settings.parent_window)),
 | 
			
		||||
        filters_(settings.filters) {
 | 
			
		||||
    auto label = settings.button_label;
 | 
			
		||||
 | 
			
		||||
    if (electron::IsElectron_gtkInitialized()) {
 | 
			
		||||
      dialog_ = GTK_FILE_CHOOSER(gtk_file_chooser_native_new(
 | 
			
		||||
          settings.title.c_str(), nullptr, action,
 | 
			
		||||
          label.empty() ? nullptr : label.c_str(), nullptr));
 | 
			
		||||
    } else {
 | 
			
		||||
      const char* confirm_text = gtk_util::GetOkLabel();
 | 
			
		||||
      if (!label.empty())
 | 
			
		||||
        confirm_text = label.c_str();
 | 
			
		||||
      else if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
 | 
			
		||||
        confirm_text = gtk_util::GetSaveLabel();
 | 
			
		||||
      else if (action == GTK_FILE_CHOOSER_ACTION_OPEN)
 | 
			
		||||
        confirm_text = gtk_util::GetOpenLabel();
 | 
			
		||||
 | 
			
		||||
      dialog_ = GTK_FILE_CHOOSER(gtk_file_chooser_dialog_new(
 | 
			
		||||
          settings.title.c_str(), nullptr, action, gtk_util::GetCancelLabel(),
 | 
			
		||||
          GTK_RESPONSE_CANCEL, confirm_text, GTK_RESPONSE_ACCEPT, nullptr));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (parent_) {
 | 
			
		||||
      parent_->SetEnabled(false);
 | 
			
		||||
      if (electron::IsElectron_gtkInitialized()) {
 | 
			
		||||
        gtk_native_dialog_set_modal(GTK_NATIVE_DIALOG(dialog_), TRUE);
 | 
			
		||||
      } else {
 | 
			
		||||
        gtk::SetGtkTransientForAura(GTK_WIDGET(dialog_),
 | 
			
		||||
                                    parent_->GetNativeWindow());
 | 
			
		||||
        gtk_window_set_modal(GTK_WINDOW(dialog_), TRUE);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (action == GTK_FILE_CHOOSER_ACTION_SAVE)
 | 
			
		||||
      gtk_file_chooser_set_do_overwrite_confirmation(dialog_, TRUE);
 | 
			
		||||
    if (action != GTK_FILE_CHOOSER_ACTION_OPEN)
 | 
			
		||||
      gtk_file_chooser_set_create_folders(dialog_, TRUE);
 | 
			
		||||
 | 
			
		||||
    if (!settings.default_path.empty()) {
 | 
			
		||||
      electron::ScopedAllowBlockingForElectron allow_blocking;
 | 
			
		||||
      if (base::DirectoryExists(settings.default_path)) {
 | 
			
		||||
        gtk_file_chooser_set_current_folder(
 | 
			
		||||
            dialog_, settings.default_path.value().c_str());
 | 
			
		||||
      } else {
 | 
			
		||||
        if (settings.default_path.IsAbsolute()) {
 | 
			
		||||
          gtk_file_chooser_set_current_folder(
 | 
			
		||||
              dialog_, settings.default_path.DirName().value().c_str());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        gtk_file_chooser_set_current_name(
 | 
			
		||||
            GTK_FILE_CHOOSER(dialog_),
 | 
			
		||||
            settings.default_path.BaseName().value().c_str());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!settings.filters.empty())
 | 
			
		||||
      AddFilters(settings.filters);
 | 
			
		||||
 | 
			
		||||
    // GtkFileChooserNative does not support preview widgets through the
 | 
			
		||||
    // org.freedesktop.portal.FileChooser portal. In the case of running through
 | 
			
		||||
    // the org.freedesktop.portal.FileChooser portal, anything having to do with
 | 
			
		||||
    // the update-preview signal or the preview widget will just be ignored.
 | 
			
		||||
    if (!electron::IsElectron_gtkInitialized()) {
 | 
			
		||||
      preview_ = gtk_image_new();
 | 
			
		||||
      signals_.emplace_back(
 | 
			
		||||
          dialog_, "update-preview",
 | 
			
		||||
          base::BindRepeating(&FileChooserDialog::OnUpdatePreview,
 | 
			
		||||
                              base::Unretained(this)));
 | 
			
		||||
      gtk_file_chooser_set_preview_widget(dialog_, preview_);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ~FileChooserDialog() {
 | 
			
		||||
    if (electron::IsElectron_gtkInitialized()) {
 | 
			
		||||
      gtk_native_dialog_destroy(GTK_NATIVE_DIALOG(dialog_));
 | 
			
		||||
    } else {
 | 
			
		||||
      gtk_widget_destroy(GTK_WIDGET(dialog_));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (parent_)
 | 
			
		||||
      parent_->SetEnabled(true);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // disable copy
 | 
			
		||||
  FileChooserDialog(const FileChooserDialog&) = delete;
 | 
			
		||||
  FileChooserDialog& operator=(const FileChooserDialog&) = delete;
 | 
			
		||||
 | 
			
		||||
  void SetupOpenProperties(int properties) {
 | 
			
		||||
    const auto hasProp = [properties](OpenFileDialogProperty prop) {
 | 
			
		||||
      return gboolean((properties & prop) != 0);
 | 
			
		||||
    };
 | 
			
		||||
    auto* file_chooser = dialog();
 | 
			
		||||
    gtk_file_chooser_set_select_multiple(file_chooser,
 | 
			
		||||
                                         hasProp(OPEN_DIALOG_MULTI_SELECTIONS));
 | 
			
		||||
    gtk_file_chooser_set_show_hidden(file_chooser,
 | 
			
		||||
                                     hasProp(OPEN_DIALOG_SHOW_HIDDEN_FILES));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void SetupSaveProperties(int properties) {
 | 
			
		||||
    const auto hasProp = [properties](SaveFileDialogProperty prop) {
 | 
			
		||||
      return gboolean((properties & prop) != 0);
 | 
			
		||||
    };
 | 
			
		||||
    auto* file_chooser = dialog();
 | 
			
		||||
    gtk_file_chooser_set_show_hidden(file_chooser,
 | 
			
		||||
                                     hasProp(SAVE_DIALOG_SHOW_HIDDEN_FILES));
 | 
			
		||||
    gtk_file_chooser_set_do_overwrite_confirmation(
 | 
			
		||||
        file_chooser, hasProp(SAVE_DIALOG_SHOW_OVERWRITE_CONFIRMATION));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void RunAsynchronous() {
 | 
			
		||||
    signals_.emplace_back(
 | 
			
		||||
        GTK_WIDGET(dialog_), "response",
 | 
			
		||||
        base::BindRepeating(&FileChooserDialog::OnFileDialogResponse,
 | 
			
		||||
                            base::Unretained(this)));
 | 
			
		||||
    if (electron::IsElectron_gtkInitialized()) {
 | 
			
		||||
      gtk_native_dialog_show(GTK_NATIVE_DIALOG(dialog_));
 | 
			
		||||
    } else {
 | 
			
		||||
      gtk_widget_show_all(GTK_WIDGET(dialog_));
 | 
			
		||||
      gtk::GtkUi::GetPlatform()->ShowGtkWindow(GTK_WINDOW(dialog_));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void RunSaveAsynchronous(
 | 
			
		||||
      gin_helper::Promise<gin_helper::Dictionary> promise) {
 | 
			
		||||
    save_promise_ =
 | 
			
		||||
        std::make_unique<gin_helper::Promise<gin_helper::Dictionary>>(
 | 
			
		||||
            std::move(promise));
 | 
			
		||||
    RunAsynchronous();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void RunOpenAsynchronous(
 | 
			
		||||
      gin_helper::Promise<gin_helper::Dictionary> promise) {
 | 
			
		||||
    open_promise_ =
 | 
			
		||||
        std::make_unique<gin_helper::Promise<gin_helper::Dictionary>>(
 | 
			
		||||
            std::move(promise));
 | 
			
		||||
    RunAsynchronous();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  base::FilePath GetFileName() const {
 | 
			
		||||
    gchar* filename = gtk_file_chooser_get_filename(dialog_);
 | 
			
		||||
    const base::FilePath path(filename);
 | 
			
		||||
    g_free(filename);
 | 
			
		||||
    return path;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  std::vector<base::FilePath> GetFileNames() const {
 | 
			
		||||
    std::vector<base::FilePath> paths;
 | 
			
		||||
    auto* filenames = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog_));
 | 
			
		||||
    for (auto* iter = filenames; iter != nullptr; iter = iter->next) {
 | 
			
		||||
      auto* filename = static_cast<char*>(iter->data);
 | 
			
		||||
      paths.emplace_back(filename);
 | 
			
		||||
      g_free(filename);
 | 
			
		||||
    }
 | 
			
		||||
    g_slist_free(filenames);
 | 
			
		||||
    return paths;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void OnFileDialogResponse(GtkWidget* widget, int response);
 | 
			
		||||
 | 
			
		||||
  GtkFileChooser* dialog() const { return dialog_; }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  void AddFilters(const Filters& filters);
 | 
			
		||||
 | 
			
		||||
  raw_ptr<electron::NativeWindowViews> parent_;
 | 
			
		||||
 | 
			
		||||
  RAW_PTR_EXCLUSION GtkFileChooser* dialog_;
 | 
			
		||||
  RAW_PTR_EXCLUSION GtkWidget* preview_;
 | 
			
		||||
 | 
			
		||||
  Filters filters_;
 | 
			
		||||
  std::unique_ptr<gin_helper::Promise<gin_helper::Dictionary>> save_promise_;
 | 
			
		||||
  std::unique_ptr<gin_helper::Promise<gin_helper::Dictionary>> open_promise_;
 | 
			
		||||
 | 
			
		||||
  // Callback for when we update the preview for the selection.
 | 
			
		||||
  void OnUpdatePreview(GtkFileChooser* chooser);
 | 
			
		||||
 | 
			
		||||
  std::vector<ScopedGSignal> signals_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void FileChooserDialog::OnFileDialogResponse(GtkWidget* widget, int response) {
 | 
			
		||||
  if (electron::IsElectron_gtkInitialized()) {
 | 
			
		||||
    gtk_native_dialog_hide(GTK_NATIVE_DIALOG(dialog_));
 | 
			
		||||
  } else {
 | 
			
		||||
    gtk_widget_hide(GTK_WIDGET(dialog_));
 | 
			
		||||
  }
 | 
			
		||||
  v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
 | 
			
		||||
  v8::HandleScope scope(isolate);
 | 
			
		||||
  if (save_promise_) {
 | 
			
		||||
    auto dict = gin_helper::Dictionary::CreateEmpty(save_promise_->isolate());
 | 
			
		||||
    if (response == GTK_RESPONSE_ACCEPT) {
 | 
			
		||||
      dict.Set("canceled", false);
 | 
			
		||||
      dict.Set("filePath", GetFileName());
 | 
			
		||||
    } else {
 | 
			
		||||
      dict.Set("canceled", true);
 | 
			
		||||
      dict.Set("filePath", base::FilePath());
 | 
			
		||||
    }
 | 
			
		||||
    save_promise_->Resolve(dict);
 | 
			
		||||
  } else if (open_promise_) {
 | 
			
		||||
    auto dict = gin_helper::Dictionary::CreateEmpty(open_promise_->isolate());
 | 
			
		||||
    if (response == GTK_RESPONSE_ACCEPT) {
 | 
			
		||||
      dict.Set("canceled", false);
 | 
			
		||||
      dict.Set("filePaths", GetFileNames());
 | 
			
		||||
    } else {
 | 
			
		||||
      dict.Set("canceled", true);
 | 
			
		||||
      dict.Set("filePaths", std::vector<base::FilePath>());
 | 
			
		||||
    }
 | 
			
		||||
    open_promise_->Resolve(dict);
 | 
			
		||||
  }
 | 
			
		||||
  delete this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileChooserDialog::AddFilters(const Filters& filters) {
 | 
			
		||||
  for (const auto& filter : filters) {
 | 
			
		||||
    GtkFileFilter* gtk_filter = gtk_file_filter_new();
 | 
			
		||||
 | 
			
		||||
    for (const auto& extension : filter.second) {
 | 
			
		||||
      std::string pattern = MakeCaseInsensitivePattern(extension);
 | 
			
		||||
      gtk_file_filter_add_pattern(gtk_filter, pattern.c_str());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    gtk_file_filter_set_name(gtk_filter, filter.first.c_str());
 | 
			
		||||
    gtk_file_chooser_add_filter(dialog_, gtk_filter);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool CanPreview(const struct stat& st) {
 | 
			
		||||
  // Only preview regular files; pipes may hang.
 | 
			
		||||
  // See https://crbug.com/534754.
 | 
			
		||||
  if (!S_ISREG(st.st_mode)) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Don't preview huge files; they may crash.
 | 
			
		||||
  // https://github.com/electron/electron/issues/31630
 | 
			
		||||
  // Setting an arbitrary filesize max t at 100 MB here.
 | 
			
		||||
  constexpr off_t ArbitraryMax = 100000000ULL;
 | 
			
		||||
  return st.st_size < ArbitraryMax;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void FileChooserDialog::OnUpdatePreview(GtkFileChooser* chooser) {
 | 
			
		||||
  CHECK(!electron::IsElectron_gtkInitialized());
 | 
			
		||||
  gchar* filename = gtk_file_chooser_get_preview_filename(chooser);
 | 
			
		||||
  if (!filename) {
 | 
			
		||||
    gtk_file_chooser_set_preview_widget_active(chooser, FALSE);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  struct stat sb;
 | 
			
		||||
  if (stat(filename, &sb) != 0 || !CanPreview(sb)) {
 | 
			
		||||
    g_free(filename);
 | 
			
		||||
    gtk_file_chooser_set_preview_widget_active(chooser, FALSE);
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // This will preserve the image's aspect ratio.
 | 
			
		||||
  GdkPixbuf* pixbuf = gdk_pixbuf_new_from_file_at_size(filename, kPreviewWidth,
 | 
			
		||||
                                                       kPreviewHeight, nullptr);
 | 
			
		||||
  g_free(filename);
 | 
			
		||||
  if (pixbuf) {
 | 
			
		||||
    gtk_image_set_from_pixbuf(GTK_IMAGE(preview_), pixbuf);
 | 
			
		||||
    g_object_unref(pixbuf);
 | 
			
		||||
  }
 | 
			
		||||
  gtk_file_chooser_set_preview_widget_active(chooser, pixbuf ? TRUE : FALSE);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace
 | 
			
		||||
 | 
			
		||||
void ShowFileDialog(const FileChooserDialog& dialog) {
 | 
			
		||||
  // gtk_native_dialog_run() will call gtk_native_dialog_show() for us.
 | 
			
		||||
  if (!electron::IsElectron_gtkInitialized()) {
 | 
			
		||||
    gtk_widget_show_all(GTK_WIDGET(dialog.dialog()));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int RunFileDialog(const FileChooserDialog& dialog) {
 | 
			
		||||
  int response = 0;
 | 
			
		||||
  if (electron::IsElectron_gtkInitialized()) {
 | 
			
		||||
    response = gtk_native_dialog_run(GTK_NATIVE_DIALOG(dialog.dialog()));
 | 
			
		||||
  } else {
 | 
			
		||||
    response = gtk_dialog_run(GTK_DIALOG(dialog.dialog()));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return response;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ShowOpenDialogSync(const DialogSettings& settings,
 | 
			
		||||
                        std::vector<base::FilePath>* paths) {
 | 
			
		||||
  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
 | 
			
		||||
  if (settings.properties & OPEN_DIALOG_OPEN_DIRECTORY)
 | 
			
		||||
    action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
 | 
			
		||||
  FileChooserDialog open_dialog(action, settings);
 | 
			
		||||
  open_dialog.SetupOpenProperties(settings.properties);
 | 
			
		||||
 | 
			
		||||
  ShowFileDialog(open_dialog);
 | 
			
		||||
 | 
			
		||||
  const int response = RunFileDialog(open_dialog);
 | 
			
		||||
  if (response == GTK_RESPONSE_ACCEPT) {
 | 
			
		||||
    *paths = open_dialog.GetFileNames();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShowOpenDialog(const DialogSettings& settings,
 | 
			
		||||
                    gin_helper::Promise<gin_helper::Dictionary> promise) {
 | 
			
		||||
  GtkFileChooserAction action = GTK_FILE_CHOOSER_ACTION_OPEN;
 | 
			
		||||
  if (settings.properties & OPEN_DIALOG_OPEN_DIRECTORY)
 | 
			
		||||
    action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
 | 
			
		||||
  FileChooserDialog* open_dialog = new FileChooserDialog(action, settings);
 | 
			
		||||
  open_dialog->SetupOpenProperties(settings.properties);
 | 
			
		||||
  open_dialog->RunOpenAsynchronous(std::move(promise));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
 | 
			
		||||
  FileChooserDialog save_dialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
 | 
			
		||||
  save_dialog.SetupSaveProperties(settings.properties);
 | 
			
		||||
 | 
			
		||||
  ShowFileDialog(save_dialog);
 | 
			
		||||
 | 
			
		||||
  const int response = RunFileDialog(save_dialog);
 | 
			
		||||
  if (response == GTK_RESPONSE_ACCEPT) {
 | 
			
		||||
    *path = save_dialog.GetFileName();
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShowSaveDialog(const DialogSettings& settings,
 | 
			
		||||
                    gin_helper::Promise<gin_helper::Dictionary> promise) {
 | 
			
		||||
  FileChooserDialog* save_dialog =
 | 
			
		||||
      new FileChooserDialog(GTK_FILE_CHOOSER_ACTION_SAVE, settings);
 | 
			
		||||
  save_dialog->RunSaveAsynchronous(std::move(promise));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace file_dialog
 | 
			
		||||
							
								
								
									
										258
									
								
								shell/browser/ui/file_dialog_linux.cc
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								shell/browser/ui/file_dialog_linux.cc
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,258 @@
 | 
			
		|||
// Copyright (c) 2024 Microsoft, GmbH.
 | 
			
		||||
// Use of this source code is governed by the MIT license that can be
 | 
			
		||||
// found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include "base/files/file_util.h"
 | 
			
		||||
#include "base/functional/bind.h"
 | 
			
		||||
#include "base/functional/callback.h"
 | 
			
		||||
#include "base/memory/raw_ptr.h"
 | 
			
		||||
#include "base/memory/raw_ptr_exclusion.h"
 | 
			
		||||
#include "base/run_loop.h"
 | 
			
		||||
#include "base/strings/string_util.h"
 | 
			
		||||
#include "base/strings/utf_string_conversions.h"
 | 
			
		||||
#include "shell/browser/javascript_environment.h"
 | 
			
		||||
#include "shell/browser/native_window_views.h"
 | 
			
		||||
#include "shell/browser/ui/file_dialog.h"
 | 
			
		||||
#include "shell/common/gin_converters/callback_converter.h"
 | 
			
		||||
#include "shell/common/gin_converters/file_path_converter.h"
 | 
			
		||||
#include "ui/gtk/select_file_dialog_linux_gtk.h"  // nogncheck
 | 
			
		||||
#include "ui/shell_dialogs/select_file_dialog.h"
 | 
			
		||||
#include "ui/shell_dialogs/selected_file_info.h"
 | 
			
		||||
 | 
			
		||||
namespace file_dialog {
 | 
			
		||||
 | 
			
		||||
DialogSettings::DialogSettings() = default;
 | 
			
		||||
DialogSettings::DialogSettings(const DialogSettings&) = default;
 | 
			
		||||
DialogSettings::~DialogSettings() = default;
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
ui::SelectFileDialog::Type GetDialogType(int properties) {
 | 
			
		||||
  if (properties & OPEN_DIALOG_OPEN_DIRECTORY)
 | 
			
		||||
    return ui::SelectFileDialog::SELECT_FOLDER;
 | 
			
		||||
 | 
			
		||||
  if (properties & OPEN_DIALOG_MULTI_SELECTIONS)
 | 
			
		||||
    return ui::SelectFileDialog::SELECT_OPEN_MULTI_FILE;
 | 
			
		||||
 | 
			
		||||
  return ui::SelectFileDialog::SELECT_OPEN_FILE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ui::SelectFileDialog::FileTypeInfo GetFilterInfo(const Filters& filters) {
 | 
			
		||||
  ui::SelectFileDialog::FileTypeInfo file_type_info;
 | 
			
		||||
 | 
			
		||||
  for (const auto& [name, extension_group] : filters) {
 | 
			
		||||
    file_type_info.extension_description_overrides.push_back(
 | 
			
		||||
        base::UTF8ToUTF16(name));
 | 
			
		||||
 | 
			
		||||
    const bool has_all_files_wildcard = base::ranges::any_of(
 | 
			
		||||
        extension_group, [](const auto& ext) { return ext == "*"; });
 | 
			
		||||
    if (has_all_files_wildcard) {
 | 
			
		||||
      file_type_info.include_all_files = true;
 | 
			
		||||
    } else {
 | 
			
		||||
      file_type_info.extensions.emplace_back(extension_group);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return file_type_info;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class FileChooserDialog : public ui::SelectFileDialog::Listener {
 | 
			
		||||
 public:
 | 
			
		||||
  enum class DialogType { OPEN, SAVE };
 | 
			
		||||
 | 
			
		||||
  FileChooserDialog() { dialog_ = ui::SelectFileDialog::Create(this, nullptr); }
 | 
			
		||||
 | 
			
		||||
  ~FileChooserDialog() override = default;
 | 
			
		||||
 | 
			
		||||
  gtk::ExtraSettings GetExtraSettings(const DialogSettings& settings) {
 | 
			
		||||
    gtk::ExtraSettings extra;
 | 
			
		||||
    extra.button_label = settings.button_label;
 | 
			
		||||
    extra.show_overwrite_confirmation =
 | 
			
		||||
        settings.properties & SAVE_DIALOG_SHOW_OVERWRITE_CONFIRMATION;
 | 
			
		||||
    extra.allow_multiple_selection =
 | 
			
		||||
        settings.properties & OPEN_DIALOG_MULTI_SELECTIONS;
 | 
			
		||||
    if (type_ == DialogType::SAVE) {
 | 
			
		||||
      extra.show_hidden = settings.properties & SAVE_DIALOG_SHOW_HIDDEN_FILES;
 | 
			
		||||
    } else {
 | 
			
		||||
      extra.show_hidden = settings.properties & OPEN_DIALOG_SHOW_HIDDEN_FILES;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return extra;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void RunSaveDialogImpl(const DialogSettings& settings) {
 | 
			
		||||
    type_ = DialogType::SAVE;
 | 
			
		||||
    ui::SelectFileDialog::FileTypeInfo file_info =
 | 
			
		||||
        GetFilterInfo(settings.filters);
 | 
			
		||||
    auto extra_settings = GetExtraSettings(settings);
 | 
			
		||||
    dialog_->SelectFile(
 | 
			
		||||
        ui::SelectFileDialog::SELECT_SAVEAS_FILE,
 | 
			
		||||
        base::UTF8ToUTF16(settings.title), settings.default_path,
 | 
			
		||||
        &file_info /* file_types */, 0 /* file_type_index */,
 | 
			
		||||
        base::FilePath::StringType() /* default_extension */,
 | 
			
		||||
        settings.parent_window ? settings.parent_window->GetNativeWindow()
 | 
			
		||||
                               : nullptr,
 | 
			
		||||
        static_cast<void*>(&extra_settings));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void RunSaveDialog(gin_helper::Promise<gin_helper::Dictionary> promise,
 | 
			
		||||
                     const DialogSettings& settings) {
 | 
			
		||||
    promise_ = std::move(promise);
 | 
			
		||||
    RunSaveDialogImpl(settings);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void RunSaveDialog(base::OnceCallback<void(gin_helper::Dictionary)> callback,
 | 
			
		||||
                     const DialogSettings& settings) {
 | 
			
		||||
    callback_ = std::move(callback);
 | 
			
		||||
    RunSaveDialogImpl(settings);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void RunOpenDialogImpl(const DialogSettings& settings) {
 | 
			
		||||
    type_ = DialogType::OPEN;
 | 
			
		||||
    ui::SelectFileDialog::FileTypeInfo file_info =
 | 
			
		||||
        GetFilterInfo(settings.filters);
 | 
			
		||||
    auto extra_settings = GetExtraSettings(settings);
 | 
			
		||||
    dialog_->SelectFile(
 | 
			
		||||
        GetDialogType(settings.properties), base::UTF8ToUTF16(settings.title),
 | 
			
		||||
        settings.default_path, &file_info, 0 /* file_type_index */,
 | 
			
		||||
        base::FilePath::StringType() /* default_extension */,
 | 
			
		||||
        settings.parent_window ? settings.parent_window->GetNativeWindow()
 | 
			
		||||
                               : nullptr,
 | 
			
		||||
        static_cast<void*>(&extra_settings));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void RunOpenDialog(gin_helper::Promise<gin_helper::Dictionary> promise,
 | 
			
		||||
                     const DialogSettings& settings) {
 | 
			
		||||
    promise_ = std::move(promise);
 | 
			
		||||
    RunOpenDialogImpl(settings);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void RunOpenDialog(base::OnceCallback<void(gin_helper::Dictionary)> callback,
 | 
			
		||||
                     const DialogSettings& settings) {
 | 
			
		||||
    callback_ = std::move(callback);
 | 
			
		||||
    RunOpenDialogImpl(settings);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void FileSelected(const ui::SelectedFileInfo& file,
 | 
			
		||||
                    int index,
 | 
			
		||||
                    void* params) override {
 | 
			
		||||
    v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
 | 
			
		||||
    v8::HandleScope scope(isolate);
 | 
			
		||||
    auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
 | 
			
		||||
    dict.Set("canceled", false);
 | 
			
		||||
    if (type_ == DialogType::SAVE) {
 | 
			
		||||
      dict.Set("filePath", file.file_path);
 | 
			
		||||
    } else {
 | 
			
		||||
      dict.Set("filePaths", std::vector<base::FilePath>{file.file_path});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (callback_) {
 | 
			
		||||
      std::move(callback_).Run(dict);
 | 
			
		||||
    } else {
 | 
			
		||||
      promise_.Resolve(dict);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    delete this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void MultiFilesSelected(const std::vector<ui::SelectedFileInfo>& files,
 | 
			
		||||
                          void* params) override {
 | 
			
		||||
    v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
 | 
			
		||||
    v8::HandleScope scope(isolate);
 | 
			
		||||
    auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
 | 
			
		||||
    dict.Set("canceled", false);
 | 
			
		||||
    dict.Set("filePaths", ui::SelectedFileInfoListToFilePathList(files));
 | 
			
		||||
 | 
			
		||||
    if (callback_) {
 | 
			
		||||
      std::move(callback_).Run(dict);
 | 
			
		||||
    } else {
 | 
			
		||||
      promise_.Resolve(dict);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    delete this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void FileSelectionCanceled(void* params) override {
 | 
			
		||||
    v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
 | 
			
		||||
    v8::HandleScope scope(isolate);
 | 
			
		||||
    auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
 | 
			
		||||
    dict.Set("canceled", true);
 | 
			
		||||
    if (type_ == DialogType::SAVE) {
 | 
			
		||||
      dict.Set("filePath", base::FilePath());
 | 
			
		||||
    } else {
 | 
			
		||||
      dict.Set("filePaths", std::vector<base::FilePath>());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (callback_) {
 | 
			
		||||
      std::move(callback_).Run(dict);
 | 
			
		||||
    } else {
 | 
			
		||||
      promise_.Resolve(dict);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    delete this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 private:
 | 
			
		||||
  DialogType type_;
 | 
			
		||||
  scoped_refptr<ui::SelectFileDialog> dialog_;
 | 
			
		||||
  base::OnceCallback<void(gin_helper::Dictionary)> callback_;
 | 
			
		||||
  gin_helper::Promise<gin_helper::Dictionary> promise_;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace
 | 
			
		||||
 | 
			
		||||
bool ShowOpenDialogSync(const DialogSettings& settings,
 | 
			
		||||
                        std::vector<base::FilePath>* paths) {
 | 
			
		||||
  v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
 | 
			
		||||
  gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
 | 
			
		||||
 | 
			
		||||
  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
 | 
			
		||||
  auto cb = base::BindOnce(
 | 
			
		||||
      [](base::RepeatingClosure cb, std::vector<base::FilePath>* file_paths,
 | 
			
		||||
         gin_helper::Dictionary result) {
 | 
			
		||||
        result.Get("filePaths", file_paths);
 | 
			
		||||
        std::move(cb).Run();
 | 
			
		||||
      },
 | 
			
		||||
      run_loop.QuitClosure(), paths);
 | 
			
		||||
 | 
			
		||||
  FileChooserDialog* dialog = new FileChooserDialog();
 | 
			
		||||
  dialog->RunOpenDialog(std::move(cb), settings);
 | 
			
		||||
 | 
			
		||||
  run_loop.Run();
 | 
			
		||||
  return !paths->empty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShowOpenDialog(const DialogSettings& settings,
 | 
			
		||||
                    gin_helper::Promise<gin_helper::Dictionary> promise) {
 | 
			
		||||
  FileChooserDialog* dialog = new FileChooserDialog();
 | 
			
		||||
  dialog->RunOpenDialog(std::move(promise), settings);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path) {
 | 
			
		||||
  base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
 | 
			
		||||
  v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
 | 
			
		||||
  gin_helper::Promise<gin_helper::Dictionary> promise(isolate);
 | 
			
		||||
  auto cb = base::BindOnce(
 | 
			
		||||
      [](base::RepeatingClosure cb, base::FilePath* file_path,
 | 
			
		||||
         gin_helper::Dictionary result) {
 | 
			
		||||
        result.Get("filePath", file_path);
 | 
			
		||||
        std::move(cb).Run();
 | 
			
		||||
      },
 | 
			
		||||
      run_loop.QuitClosure(), path);
 | 
			
		||||
 | 
			
		||||
  FileChooserDialog* dialog = new FileChooserDialog();
 | 
			
		||||
  dialog->RunSaveDialog(std::move(promise), settings);
 | 
			
		||||
  run_loop.Run();
 | 
			
		||||
  return !path->empty();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShowSaveDialog(const DialogSettings& settings,
 | 
			
		||||
                    gin_helper::Promise<gin_helper::Dictionary> promise) {
 | 
			
		||||
  FileChooserDialog* dialog = new FileChooserDialog();
 | 
			
		||||
  dialog->RunSaveDialog(std::move(promise), settings);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace file_dialog
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue