Repository: electron/electron
Pull request: fix: multiple directory selection on Linux

Backport of #45373

See that PR for details.


Notes: Fixed an issue where selection of multiple directories with the `dialog` module didn't work on Linux.

Changed files:
filename: patches/chromium/feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch
patch: @@ -14,10 +14,25 @@ It also:
 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 b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a846f0d923 100644
+index b83f0177a2adb0a19be49684f865941e6708f626..a8c7032cfc122b97665c41da9e1191e747b95a33 100644
 --- a/ui/gtk/select_file_dialog_linux_gtk.cc
 +++ b/ui/gtk/select_file_dialog_linux_gtk.cc
-@@ -407,9 +407,11 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
+@@ -259,8 +259,12 @@ void SelectFileDialogLinuxGtk::SelectFileImpl(
+     case SELECT_EXISTING_FOLDER:
+       dialog = CreateSelectFolderDialog(type, title_string, default_path,
+                                         owning_window);
+-      connect("response",
+-              &SelectFileDialogLinuxGtk::OnSelectSingleFolderDialogResponse);
++      if (allow_multiple_selection())
++        connect("response",
++                &SelectFileDialogLinuxGtk::OnSelectMultiFolderDialogResponse);
++      else
++        connect("response",
++                &SelectFileDialogLinuxGtk::OnSelectSingleFolderDialogResponse);
+       break;
+     case SELECT_OPEN_FILE:
+       dialog = CreateFileOpenDialog(title_string, default_path, owning_window);
+@@ -407,9 +411,11 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
      const std::string& title,
      const base::FilePath& default_path,
      gfx::NativeWindow parent) {
@@ -30,15 +45,15 @@ index b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a8
    SetGtkTransientForAura(dialog, parent);
    AddFilters(GTK_FILE_CHOOSER(dialog));
  
-@@ -425,6 +427,7 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
+@@ -425,6 +431,7 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateFileOpenHelper(
      GtkFileChooserSetCurrentFolder(GTK_FILE_CHOOSER(dialog),
                                     *last_opened_path());
    }
 +  gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(dialog), show_hidden());
    return dialog;
  }
  
-@@ -440,11 +443,15 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
+@@ -440,11 +447,15 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
              ? l10n_util::GetStringUTF8(IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE)
              : l10n_util::GetStringUTF8(IDS_SELECT_FOLDER_DIALOG_TITLE);
    }
@@ -59,7 +74,7 @@ index b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a8
  
    GtkWidget* dialog = GtkFileChooserDialogNew(
        title_string.c_str(), nullptr, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
-@@ -466,7 +473,8 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSelectFolderDialog(
+@@ -466,7 +477,8 @@ 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);
@@ -69,7 +84,7 @@ index b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a8
    return dialog;
  }
  
-@@ -503,10 +511,11 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
+@@ -503,10 +515,11 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
    std::string title_string =
        !title.empty() ? title
                       : l10n_util::GetStringUTF8(IDS_SAVE_AS_DIALOG_TITLE);
@@ -83,7 +98,7 @@ index b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a8
        GTK_RESPONSE_ACCEPT);
    SetGtkTransientForAura(dialog, parent);
  
-@@ -532,9 +541,10 @@ GtkWidget* SelectFileDialogLinuxGtk::CreateSaveAsDialog(
+@@ -532,9 +545,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)) {
@@ -96,6 +111,65 @@ index b83f0177a2adb0a19be49684f865941e6708f626..f313c766ddc2b79f082e70138dd566a8
    return dialog;
  }
  
