refactor: use //ui/shell_dialogs
on Linux (#42109)
This commit is contained in:
parent
2fc89a6b2c
commit
72d85e90ac
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
|
# Generates headers which contain stubs for extracting function ptrs
|
||||||
# stubs for extracting function ptrs from the gtk library.
|
# from the gtk library. Function signatures for which stubs are
|
||||||
# Function signatures for which stubs are required should be
|
# required should be declared in the sig files.
|
||||||
# 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.
|
|
||||||
generate_stubs("electron_gtk_stubs") {
|
generate_stubs("electron_gtk_stubs") {
|
||||||
sigs = [
|
sigs = [ "shell/browser/ui/electron_gdk_pixbuf.sigs" ]
|
||||||
"shell/browser/ui/electron_gdk_pixbuf.sigs",
|
|
||||||
"shell/browser/ui/electron_gtk.sigs",
|
|
||||||
]
|
|
||||||
extra_header = "shell/browser/ui/electron_gtk.fragment"
|
extra_header = "shell/browser/ui/electron_gtk.fragment"
|
||||||
output_name = "electron_gtk_stubs"
|
output_name = "electron_gtk_stubs"
|
||||||
public_deps = [ "//ui/gtk:gtk_config" ]
|
public_deps = [ "//ui/gtk:gtk_config" ]
|
||||||
|
|
|
@ -34,7 +34,7 @@ filenames = {
|
||||||
"shell/browser/notifications/linux/notification_presenter_linux.h",
|
"shell/browser/notifications/linux/notification_presenter_linux.h",
|
||||||
"shell/browser/relauncher_linux.cc",
|
"shell/browser/relauncher_linux.cc",
|
||||||
"shell/browser/ui/electron_desktop_window_tree_host_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.cc",
|
||||||
"shell/browser/ui/gtk/menu_gtk.h",
|
"shell/browser/ui/gtk/menu_gtk.h",
|
||||||
"shell/browser/ui/gtk/menu_util.cc",
|
"shell/browser/ui/gtk/menu_util.cc",
|
||||||
|
|
|
@ -129,3 +129,4 @@ fix_add_support_for_skipping_first_2_no-op_refreshes_in_thumb_cap.patch
|
||||||
refactor_expose_file_system_access_blocklist.patch
|
refactor_expose_file_system_access_blocklist.patch
|
||||||
revert_power_update_trace_counter_in_power_monitor.patch
|
revert_power_update_trace_counter_in_power_monitor.patch
|
||||||
cherry-pick-b2cc7b7ac538.patch
|
cherry-pick-b2cc7b7ac538.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 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
From: deepak1556 <hop2deep@gmail.com>
|
From: deepak1556 <hop2deep@gmail.com>
|
||||||
Date: Thu, 7 Apr 2022 20:30:16 +0900
|
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
|
Allows embedders to get a handle to the gdk_pixbuf
|
||||||
gdk_pixbuf libraries already loaded in the process.
|
library already loaded in the process.
|
||||||
|
|
||||||
diff --git a/ui/gtk/gtk_compat.cc b/ui/gtk/gtk_compat.cc
|
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
|
--- a/ui/gtk/gtk_compat.cc
|
||||||
+++ b/ui/gtk/gtk_compat.cc
|
+++ b/ui/gtk/gtk_compat.cc
|
||||||
@@ -66,11 +66,6 @@ void* GetLibGio() {
|
@@ -66,11 +66,6 @@ void* GetLibGio() {
|
||||||
|
@ -22,20 +22,7 @@ index 3a4b856ec5c2f5c3ede6e8f6db7858498b737702..a308249c66c01856df84e64bda0411db
|
||||||
void* GetLibGdk3() {
|
void* GetLibGdk3() {
|
||||||
static void* libgdk3 = DlOpen("libgdk-3.so.0");
|
static void* libgdk3 = DlOpen("libgdk-3.so.0");
|
||||||
return libgdk3;
|
return libgdk3;
|
||||||
@@ -86,12 +81,6 @@ void* GetLibGtk4(bool check = true) {
|
@@ -134,6 +129,11 @@ gfx::Insets InsetsFromGtkBorder(const GtkBorder& border) {
|
||||||
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) {
|
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
@ -43,29 +30,20 @@ index 3a4b856ec5c2f5c3ede6e8f6db7858498b737702..a308249c66c01856df84e64bda0411db
|
||||||
+ static void* libgdk_pixbuf = DlOpen("libgdk_pixbuf-2.0.so.0");
|
+ static void* libgdk_pixbuf = DlOpen("libgdk_pixbuf-2.0.so.0");
|
||||||
+ return libgdk_pixbuf;
|
+ return libgdk_pixbuf;
|
||||||
+}
|
+}
|
||||||
+
|
|
||||||
+void* GetLibGtk() {
|
|
||||||
+ if (GtkCheckVersion(4))
|
|
||||||
+ return GetLibGtk4();
|
|
||||||
+ return GetLibGtk3();
|
|
||||||
+}
|
|
||||||
+
|
+
|
||||||
bool LoadGtk() {
|
bool LoadGtk() {
|
||||||
static bool loaded = LoadGtkImpl();
|
static bool loaded = LoadGtkImpl();
|
||||||
return loaded;
|
return loaded;
|
||||||
diff --git a/ui/gtk/gtk_compat.h b/ui/gtk/gtk_compat.h
|
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
|
--- a/ui/gtk/gtk_compat.h
|
||||||
+++ b/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 {
|
namespace gtk {
|
||||||
|
|
||||||
+// Get handle to the currently loaded gdk_pixbuf library in the process.
|
+// Get handle to the currently loaded gdk_pixbuf library in the process.
|
||||||
+void* GetLibGdkPixbuf();
|
+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.
|
// Loads libgtk and related libraries and returns true on success.
|
||||||
bool LoadGtk();
|
bool LoadGtk();
|
||||||
|
|
|
@ -398,12 +398,6 @@ void ElectronBrowserMainParts::ToolkitInitialized() {
|
||||||
CHECK(linux_ui);
|
CHECK(linux_ui);
|
||||||
linux_ui_getter_ = std::make_unique<LinuxUiGetterImpl>();
|
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());
|
electron::InitializeElectron_gdk_pixbuf(gtk::GetLibGdkPixbuf());
|
||||||
CHECK(electron::IsElectron_gdk_pixbufInitialized())
|
CHECK(electron::IsElectron_gdk_pixbufInitialized())
|
||||||
<< "Failed to initialize libgdk_pixbuf-2.0.so.0";
|
<< "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…
Reference in a new issue