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/ui/electron_desktop_window_tree_host_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.h",
|
||||
"shell/browser/ui/gtk/menu_util.cc",
|
||||
|
|
|
@ -10,6 +10,8 @@ This CL adds support for the following features to //shell_dialogs:
|
|||
|
||||
It also:
|
||||
* 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.
|
||||
|
||||
|
@ -345,83 +347,52 @@ index 9d45ec49a4fb5e12407b65b83c1ba0c13cd0dfd8..400cce91b020ecd5e48566f125515d2c
|
|||
+
|
||||
} // 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 b6a116654ef6815e3d97dd9302d2a9930877dda8..20ebcdd46bd1570ad671c661e7f866ea0396a49e 100644
|
||||
index b6a116654ef6815e3d97dd9302d2a9930877dda8..cdc929d9538ae22ad8b9fa4044e4fc6ee326564b 100644
|
||||
--- a/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
||||
+++ b/ui/shell_dialogs/select_file_dialog_linux_portal.cc
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <string_view>
|
||||
|
||||
#include "base/check.h"
|
||||
+#include "base/command_line.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/logging.h"
|
||||
@@ -40,6 +41,8 @@ namespace {
|
||||
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
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "dbus/message.h"
|
||||
#include "dbus/object_path.h"
|
||||
#include "dbus/object_proxy.h"
|
||||
+#include "electron/shell/browser/ui/file_dialog.h"
|
||||
#include "ui/aura/window_tree_host.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/gfx/native_widget_types.h"
|
||||
@@ -94,7 +95,7 @@ void OnGetPropertyReply(dbus::Response* response) {
|
||||
: ServiceAvailability::kNotAvailable;
|
||||
+ g_available_portal_version = version;
|
||||
}
|
||||
|
||||
void OnServiceStarted(std::optional<bool> service_started) {
|
||||
@@ -159,6 +168,12 @@ void SelectFileDialogLinuxPortal::StartAvailabilityTestInBackground() {
|
||||
}
|
||||
g_service_availability = ServiceAvailability::kInProgress;
|
||||
-void OnServiceStarted(std::optional<bool> service_started) {
|
||||
+[[maybe_unused]] void OnServiceStarted(std::optional<bool> service_started) {
|
||||
if (!service_started.value_or(false)) {
|
||||
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();
|
||||
|
||||
+#if 0
|
||||
dbus_utils::CheckForServiceAndStart(dbus_thread_linux::GetSharedSessionBus(),
|
||||
@@ -175,6 +190,11 @@ bool SelectFileDialogLinuxPortal::IsPortalAvailable() {
|
||||
return g_service_availability == ServiceAvailability::kAvailable;
|
||||
kXdgPortalService,
|
||||
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(
|
||||
gfx::NativeWindow parent_window) const {
|
||||
return parent_window && host_ && host_.get() == parent_window->GetHost();
|
||||
@@ -377,11 +397,14 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
|
||||
@@ -377,11 +384,14 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
|
||||
const PortalFilterSet& filter_set) {
|
||||
DbusDictionary dict;
|
||||
|
||||
|
@ -439,7 +410,7 @@ index b6a116654ef6815e3d97dd9302d2a9930877dda8..20ebcdd46bd1570ad671c661e7f866ea
|
|||
[[fallthrough]];
|
||||
case SelectFileDialog::SELECT_FOLDER:
|
||||
case SelectFileDialog::Type::SELECT_EXISTING_FOLDER:
|
||||
@@ -394,6 +417,10 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
|
||||
@@ -394,6 +404,10 @@ DbusDictionary SelectFileDialogLinuxPortal::BuildOptionsDictionary(
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -450,17 +421,3 @@ index b6a116654ef6815e3d97dd9302d2a9930877dda8..20ebcdd46bd1570ad671c661e7f866ea
|
|||
if (!default_path.empty()) {
|
||||
if (default_path_exists) {
|
||||
// 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,
|
||||
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
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_UI_FILE_DIALOG_H_
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/promise.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/selected_file_info.h"
|
||||
|
||||
|
@ -60,11 +59,9 @@ ui::SelectFileDialog::FileTypeInfo GetFilterInfo(const Filters& filters) {
|
|||
}
|
||||
|
||||
void LogIfNeededAboutUnsupportedPortalFeature(const DialogSettings& settings) {
|
||||
if (!settings.default_path.empty() &&
|
||||
ui::SelectFileDialogLinuxPortal::IsPortalAvailable() &&
|
||||
ui::SelectFileDialogLinuxPortal::GetPortalVersion() < 4) {
|
||||
LOG(INFO) << "Available portal version "
|
||||
<< ui::SelectFileDialogLinuxPortal::GetPortalVersion()
|
||||
if (!settings.default_path.empty() && IsPortalAvailable() &&
|
||||
GetPortalVersion() < 4) {
|
||||
LOG(INFO) << "Available portal version " << GetPortalVersion()
|
||||
<< " does not support defaultPath option, try the non-portal"
|
||||
<< " file chooser dialogs by launching with"
|
||||
<< " --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