+@@ -589,15 +603,29 @@ void SelectFileDialogLinuxGtk::OnSelectSingleFolderDialogResponse(
+ void SelectFileDialogLinuxGtk::OnSelectMultiFileDialogResponse(
+     GtkWidget* dialog,
+     int response_id) {
++  SelectMultiFileHelper(dialog, response_id, false);
++}
++
++void SelectFileDialogLinuxGtk::OnSelectMultiFolderDialogResponse(
++    GtkWidget* dialog,
++    int response_id) {
++  SelectMultiFileHelper(dialog, response_id, true);
++}
++
++void SelectFileDialogLinuxGtk::SelectMultiFileHelper(GtkWidget* dialog,
++                                                     int response_id,
++                                                     bool allow_folder) {
+   if (IsCancelResponse(response_id)) {
+     FileNotSelected(dialog);
+     return;
+   }
+ 
+   auto filenames = GtkFileChooserGetFilenames(dialog);
+-  std::erase_if(filenames, [this](const base::FilePath& path) {
+-    return CallDirectoryExistsOnUIThread(path);
++  std::erase_if(filenames, [this, &allow_folder](const base::FilePath& path) {
++    bool directory_exists = CallDirectoryExistsOnUIThread(path);
++    return !allow_folder && directory_exists;
+   });
++
+   if (filenames.empty()) {
+     FileNotSelected(dialog);
+     return;
+diff --git a/ui/gtk/select_file_dialog_linux_gtk.h b/ui/gtk/select_file_dialog_linux_gtk.h
+index 213eaa5ec6d657a659726cb38103e8bd671fe907..f497447c598198bf690758b1d1c5c6fe4112627f 100644
+--- a/ui/gtk/select_file_dialog_linux_gtk.h
++++ b/ui/gtk/select_file_dialog_linux_gtk.h
+@@ -108,6 +108,12 @@ class SelectFileDialogLinuxGtk : public ui::SelectFileDialogLinux,
+                               gint response_id,
+                               bool allow_folder);
+ 
++  // Common function for OnSelectMultiFileDialogResponse and
++  // OnSelectMultiFolderDialogResponse.
++  void SelectMultiFileHelper(GtkWidget* dialog,
++                             gint response_id,
++                             bool allow_folder);
++
+   // Common function for CreateFileOpenDialog and CreateMultiFileOpenDialog.
+   GtkWidget* CreateFileOpenHelper(const std::string& title,
+                                   const base::FilePath& default_path,
+@@ -122,6 +128,9 @@ class SelectFileDialogLinuxGtk : public ui::SelectFileDialogLinux,
+   // Callback for when the user responds to a Open Multiple Files dialog.
+   void OnSelectMultiFileDialogResponse(GtkWidget* dialog, int response_id);
+ 
++  // Callback for when the user responds to a Select Multiple Folders dialog.
++  void OnSelectMultiFolderDialogResponse(GtkWidget* dialog, int response_id);
++
+   // Callback for when the file chooser gets destroyed.
+   void OnFileChooserDestroy(GtkWidget* dialog);
+ 
 diff --git a/ui/shell_dialogs/select_file_dialog.h b/ui/shell_dialogs/select_file_dialog.h
 index eb3d997598631b220c3566748f23a5cdac3e4692..b4b2f7294ce6e9349a4a8a05f614e93359eca25a 100644
 --- a/ui/shell_dialogs/select_file_dialog.h
@@ -186,18 +260,90 @@ index 61683d0eddb04c494ca5e650e7d556b44968ec49..5492456a9138b250e97a5479838bb443
  
  }  // namespace ui
 diff --git a/ui/shell_dialogs/select_file_dialog_linux_kde.cc b/ui/shell_dialogs/select_file_dialog_linux_kde.cc
-index 64a79ebe2e2d21d5a6b4a98042d1cdb7b6edad52..16f2ae01a8d33e6341ed52638e963c340455ebf8 100644
+index 64a79ebe2e2d21d5a6b4a98042d1cdb7b6edad52..748c2506781a237641b25b426876be14c8b7ba82 100644
 --- a/ui/shell_dialogs/select_file_dialog_linux_kde.cc
 +++ b/ui/shell_dialogs/select_file_dialog_linux_kde.cc
-@@ -468,7 +468,7 @@ void SelectFileDialogLinuxKde::CreateSelectFolderDialog(
+@@ -154,9 +154,20 @@ class SelectFileDialogLinuxKde : public SelectFileDialogLinux {
+   void OnSelectMultiFileDialogResponse(
+       gfx::AcceleratedWidget parent,
+       std::unique_ptr<KDialogOutputParams> results);
++
++  // Common function for OnSelectSingleFolderDialogResponse and
++  // OnSelectMultiFileDialogResponse.
++  void SelectMultiFileDialogHelper(
++      bool allow_folder,
++      gfx::AcceleratedWidget parent,
++      std::unique_ptr<KDialogOutputParams> results);
++
+   void OnSelectSingleFolderDialogResponse(
+       gfx::AcceleratedWidget parent,
+       std::unique_ptr<KDialogOutputParams> results);
++  void OnSelectMultiFolderDialogResponse(
++      gfx::AcceleratedWidget parent,
++      std::unique_ptr<KDialogOutputParams> results);
+ 
+   // Should be either DESKTOP_ENVIRONMENT_KDE3, KDE4, KDE5, or KDE6.
+   base::nix::DesktopEnvironment desktop_;
+@@ -461,6 +472,7 @@ void SelectFileDialogLinuxKde::CreateSelectFolderDialog(
+   int title_message_id = (type == SELECT_UPLOAD_FOLDER)
+                              ? IDS_SELECT_UPLOAD_FOLDER_DIALOG_TITLE
+                              : IDS_SELECT_FOLDER_DIALOG_TITLE;
++  bool multiple_selection = allow_multiple_selection();
+   pipe_task_runner_->PostTaskAndReplyWithResult(
+       FROM_HERE,
+       base::BindOnce(
+@@ -468,10 +480,12 @@ void SelectFileDialogLinuxKde::CreateSelectFolderDialog(
            KDialogParams(
                "--getexistingdirectory", GetTitle(title, title_message_id),
                default_path.empty() ? *last_opened_path() : default_path, parent,
 -              false, false)),
-+              false, allow_multiple_selection())),
++              false, multiple_selection)),
        base::BindOnce(
-           &SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse, this,
-           parent));
+-          &SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse, this,
+-          parent));
++          multiple_selection
++              ? &SelectFileDialogLinuxKde::OnSelectMultiFolderDialogResponse
++              : &SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse,
++          this, parent));
+ }
+ 
+ void SelectFileDialogLinuxKde::CreateFileOpenDialog(
+@@ -561,7 +575,8 @@ void SelectFileDialogLinuxKde::OnSelectSingleFolderDialogResponse(
+   SelectSingleFileHelper(true, std::move(results));
+ }
+ 
+-void SelectFileDialogLinuxKde::OnSelectMultiFileDialogResponse(
++void SelectFileDialogLinuxKde::SelectMultiFileDialogHelper(
++    bool allow_folder,
+     gfx::AcceleratedWidget parent,
+     std::unique_ptr<KDialogOutputParams> results) {
+   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+@@ -579,7 +594,7 @@ void SelectFileDialogLinuxKde::OnSelectMultiFileDialogResponse(
+        base::SplitStringPiece(results->output, "\n", base::KEEP_WHITESPACE,
+                               base::SPLIT_WANT_NONEMPTY)) {
+     base::FilePath path(line);
+-    if (CallDirectoryExistsOnUIThread(path))
++    if (!allow_folder && CallDirectoryExistsOnUIThread(path))
+       continue;
+     filenames_fp.push_back(path);
+   }
+@@ -591,4 +606,16 @@ void SelectFileDialogLinuxKde::OnSelectMultiFileDialogResponse(
+   MultiFilesSelected(filenames_fp);
+ }
+ 
++void SelectFileDialogLinuxKde::OnSelectMultiFolderDialogResponse(
++    gfx::AcceleratedWidget parent,
++    std::unique_ptr<KDialogOutputParams> results) {
++  SelectMultiFileDialogHelper(true, parent, std::move(results));
++}
++
++void SelectFileDialogLinuxKde::OnSelectMultiFileDialogResponse(
++    gfx::AcceleratedWidget parent,
++    std::unique_ptr<KDialogOutputParams> results) {
++  SelectMultiFileDialogHelper(false, parent, std::move(results));
++}
++
+ }  // namespace ui
 diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.cc b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
 index 9780c80ffff7bdb715a9adcb656e5d33be974db6..cf6edf5c68a8e2f8ffda119b58c6283bc43199c0 100644
 --- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc