fix: xdg portal version detection for file dialogs on linux (#46936)
chore: use dbus thread for portal version detection
This commit is contained in:
parent
800640ea2d
commit
cd871fd58c
5 changed files with 172 additions and 86 deletions
|
@ -35,6 +35,7 @@ filenames = {
|
||||||
"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_linux.cc",
|
"shell/browser/ui/file_dialog_linux.cc",
|
||||||
|
"shell/browser/ui/file_dialog_linux_portal.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",
|
||||||
|
|
|
@ -10,6 +10,8 @@ This CL adds support for the following features to //shell_dialogs:
|
||||||
|
|
||||||
It also:
|
It also:
|
||||||
* Changes XDG Portal implementation behavior to set default path regardless of dialog type.
|
* Changes XDG Portal implementation behavior to set default path regardless of dialog type.
|
||||||
|
* XDG Portal implementation calls into //electron to perform version checks on the dbus thread
|
||||||
|
Refs https://github.com/electron/electron/issues/46652.
|
||||||
|
|
||||||
This may be partially upstreamed to Chromium in the future.
|
This may be partially upstreamed to Chromium in the future.
|
||||||
|
|
||||||
|
@ -345,83 +347,52 @@ index 9d45ec49a4fb5e12407b65b83c1ba0c13cd0dfd8..400cce91b020ecd5e48566f125515d2c
|
||||||
+
|
+
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.cc b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.cc b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
||||||
index b6a116654ef6815e3d97dd9302d2a9930877dda8..20ebcdd46bd1570ad671c661e7f866ea0396a49e 100644
|
index b6a116654ef6815e3d97dd9302d2a9930877dda8..cdc929d9538ae22ad8b9fa4044e4fc6ee326564b 100644
|
||||||
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
||||||
+++ b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
+++ b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
||||||
@@ -7,6 +7,7 @@
|
@@ -23,6 +23,7 @@
|
||||||
#include <string_view>
|
#include "dbus/message.h"
|
||||||
|
#include "dbus/object_path.h"
|
||||||
#include "base/check.h"
|
#include "dbus/object_proxy.h"
|
||||||
+#include "base/command_line.h"
|
+#include "electron/shell/browser/ui/file_dialog.h"
|
||||||
#include "base/files/file_util.h"
|
#include "ui/aura/window_tree_host.h"
|
||||||
#include "base/functional/bind.h"
|
#include "ui/base/l10n/l10n_util.h"
|
||||||
#include "base/logging.h"
|
#include "ui/gfx/native_widget_types.h"
|
||||||
@@ -40,6 +41,8 @@ namespace {
|
@@ -94,7 +95,7 @@ void OnGetPropertyReply(dbus::Response* response) {
|
||||||
constexpr char kXdgPortalService[] = "org.freedesktop.portal.Desktop";
|
|
||||||
constexpr char kXdgPortalObject[] = "/org/freedesktop/portal/desktop";
|
|
||||||
|
|
||||||
+// Version 4 includes support for current_folder option to the OpenFile method via
|
|
||||||
+// https://github.com/flatpak/xdg-desktop-portal/commit/71165a5.
|
|
||||||
constexpr int kXdgPortalRequiredVersion = 3;
|
|
||||||
|
|
||||||
constexpr char kFileChooserInterfaceName[] =
|
|
||||||
@@ -61,6 +64,8 @@ constexpr uint32_t kFileChooserFilterKindGlob = 0;
|
|
||||||
|
|
||||||
constexpr char kFileUriPrefix[] = "file://";
|
|
||||||
|
|
||||||
+const char kXdgPortalRequiredVersionFlag[] = "xdg-portal-required-version";
|
|
||||||
+
|
|
||||||
enum class ServiceAvailability {
|
|
||||||
kNotStarted,
|
|
||||||
kInProgress,
|
|
||||||
@@ -70,6 +75,9 @@ enum class ServiceAvailability {
|
|
||||||
|
|
||||||
ServiceAvailability g_service_availability = ServiceAvailability::kNotStarted;
|
|
||||||
|
|
||||||
+uint32_t g_available_portal_version = 0;
|
|
||||||
+uint32_t g_required_portal_version = kXdgPortalRequiredVersion;
|
|
||||||
+
|
|
||||||
scoped_refptr<base::SequencedTaskRunner>& GetMainTaskRunner() {
|
|
||||||
static base::NoDestructor<scoped_refptr<base::SequencedTaskRunner>>
|
|
||||||
main_task_runner;
|
|
||||||
@@ -89,9 +97,10 @@ void OnGetPropertyReply(dbus::Response* response) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
- g_service_availability = version >= kXdgPortalRequiredVersion
|
|
||||||
+ g_service_availability = version >= g_required_portal_version
|
|
||||||
? ServiceAvailability::kAvailable
|
|
||||||
: ServiceAvailability::kNotAvailable;
|
: ServiceAvailability::kNotAvailable;
|
||||||
+ g_available_portal_version = version;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnServiceStarted(std::optional<bool> service_started) {
|
-void OnServiceStarted(std::optional<bool> service_started) {
|
||||||
@@ -159,6 +168,12 @@ void SelectFileDialogLinuxPortal::StartAvailabilityTestInBackground() {
|
+[[maybe_unused]] void OnServiceStarted(std::optional<bool> service_started) {
|
||||||
}
|
if (!service_started.value_or(false)) {
|
||||||
g_service_availability = ServiceAvailability::kInProgress;
|
g_service_availability = ServiceAvailability::kNotAvailable;
|
||||||
|
return;
|
||||||
|
@@ -161,18 +162,24 @@ void SelectFileDialogLinuxPortal::StartAvailabilityTestInBackground() {
|
||||||
|
|
||||||
+ auto* cmd = base::CommandLine::ForCurrentProcess();
|
|
||||||
+ if (!base::StringToUint(cmd->GetSwitchValueASCII(kXdgPortalRequiredVersionFlag),
|
|
||||||
+ &g_required_portal_version)) {
|
|
||||||
+ g_required_portal_version = kXdgPortalRequiredVersion;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
GetMainTaskRunner() = base::SequencedTaskRunner::GetCurrentDefault();
|
GetMainTaskRunner() = base::SequencedTaskRunner::GetCurrentDefault();
|
||||||
|
|
||||||
|
+#if 0
|
||||||
dbus_utils::CheckForServiceAndStart(dbus_thread_linux::GetSharedSessionBus(),
|
dbus_utils::CheckForServiceAndStart(dbus_thread_linux::GetSharedSessionBus(),
|
||||||
@@ -175,6 +190,11 @@ bool SelectFileDialogLinuxPortal::IsPortalAvailable() {
|
kXdgPortalService,
|
||||||
return g_service_availability == ServiceAvailability::kAvailable;
|
base::BindOnce(&OnServiceStarted));
|
||||||
|
+#endif
|
||||||
|
+ file_dialog::StartPortalAvailabilityTestInBackground();
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
bool SelectFileDialogLinuxPortal::IsPortalAvailable() {
|
||||||
|
+#if 0
|
||||||
|
if (g_service_availability == ServiceAvailability::kInProgress) {
|
||||||
|
LOG(WARNING) << "Portal availability checked before test was complete";
|
||||||
|
}
|
||||||
|
|
||||||
|
return g_service_availability == ServiceAvailability::kAvailable;
|
||||||
|
+#endif
|
||||||
|
+ return file_dialog::IsPortalAvailable();
|
||||||
}
|
}
|
||||||
|
|
||||||
+// static
|
|
||||||
+uint32_t SelectFileDialogLinuxPortal::GetPortalVersion() {
|
|
||||||
+ return g_available_portal_version;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
bool SelectFileDialogLinuxPortal::IsRunning(
|
bool SelectFileDialogLinuxPortal::IsRunning(
|
||||||
gfx::NativeWindow parent_window) const {
|
@@ -377,11 +384,14 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
|
||||||
return parent_window && host_ && host_.get() == parent_window->GetHost();
|
|
||||||
@@ -377,11 +397,14 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
|
|
||||||
const PortalFilterSet& filter_set) {
|
const PortalFilterSet& filter_set) {
|
||||||
DbusDictionary dict;
|
DbusDictionary dict;
|
||||||
|
|
||||||
|
@ -439,7 +410,7 @@ index b6a116654ef6815e3d97dd9302d2a9930877dda8..20ebcdd46bd1570ad671c661e7f866ea
|
||||||
[[fallthrough]];
|
[[fallthrough]];
|
||||||
case SelectFileDialog::SELECT_FOLDER:
|
case SelectFileDialog::SELECT_FOLDER:
|
||||||
case SelectFileDialog::Type::SELECT_EXISTING_FOLDER:
|
case SelectFileDialog::Type::SELECT_EXISTING_FOLDER:
|
||||||
@@ -394,6 +417,10 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
|
@@ -394,6 +404,10 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -450,17 +421,3 @@ index b6a116654ef6815e3d97dd9302d2a9930877dda8..20ebcdd46bd1570ad671c661e7f866ea
|
||||||
if (!default_path.empty()) {
|
if (!default_path.empty()) {
|
||||||
if (default_path_exists) {
|
if (default_path_exists) {
|
||||||
// If this is an existing directory, navigate to that directory, with no
|
// If this is an existing directory, navigate to that directory, with no
|
||||||
diff --git a/ui/shell_dialogs/select_file_dialog_linux_portal.h b/ui/shell_dialogs/select_file_dialog_linux_portal.h
|
|
||||||
index 651684b1840eaff664f3d73d99bbea40e097c866..9a9d541f1e9586d9d545f8547d3f09ff33dce48d 100644
|
|
||||||
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.h
|
|
||||||
+++ b/ui/shell_dialogs/select_file_dialog_linux_portal.h
|
|
||||||
@@ -45,6 +45,9 @@ class SelectFileDialogLinuxPortal : public SelectFileDialogLinux {
|
|
||||||
// availability test has not yet completed.
|
|
||||||
static bool IsPortalAvailable();
|
|
||||||
|
|
||||||
+ // Get version of portal if available.
|
|
||||||
+ static uint32_t GetPortalVersion();
|
|
||||||
+
|
|
||||||
protected:
|
|
||||||
~SelectFileDialogLinuxPortal() override;
|
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,16 @@ bool ShowSaveDialogSync(const DialogSettings& settings, base::FilePath* path);
|
||||||
void ShowSaveDialog(const DialogSettings& settings,
|
void ShowSaveDialog(const DialogSettings& settings,
|
||||||
gin_helper::Promise<gin_helper::Dictionary> promise);
|
gin_helper::Promise<gin_helper::Dictionary> promise);
|
||||||
|
|
||||||
|
#if BUILDFLAG(IS_LINUX)
|
||||||
|
// Rewrite of SelectFileDialogLinuxPortal equivalent functions with primary
|
||||||
|
// difference being that dbus_thread_linux::GetSharedSessionBus is not used
|
||||||
|
// so that version detection can be initiated and compeleted on the dbus thread
|
||||||
|
// Refs https://github.com/electron/electron/issues/46652
|
||||||
|
void StartPortalAvailabilityTestInBackground();
|
||||||
|
bool IsPortalAvailable();
|
||||||
|
uint32_t GetPortalVersion();
|
||||||
|
#endif
|
||||||
|
|
||||||
} // namespace file_dialog
|
} // namespace file_dialog
|
||||||
|
|
||||||
#endif // ELECTRON_SHELL_BROWSER_UI_FILE_DIALOG_H_
|
#endif // ELECTRON_SHELL_BROWSER_UI_FILE_DIALOG_H_
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
#include "shell/common/gin_helper/dictionary.h"
|
#include "shell/common/gin_helper/dictionary.h"
|
||||||
#include "shell/common/gin_helper/promise.h"
|
#include "shell/common/gin_helper/promise.h"
|
||||||
#include "ui/shell_dialogs/select_file_dialog.h"
|
#include "ui/shell_dialogs/select_file_dialog.h"
|
||||||
#include "ui/shell_dialogs/select_file_dialog_linux_portal.h"
|
|
||||||
#include "ui/shell_dialogs/select_file_policy.h"
|
#include "ui/shell_dialogs/select_file_policy.h"
|
||||||
#include "ui/shell_dialogs/selected_file_info.h"
|
#include "ui/shell_dialogs/selected_file_info.h"
|
||||||
|
|
||||||
|
@ -60,11 +59,9 @@ ui::SelectFileDialog::FileTypeInfo GetFilterInfo(const Filters& filters) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogIfNeededAboutUnsupportedPortalFeature(const DialogSettings& settings) {
|
void LogIfNeededAboutUnsupportedPortalFeature(const DialogSettings& settings) {
|
||||||
if (!settings.default_path.empty() &&
|
if (!settings.default_path.empty() && IsPortalAvailable() &&
|
||||||
ui::SelectFileDialogLinuxPortal::IsPortalAvailable() &&
|
GetPortalVersion() < 4) {
|
||||||
ui::SelectFileDialogLinuxPortal::GetPortalVersion() < 4) {
|
LOG(INFO) << "Available portal version " << GetPortalVersion()
|
||||||
LOG(INFO) << "Available portal version "
|
|
||||||
<< ui::SelectFileDialogLinuxPortal::GetPortalVersion()
|
|
||||||
<< " does not support defaultPath option, try the non-portal"
|
<< " does not support defaultPath option, try the non-portal"
|
||||||
<< " file chooser dialogs by launching with"
|
<< " file chooser dialogs by launching with"
|
||||||
<< " --xdg-portal-required-version";
|
<< " --xdg-portal-required-version";
|
||||||
|
|
121
shell/browser/ui/file_dialog_linux_portal.cc
Normal file
121
shell/browser/ui/file_dialog_linux_portal.cc
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright (c) 2025 Microsoft, GmbH.
|
||||||
|
// Use of this source code is governed by the MIT license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "shell/browser/ui/file_dialog.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "base/command_line.h"
|
||||||
|
#include "base/functional/bind.h"
|
||||||
|
#include "base/logging.h"
|
||||||
|
#include "base/memory/scoped_refptr.h"
|
||||||
|
#include "base/no_destructor.h"
|
||||||
|
#include "base/strings/string_number_conversions.h"
|
||||||
|
#include "base/synchronization/atomic_flag.h"
|
||||||
|
#include "components/dbus/thread_linux/dbus_thread_linux.h"
|
||||||
|
#include "components/dbus/utils/check_for_service_and_start.h"
|
||||||
|
#include "dbus/bus.h"
|
||||||
|
#include "dbus/object_path.h"
|
||||||
|
#include "dbus/object_proxy.h"
|
||||||
|
#include "dbus/property.h"
|
||||||
|
|
||||||
|
namespace file_dialog {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr char kXdgPortalService[] = "org.freedesktop.portal.Desktop";
|
||||||
|
constexpr char kXdgPortalObject[] = "/org/freedesktop/portal/desktop";
|
||||||
|
constexpr char kFileChooserInterfaceName[] =
|
||||||
|
"org.freedesktop.portal.FileChooser";
|
||||||
|
|
||||||
|
// Version 4 includes support for current_folder option to the OpenFile method
|
||||||
|
// via https://github.com/flatpak/xdg-desktop-portal/commit/71165a5.
|
||||||
|
uint32_t g_required_portal_version = 3;
|
||||||
|
uint32_t g_available_portal_version = 0;
|
||||||
|
constexpr char kXdgPortalRequiredVersionFlag[] = "xdg-portal-required-version";
|
||||||
|
|
||||||
|
bool g_portal_available = false;
|
||||||
|
|
||||||
|
struct FileChooserProperties : dbus::PropertySet {
|
||||||
|
dbus::Property<uint32_t> version;
|
||||||
|
|
||||||
|
explicit FileChooserProperties(dbus::ObjectProxy* object_proxy)
|
||||||
|
: dbus::PropertySet(object_proxy, kFileChooserInterfaceName, {}) {
|
||||||
|
RegisterProperty("version", &version);
|
||||||
|
}
|
||||||
|
|
||||||
|
~FileChooserProperties() override = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
base::AtomicFlag* GetAvailabilityTestCompletionFlag() {
|
||||||
|
static base::NoDestructor<base::AtomicFlag> flag;
|
||||||
|
return flag.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CheckPortalAvailabilityOnBusThread() {
|
||||||
|
auto* flag = GetAvailabilityTestCompletionFlag();
|
||||||
|
if (flag->IsSet())
|
||||||
|
return;
|
||||||
|
|
||||||
|
dbus::Bus::Options options;
|
||||||
|
options.bus_type = dbus::Bus::SESSION;
|
||||||
|
options.connection_type = dbus::Bus::PRIVATE;
|
||||||
|
options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
|
||||||
|
scoped_refptr<dbus::Bus> bus = base::MakeRefCounted<dbus::Bus>(options);
|
||||||
|
dbus_utils::CheckForServiceAndStart(
|
||||||
|
bus, kXdgPortalService,
|
||||||
|
base::BindOnce(
|
||||||
|
[](scoped_refptr<dbus::Bus> bus, base::AtomicFlag* flag,
|
||||||
|
std::optional<bool> name_has_owner) {
|
||||||
|
if (name_has_owner.value_or(false)) {
|
||||||
|
//
|
||||||
|
dbus::ObjectPath portal_path(kXdgPortalObject);
|
||||||
|
dbus::ObjectProxy* portal =
|
||||||
|
bus->GetObjectProxy(kXdgPortalService, portal_path);
|
||||||
|
FileChooserProperties properties(portal);
|
||||||
|
if (!properties.GetAndBlock(&properties.version)) {
|
||||||
|
LOG(ERROR) << "Failed to read portal version property";
|
||||||
|
} else if (properties.version.value() >=
|
||||||
|
g_required_portal_version) {
|
||||||
|
g_portal_available = true;
|
||||||
|
g_available_portal_version = properties.version.value();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VLOG(1) << "File chooser portal available: "
|
||||||
|
<< (g_portal_available ? "yes" : "no");
|
||||||
|
flag->Set();
|
||||||
|
bus->ShutdownAndBlock();
|
||||||
|
},
|
||||||
|
std::move(bus), flag));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void StartPortalAvailabilityTestInBackground() {
|
||||||
|
if (GetAvailabilityTestCompletionFlag()->IsSet())
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto* cmd = base::CommandLine::ForCurrentProcess();
|
||||||
|
if (!base::StringToUint(
|
||||||
|
cmd->GetSwitchValueASCII(kXdgPortalRequiredVersionFlag),
|
||||||
|
&g_required_portal_version)) {
|
||||||
|
VLOG(1) << "Unable to parse --xdg-portal-required-version";
|
||||||
|
}
|
||||||
|
|
||||||
|
dbus_thread_linux::GetTaskRunner()->PostTask(
|
||||||
|
FROM_HERE, base::BindOnce(&CheckPortalAvailabilityOnBusThread));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsPortalAvailable() {
|
||||||
|
if (!GetAvailabilityTestCompletionFlag()->IsSet())
|
||||||
|
LOG(WARNING) << "Portal availability checked before test was complete";
|
||||||
|
|
||||||
|
return g_portal_available;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GetPortalVersion() {
|
||||||
|
return g_available_portal_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace file_dialog
|
Loading…
Add table
Add a link
Reference in a new issue