diff --git a/AUTHORS b/AUTHORS index a7487835534c1..4ee5bc174fbf6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -930,6 +930,7 @@ Ruiyi Luo <luoruiyi2008@gmail.com> Rulong Chen <rulong.crl@alibaba-inc.com> Russell Davis <russell.davis@gmail.com> Ryan Ackley <ryanackley@gmail.com> +Ryan Gonzalez <rymg19@gmail.com> Ryan Norton <rnorton10@gmail.com> Ryan Sleevi <ryan-chromium-dev@sleevi.com> Ryan Yoakum <ryoakum@skobalt.com> diff --git a/ui/base/linux/linux_ui_delegate.cc b/ui/base/linux/linux_ui_delegate.cc index 7b0aefd431142..795da1cb03cee 100644 --- a/ui/base/linux/linux_ui_delegate.cc +++ b/ui/base/linux/linux_ui_delegate.cc @@ -27,7 +27,7 @@ LinuxUiDelegate::~LinuxUiDelegate() { instance_ = nullptr; } -bool LinuxUiDelegate::SetWidgetTransientFor( +bool LinuxUiDelegate::ExportWindowHandle( uint32_t parent_widget, base::OnceCallback<void(const std::string&)> callback) { // This function should not be called when using a platform that doesn't diff --git a/ui/base/linux/linux_ui_delegate.h b/ui/base/linux/linux_ui_delegate.h index 1ed7f90c36641..c0a9df8022c5d 100644 --- a/ui/base/linux/linux_ui_delegate.h +++ b/ui/base/linux/linux_ui_delegate.h @@ -28,7 +28,7 @@ class COMPONENT_EXPORT(UI_BASE) LinuxUiDelegate { virtual LinuxUiBackend GetBackend() const = 0; // Only implemented on Wayland. - virtual bool SetWidgetTransientFor( + virtual bool ExportWindowHandle( uint32_t parent_widget, base::OnceCallback<void(const std::string&)> callback); diff --git a/ui/gtk/BUILD.gn b/ui/gtk/BUILD.gn index 7bdb5a0e439ab..bef73bd7a6dfc 100644 --- a/ui/gtk/BUILD.gn +++ b/ui/gtk/BUILD.gn @@ -69,6 +69,8 @@ component("gtk") { "select_file_dialog_impl_gtk.cc", "select_file_dialog_impl_gtk.h", "select_file_dialog_impl_kde.cc", + "select_file_dialog_impl_portal.cc", + "select_file_dialog_impl_portal.h", "settings_provider.h", "settings_provider_gtk.cc", "settings_provider_gtk.h", @@ -90,6 +92,8 @@ component("gtk") { deps = [ ":gtk_stubs", "//base", + "//components/dbus/thread_linux", + "//dbus", "//skia", # GTK pulls pangoft2, which requires HarfBuzz symbols. When linking @@ -115,6 +119,7 @@ component("gtk") { "//ui/shell_dialogs", "//ui/strings", "//ui/views", + "//url", ] if (enable_basic_printing) { diff --git a/ui/gtk/DEPS b/ui/gtk/DEPS index 51f523c4958b0..e93e6c1eef302 100644 --- a/ui/gtk/DEPS +++ b/ui/gtk/DEPS @@ -1,5 +1,7 @@ include_rules = [ "+chrome/browser/themes/theme_properties.h", + "+components/dbus/thread_linux", + "+dbus", "+printing", "+third_party/skia", "+ui/aura", diff --git a/ui/gtk/gtk_ui.cc b/ui/gtk/gtk_ui.cc index a3302b63a5c6c..6266d03b50fd7 100644 --- a/ui/gtk/gtk_ui.cc +++ b/ui/gtk/gtk_ui.cc @@ -330,6 +330,8 @@ GtkUi::GtkUi() { auto backend = delegate ? delegate->GetBackend() : ui::LinuxUiBackend::kX11; platform_ = CreateGtkUiPlatform(backend); + SelectFileDialogImpl::Initialize(); + // Avoid GTK initializing atk-bridge, and let AuraLinux implementation // do it once it is ready. std::unique_ptr<base::Environment> env(base::Environment::Create()); @@ -346,6 +348,8 @@ GtkUi::GtkUi() { GtkUi::~GtkUi() { DCHECK_EQ(g_gtk_ui, this); g_gtk_ui = nullptr; + + SelectFileDialogImpl::Shutdown(); } // static diff --git a/ui/gtk/gtk_ui_platform.h b/ui/gtk/gtk_ui_platform.h index 8650770cd8c5b..bfd4a98f1f6a1 100644 --- a/ui/gtk/gtk_ui_platform.h +++ b/ui/gtk/gtk_ui_platform.h @@ -5,8 +5,11 @@ #ifndef UI_GTK_GTK_UI_PLATFORM_H_ #define UI_GTK_GTK_UI_PLATFORM_H_ +#include "base/callback_forward.h" #include "ui/gfx/native_widget_types.h" +#include <string> + using GdkKeymap = struct _GdkKeymap; using GtkWindow = struct _GtkWindow; using GtkWidget = struct _GtkWidget; @@ -33,6 +36,12 @@ class GtkUiPlatform { // and is supported only in X11 backend (both Aura and Ozone). virtual GdkWindow* GetGdkWindow(gfx::AcceleratedWidget window_id) = 0; + // Exports a prefixed, platform-dependent (X11 or Wayland) window handle for + // an Aura window id, then calls the given callback with the handle. + virtual bool ExportWindowHandle( + gfx::AcceleratedWidget window_id, + base::OnceCallback<void(std::string)> callback) = 0; + // Gtk dialog windows must be set transient for the browser window. This // function abstracts away such functionality. virtual bool SetGtkWidgetTransientFor(GtkWidget* widget, diff --git a/ui/gtk/native_theme_gtk_unittest.cc b/ui/gtk/native_theme_gtk_unittest.cc index a415404b07e9d..a422c2e294965 100644 --- a/ui/gtk/native_theme_gtk_unittest.cc +++ b/ui/gtk/native_theme_gtk_unittest.cc @@ -9,6 +9,7 @@ #include "base/command_line.h" #include "base/memory/ptr_util.h" #include "base/test/scoped_feature_list.h" +#include "base/test/task_environment.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/base/ui_base_features.h" #include "ui/gtk/gtk_ui_factory.h" @@ -57,6 +58,7 @@ class NativeThemeGtkRedirectedEquivalenceTest } } + base::test::TaskEnvironment task_environment_; std::unique_ptr<views::LinuxUI> gtk_ui_; }; diff --git a/ui/gtk/select_file_dialog_impl.cc b/ui/gtk/select_file_dialog_impl.cc index 36d53b986b8d3..884edef57ae5a 100644 --- a/ui/gtk/select_file_dialog_impl.cc +++ b/ui/gtk/select_file_dialog_impl.cc @@ -10,13 +10,16 @@ #include "base/files/file_util.h" #include "base/nix/xdg_util.h" #include "base/no_destructor.h" +#include "base/notreached.h" #include "base/threading/thread_restrictions.h" +#include "ui/gtk/select_file_dialog_impl_portal.h" namespace { -enum UseKdeFileDialogStatus { UNKNOWN, NO_KDE, YES_KDE }; +enum FileDialogChoice { kUnknown, kGtk, kKde, kPortal }; + +FileDialogChoice dialog_choice_ = kUnknown; -UseKdeFileDialogStatus use_kde_ = UNKNOWN; std::string& KDialogVersion() { static base::NoDestructor<std::string> version; return *version; @@ -29,42 +32,68 @@ namespace gtk { base::FilePath* SelectFileDialogImpl::last_saved_path_ = nullptr; base::FilePath* SelectFileDialogImpl::last_opened_path_ = nullptr; +// static +void SelectFileDialogImpl::Initialize() { + SelectFileDialogImplPortal::StartAvailabilityTestInBackground(); +} + +// static +void SelectFileDialogImpl::Shutdown() { + SelectFileDialogImplPortal::DestroyPortalConnection(); +} + // static ui::SelectFileDialog* SelectFileDialogImpl::Create( ui::SelectFileDialog::Listener* listener, std::unique_ptr<ui::SelectFilePolicy> policy) { - if (use_kde_ == UNKNOWN) { - // Start out assumimg we are not going to use KDE. - use_kde_ = NO_KDE; + if (dialog_choice_ == kUnknown) { + // Start out assumimg we are going to use GTK. + dialog_choice_ = kGtk; - // Check to see if KDE is the desktop environment. - std::unique_ptr<base::Environment> env(base::Environment::Create()); - base::nix::DesktopEnvironment desktop = - base::nix::GetDesktopEnvironment(env.get()); - if (desktop == base::nix::DESKTOP_ENVIRONMENT_KDE3 || - desktop == base::nix::DESKTOP_ENVIRONMENT_KDE4 || - desktop == base::nix::DESKTOP_ENVIRONMENT_KDE5) { - // Check to see if the user dislikes the KDE file dialog. - if (!env->HasVar("NO_CHROME_KDE_FILE_DIALOG")) { - // Check to see if the KDE dialog works. - if (SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread( - KDialogVersion())) { - use_kde_ = YES_KDE; + // Check to see if the portal is available. + if (SelectFileDialogImplPortal::IsPortalAvailable()) { + dialog_choice_ = kPortal; + } else { + // Make sure to kill the portal connection. + SelectFileDialogImplPortal::DestroyPortalConnection(); + + // Check to see if KDE is the desktop environment. + std::unique_ptr<base::Environment> env(base::Environment::Create()); + base::nix::DesktopEnvironment desktop = + base::nix::GetDesktopEnvironment(env.get()); + if (desktop == base::nix::DESKTOP_ENVIRONMENT_KDE3 || + desktop == base::nix::DESKTOP_ENVIRONMENT_KDE4 || + desktop == base::nix::DESKTOP_ENVIRONMENT_KDE5) { + // Check to see if the user dislikes the KDE file dialog. + if (!env->HasVar("NO_CHROME_KDE_FILE_DIALOG")) { + // Check to see if the KDE dialog works. + if (SelectFileDialogImpl::CheckKDEDialogWorksOnUIThread( + KDialogVersion())) { + dialog_choice_ = kKde; + } } } } } - if (use_kde_ == NO_KDE) { - return SelectFileDialogImpl::NewSelectFileDialogImplGTK(listener, - std::move(policy)); + switch (dialog_choice_) { + case kGtk: + return SelectFileDialogImpl::NewSelectFileDialogImplGTK( + listener, std::move(policy)); + case kPortal: + return SelectFileDialogImpl::NewSelectFileDialogImplPortal( + listener, std::move(policy)); + case kKde: { + std::unique_ptr<base::Environment> env(base::Environment::Create()); + base::nix::DesktopEnvironment desktop = + base::nix::GetDesktopEnvironment(env.get()); + return SelectFileDialogImpl::NewSelectFileDialogImplKDE( + listener, std::move(policy), desktop, KDialogVersion()); + } + case kUnknown: + NOTREACHED(); + return nullptr; } - - std::unique_ptr<base::Environment> env(base::Environment::Create()); - base::nix::DesktopEnvironment desktop = - base::nix::GetDesktopEnvironment(env.get()); - return SelectFileDialogImpl::NewSelectFileDialogImplKDE( - listener, std::move(policy), desktop, KDialogVersion()); } SelectFileDialogImpl::SelectFileDialogImpl( diff --git a/ui/gtk/select_file_dialog_impl.h b/ui/gtk/select_file_dialog_impl.h index e4698a945b783..987919abbe375 100644 --- a/ui/gtk/select_file_dialog_impl.h +++ b/ui/gtk/select_file_dialog_impl.h @@ -24,6 +24,9 @@ namespace gtk { // Shared implementation SelectFileDialog used by SelectFileDialogImplGTK class SelectFileDialogImpl : public ui::SelectFileDialog { public: + static void Initialize(); + static void Shutdown(); + // Main factory method which returns correct type. static ui::SelectFileDialog* Create( Listener* listener, @@ -39,6 +42,10 @@ class SelectFileDialogImpl : public ui::SelectFileDialog { std::unique_ptr<ui::SelectFilePolicy> policy, base::nix::DesktopEnvironment desktop, const std::string& kdialog_version); + // Factory method for creating an XDG portal-backed SelectFileDialogImpl + static SelectFileDialogImpl* NewSelectFileDialogImplPortal( + Listener* listener, + std::unique_ptr<ui::SelectFilePolicy> policy); // Returns true if the SelectFileDialog class returned by // NewSelectFileDialogImplKDE will actually work. diff --git a/ui/gtk/select_file_dialog_impl_portal.cc b/ui/gtk/select_file_dialog_impl_portal.cc new file mode 100644 index 0000000000000..89cae70162f37 --- /dev/null +++ b/ui/gtk/select_file_dialog_impl_portal.cc @@ -0,0 +1,796 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/gtk/select_file_dialog_impl_portal.h" + +#include "base/bind.h" +#include "base/containers/contains.h" +#include "base/logging.h" +#include "base/no_destructor.h" +#include "base/notreached.h" +#include "base/strings/string_piece.h" +#include "base/strings/stringprintf.h" +#include "components/dbus/thread_linux/dbus_thread_linux.h" +#include "dbus/object_path.h" +#include "dbus/property.h" +#include "ui/aura/window_tree_host.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/linux/linux_ui_delegate.h" +#include "ui/gfx/native_widget_types.h" +#include "ui/gtk/gtk_ui.h" +#include "ui/strings/grit/ui_strings.h" +#include "url/url_util.h" + +namespace gtk { + +namespace { + +constexpr char kDBusMethodNameHasOwner[] = "NameHasOwner"; +constexpr char kDBusMethodListActivatableNames[] = "ListActivatableNames"; + +constexpr char kXdgPortalService[] = "org.freedesktop.portal.Desktop"; +constexpr char kXdgPortalObject[] = "/org/freedesktop/portal/desktop"; + +constexpr int kXdgPortalRequiredVersion = 3; + +constexpr char kXdgPortalRequestInterfaceName[] = + "org.freedesktop.portal.Request"; +constexpr char kXdgPortalResponseSignal[] = "Response"; + +constexpr char kFileChooserInterfaceName[] = + "org.freedesktop.portal.FileChooser"; + +constexpr char kFileChooserMethodOpenFile[] = "OpenFile"; +constexpr char kFileChooserMethodSaveFile[] = "SaveFile"; + +constexpr char kFileChooserOptionHandleToken[] = "handle_token"; +constexpr char kFileChooserOptionAcceptLabel[] = "accept_label"; +constexpr char kFileChooserOptionMultiple[] = "multiple"; +constexpr char kFileChooserOptionDirectory[] = "directory"; +constexpr char kFileChooserOptionFilters[] = "filters"; +constexpr char kFileChooserOptionCurrentFilter[] = "current_filter"; +constexpr char kFileChooserOptionCurrentFolder[] = "current_folder"; +constexpr char kFileChooserOptionCurrentName[] = "current_name"; + +constexpr int kFileChooserFilterKindGlob = 0; + +constexpr char kFileUriPrefix[] = "file://"; + +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; +}; + +void AppendStringOption(dbus::MessageWriter* writer, + const std::string& name, + const std::string& value) { + dbus::MessageWriter option_writer(nullptr); + writer->OpenDictEntry(&option_writer); + + option_writer.AppendString(name); + option_writer.AppendVariantOfString(value); + + writer->CloseContainer(&option_writer); +} + +void AppendByteStringOption(dbus::MessageWriter* writer, + const std::string& name, + const std::string& value) { + dbus::MessageWriter option_writer(nullptr); + writer->OpenDictEntry(&option_writer); + + option_writer.AppendString(name); + + dbus::MessageWriter value_writer(nullptr); + option_writer.OpenVariant("ay", &value_writer); + + value_writer.AppendArrayOfBytes( + reinterpret_cast<const std::uint8_t*>(value.c_str()), + // size + 1 will include the null terminator. + value.size() + 1); + + option_writer.CloseContainer(&value_writer); + writer->CloseContainer(&option_writer); +} + +void AppendBoolOption(dbus::MessageWriter* writer, + const std::string& name, + bool value) { + dbus::MessageWriter option_writer(nullptr); + writer->OpenDictEntry(&option_writer); + + option_writer.AppendString(name); + option_writer.AppendVariantOfBool(value); + + writer->CloseContainer(&option_writer); +} + +} // namespace + +// static +SelectFileDialogImpl* SelectFileDialogImpl::NewSelectFileDialogImplPortal( + Listener* listener, + std::unique_ptr<ui::SelectFilePolicy> policy) { + return new SelectFileDialogImplPortal(listener, std::move(policy)); +} + +SelectFileDialogImplPortal::SelectFileDialogImplPortal( + Listener* listener, + std::unique_ptr<ui::SelectFilePolicy> policy) + : SelectFileDialogImpl(listener, std::move(policy)) {} + +SelectFileDialogImplPortal::~SelectFileDialogImplPortal() = default; + +// static +void SelectFileDialogImplPortal::StartAvailabilityTestInBackground() { + if (GetAvailabilityTestCompletionFlag()->IsSet()) + return; + + dbus_thread_linux::GetTaskRunner()->PostTask( + FROM_HERE, + base::BindOnce( + &SelectFileDialogImplPortal::CheckPortalAvailabilityOnBusThread)); +} + +// static +bool SelectFileDialogImplPortal::IsPortalAvailable() { + if (!GetAvailabilityTestCompletionFlag()->IsSet()) + LOG(WARNING) << "Portal availiability checked before test was complete"; + + return is_portal_available_; +} + +// static +void SelectFileDialogImplPortal::DestroyPortalConnection() { + dbus_thread_linux::GetTaskRunner()->PostTask( + FROM_HERE, + base::BindOnce(&SelectFileDialogImplPortal::DestroyBusOnBusThread)); +} + +bool SelectFileDialogImplPortal::IsRunning( + gfx::NativeWindow parent_window) const { + if (parent_window && parent_window->GetHost()) { + auto window = parent_window->GetHost()->GetAcceleratedWidget(); + return parents_.find(window) != parents_.end(); + } + + return false; +} + +void SelectFileDialogImplPortal::SelectFileImpl( + Type type, + const std::u16string& title, + const base::FilePath& default_path, + const FileTypeInfo* file_types, + int file_type_index, + const base::FilePath::StringType& default_extension, + gfx::NativeWindow owning_window, + void* params) { + auto info = base::MakeRefCounted<DialogInfo>(); + info->type = type; + info->main_task_runner = base::SequencedTaskRunnerHandle::Get(); + info->listener_params = params; + + if (owning_window && owning_window->GetHost()) { + info->parent = owning_window->GetHost()->GetAcceleratedWidget(); + parents_.insert(*info->parent); + } + + if (file_types) + file_types_ = *file_types; + + file_type_index_ = file_type_index; + + PortalFilterSet filter_set = BuildFilterSet(); + + auto parent_handle_callback = base::BindOnce( + &SelectFileDialogImplPortal::SelectFileImplWithParentHandle, + base::Unretained(this), + // We don't move info, because it will be needed below to cancel the open + // on error. + info, std::move(title), std::move(default_path), std::move(filter_set), + std::move(default_extension)); + + if (info->parent) { + if (!GtkUi::GetPlatform()->ExportWindowHandle( + *info->parent, std::move(parent_handle_callback))) { + LOG(ERROR) << "Failed to export window handle for portal select dialog"; + CancelOpenOnMainThread(std::move(info)); + } + } else { + // No parent, so just use a blank parent handle. + std::move(parent_handle_callback).Run(""); + } +} + +bool SelectFileDialogImplPortal::HasMultipleFileTypeChoicesImpl() { + return file_types_.extensions.size() > 1; +} + +// static +void SelectFileDialogImplPortal::CheckPortalAvailabilityOnBusThread() { + base::AtomicFlag* availability_test_complete = + GetAvailabilityTestCompletionFlag(); + if (availability_test_complete->IsSet()) + return; + + dbus::Bus* bus = AcquireBusOnBusThread(); + + dbus::ObjectProxy* dbus_proxy = + bus->GetObjectProxy(DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS)); + + if (IsPortalRunningOnBusThread(dbus_proxy) || + IsPortalActivatableOnBusThread(dbus_proxy)) { + 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() >= kXdgPortalRequiredVersion) { + is_portal_available_ = true; + } + } + + VLOG(1) << "File chooser portal available: " + << (is_portal_available_ ? "yes" : "no"); + availability_test_complete->Set(); +} + +// static +bool SelectFileDialogImplPortal::IsPortalRunningOnBusThread( + dbus::ObjectProxy* dbus_proxy) { + dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, kDBusMethodNameHasOwner); + dbus::MessageWriter writer(&method_call); + writer.AppendString(kXdgPortalService); + + std::unique_ptr<dbus::Response> response = dbus_proxy->CallMethodAndBlock( + &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); + if (!response) + return false; + + dbus::MessageReader reader(response.get()); + bool owned = false; + if (!reader.PopBool(&owned)) { + LOG(ERROR) << "Failed to read response"; + return false; + } + + return owned; +} + +// static +bool SelectFileDialogImplPortal::IsPortalActivatableOnBusThread( + dbus::ObjectProxy* dbus_proxy) { + dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, + kDBusMethodListActivatableNames); + + std::unique_ptr<dbus::Response> response = dbus_proxy->CallMethodAndBlock( + &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT); + if (!response) + return false; + + dbus::MessageReader reader(response.get()); + std::vector<std::string> names; + if (!reader.PopArrayOfStrings(&names)) { + LOG(ERROR) << "Failed to read response"; + return false; + } + + return base::Contains(names, kXdgPortalService); +} + +SelectFileDialogImplPortal::PortalFilter::PortalFilter() = default; +SelectFileDialogImplPortal::PortalFilter::PortalFilter( + const PortalFilter& other) = default; +SelectFileDialogImplPortal::PortalFilter::PortalFilter(PortalFilter&& other) = + default; +SelectFileDialogImplPortal::PortalFilter::~PortalFilter() = default; + +SelectFileDialogImplPortal::PortalFilterSet::PortalFilterSet() = default; +SelectFileDialogImplPortal::PortalFilterSet::PortalFilterSet( + const PortalFilterSet& other) = default; +SelectFileDialogImplPortal::PortalFilterSet::PortalFilterSet( + PortalFilterSet&& other) = default; +SelectFileDialogImplPortal::PortalFilterSet::~PortalFilterSet() = default; + +SelectFileDialogImplPortal::DialogInfo::DialogInfo() = default; +SelectFileDialogImplPortal::DialogInfo::~DialogInfo() = default; + +// static +scoped_refptr<dbus::Bus>* +SelectFileDialogImplPortal::AcquireBusStorageOnBusThread() { + static base::NoDestructor<scoped_refptr<dbus::Bus>> bus(nullptr); + if (!*bus) { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SESSION; + options.connection_type = dbus::Bus::PRIVATE; + options.dbus_task_runner = dbus_thread_linux::GetTaskRunner(); + + *bus = base::MakeRefCounted<dbus::Bus>(options); + } + + return bus.get(); +} + +// static +dbus::Bus* SelectFileDialogImplPortal::AcquireBusOnBusThread() { + return AcquireBusStorageOnBusThread()->get(); +} + +void SelectFileDialogImplPortal::DestroyBusOnBusThread() { + scoped_refptr<dbus::Bus>* bus_storage = AcquireBusStorageOnBusThread(); + (*bus_storage)->ShutdownAndBlock(); + + // If the connection is restarted later on, we need to make sure the entire + // bus is newly created. Otherwise, references to an old, invalid task runner + // may persist. + bus_storage->reset(); +} + +// static +base::AtomicFlag* +SelectFileDialogImplPortal::GetAvailabilityTestCompletionFlag() { + static base::NoDestructor<base::AtomicFlag> flag; + return flag.get(); +} + +SelectFileDialogImplPortal::PortalFilterSet +SelectFileDialogImplPortal::BuildFilterSet() { + PortalFilterSet filter_set; + + for (size_t i = 0; i < file_types_.extensions.size(); ++i) { + PortalFilter filter; + + for (const std::string& extension : file_types_.extensions[i]) { + if (extension.empty()) + continue; + + filter.patterns.insert(base::StringPrintf("*.%s", extension.c_str())); + } + + if (filter.patterns.empty()) + continue; + + // If there is no matching description, use a default description based on + // the filter. + if (i < file_types_.extension_description_overrides.size()) { + filter.name = + base::UTF16ToUTF8(file_types_.extension_description_overrides[i]); + } else { + std::vector<std::string> patterns_vector(filter.patterns.begin(), + filter.patterns.end()); + filter.name = base::JoinString(patterns_vector, ","); + } + + if (i == file_type_index_) + filter_set.default_filter = filter; + + filter_set.filters.push_back(std::move(filter)); + } + + if (file_types_.include_all_files && !filter_set.filters.empty()) { + // Add the *.* filter, but only if we have added other filters (otherwise it + // is implied). + PortalFilter filter; + filter.name = l10n_util::GetStringUTF8(IDS_SAVEAS_ALL_FILES); + filter.patterns.insert("*.*"); + + filter_set.filters.push_back(std::move(filter)); + } + + return filter_set; +} + +void SelectFileDialogImplPortal::SelectFileImplWithParentHandle( + scoped_refptr<DialogInfo> info, + std::u16string title, + base::FilePath default_path, + PortalFilterSet filter_set, + base::FilePath::StringType default_extension, + std::string parent_handle) { + dbus_thread_linux::GetTaskRunner()->PostTask( + FROM_HERE, + base::BindOnce(&SelectFileDialogImplPortal::SelectFileImplOnBusThread, + base::Unretained(this), std::move(info), std::move(title), + std::move(default_path), std::move(filter_set), + std::move(default_extension), std::move(parent_handle))); +} + +void SelectFileDialogImplPortal::SelectFileImplOnBusThread( + scoped_refptr<DialogInfo> info, + std::u16string title, + base::FilePath default_path, + PortalFilterSet filter_set, + base::FilePath::StringType default_extension, + std::string parent_handle) { + dbus::Bus* bus = AcquireBusOnBusThread(); + if (!bus->Connect()) + LOG(ERROR) << "Could not connect to bus for XDG portal"; + + std::string method; + switch (info->type) { + case SELECT_FOLDER: + case SELECT_UPLOAD_FOLDER: + case SELECT_EXISTING_FOLDER: + case SELECT_OPEN_FILE: + case SELECT_OPEN_MULTI_FILE: + method = kFileChooserMethodOpenFile; + break; + case SELECT_SAVEAS_FILE: + method = kFileChooserMethodSaveFile; + break; + case SELECT_NONE: + NOTREACHED(); + break; + } + + dbus::MethodCall method_call(kFileChooserInterfaceName, method); + dbus::MessageWriter writer(&method_call); + + writer.AppendString(parent_handle); + + if (!title.empty()) { + writer.AppendString(base::UTF16ToUTF8(title)); + } else { + int message_id = 0; + if (info->type == SELECT_SAVEAS_FILE) { + message_id = IDS_SAVEAS_ALL_FILES; + } else if (info->type == SELECT_OPEN_MULTI_FILE) { + message_id = IDS_OPEN_FILES_DIALOG_TITLE; + } else { + message_id = IDS_OPEN_FILE_DIALOG_TITLE; + } + writer.AppendString(l10n_util::GetStringUTF8(message_id)); + } + + std::string response_handle_token = + base::StringPrintf("handle_%d", handle_token_counter_++); + + AppendOptions(&writer, info->type, response_handle_token, default_path, + filter_set); + + // The sender part of the handle object contains the D-Bus connection name + // without the prefix colon and with all dots replaced with underscores. + std::string sender_part; + base::ReplaceChars(bus->GetConnectionName().substr(1), ".", "_", + &sender_part); + + dbus::ObjectPath expected_handle_path( + base::StringPrintf("/org/freedesktop/portal/desktop/request/%s/%s", + sender_part.c_str(), response_handle_token.c_str())); + + info->response_handle = + bus->GetObjectProxy(kXdgPortalService, expected_handle_path); + ConnectToHandle(info); + + dbus::ObjectPath portal_path(kXdgPortalObject); + dbus::ObjectProxy* portal = + bus->GetObjectProxy(kXdgPortalService, portal_path); + portal->CallMethodWithErrorResponse( + &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT, + base::BindOnce(&SelectFileDialogImplPortal::OnCallResponse, + base::Unretained(this), base::Unretained(bus), info)); +} + +void SelectFileDialogImplPortal::AppendOptions( + dbus::MessageWriter* writer, + Type type, + const std::string& response_handle_token, + const base::FilePath& default_path, + const PortalFilterSet& filter_set) { + dbus::MessageWriter options_writer(nullptr); + writer->OpenArray("{sv}", &options_writer); + + AppendStringOption(&options_writer, kFileChooserOptionHandleToken, + response_handle_token); + + if (type == SELECT_UPLOAD_FOLDER) { + AppendStringOption(&options_writer, kFileChooserOptionAcceptLabel, + l10n_util::GetStringUTF8( + IDS_SELECT_UPLOAD_FOLDER_DIALOG_UPLOAD_BUTTON)); + } + + if (type == SELECT_FOLDER || type == SELECT_UPLOAD_FOLDER || + type == SELECT_EXISTING_FOLDER) { + AppendBoolOption(&options_writer, kFileChooserOptionDirectory, true); + } else if (type == SELECT_OPEN_MULTI_FILE) { + AppendBoolOption(&options_writer, kFileChooserOptionMultiple, true); + } + + if (type == SELECT_SAVEAS_FILE && !default_path.empty()) { + if (CallDirectoryExistsOnUIThread(default_path)) { + // If this is an existing directory, navigate to that directory, with no + // filename. + AppendByteStringOption(&options_writer, kFileChooserOptionCurrentFolder, + default_path.value()); + } else { + // The default path does not exist, or is an existing file. We use + // current_folder followed by current_name, as per the recommendation of + // the GTK docs and the pattern followed by SelectFileDialogImplGTK. + AppendByteStringOption(&options_writer, kFileChooserOptionCurrentFolder, + default_path.DirName().value()); + AppendStringOption(&options_writer, kFileChooserOptionCurrentName, + default_path.BaseName().value()); + } + } + + AppendFiltersOption(&options_writer, filter_set.filters); + if (filter_set.default_filter) { + dbus::MessageWriter option_writer(nullptr); + options_writer.OpenDictEntry(&option_writer); + + option_writer.AppendString(kFileChooserOptionCurrentFilter); + + dbus::MessageWriter value_writer(nullptr); + option_writer.OpenVariant("(sa(us))", &value_writer); + + AppendFilterStruct(&value_writer, *filter_set.default_filter); + + option_writer.CloseContainer(&value_writer); + options_writer.CloseContainer(&option_writer); + } + + writer->CloseContainer(&options_writer); +} + +void SelectFileDialogImplPortal::AppendFiltersOption( + dbus::MessageWriter* writer, + const std::vector<PortalFilter>& filters) { + dbus::MessageWriter option_writer(nullptr); + writer->OpenDictEntry(&option_writer); + + option_writer.AppendString(kFileChooserOptionFilters); + + dbus::MessageWriter variant_writer(nullptr); + option_writer.OpenVariant("a(sa(us))", &variant_writer); + + dbus::MessageWriter filters_writer(nullptr); + variant_writer.OpenArray("(sa(us))", &filters_writer); + + for (const PortalFilter& filter : filters) { + AppendFilterStruct(&filters_writer, filter); + } + + variant_writer.CloseContainer(&filters_writer); + option_writer.CloseContainer(&variant_writer); + writer->CloseContainer(&option_writer); +} + +void SelectFileDialogImplPortal::AppendFilterStruct( + dbus::MessageWriter* writer, + const PortalFilter& filter) { + dbus::MessageWriter filter_writer(nullptr); + writer->OpenStruct(&filter_writer); + + filter_writer.AppendString(filter.name); + + dbus::MessageWriter patterns_writer(nullptr); + filter_writer.OpenArray("(us)", &patterns_writer); + + for (const std::string& pattern : filter.patterns) { + dbus::MessageWriter pattern_writer(nullptr); + patterns_writer.OpenStruct(&pattern_writer); + + pattern_writer.AppendUint32(kFileChooserFilterKindGlob); + pattern_writer.AppendString(pattern); + + patterns_writer.CloseContainer(&pattern_writer); + } + + filter_writer.CloseContainer(&patterns_writer); + writer->CloseContainer(&filter_writer); +} + +void SelectFileDialogImplPortal::ConnectToHandle( + scoped_refptr<DialogInfo> info) { + info->response_handle->ConnectToSignal( + kXdgPortalRequestInterfaceName, kXdgPortalResponseSignal, + base::BindRepeating(&SelectFileDialogImplPortal::OnResponseSignalEmitted, + base::Unretained(this), info), + base::BindOnce(&SelectFileDialogImplPortal::OnResponseSignalConnected, + base::Unretained(this), info)); +} + +void SelectFileDialogImplPortal::CompleteOpen( + scoped_refptr<DialogInfo> info, + std::vector<base::FilePath> paths) { + info->response_handle->Detach(); + info->main_task_runner->PostTask( + FROM_HERE, + base::BindOnce(&SelectFileDialogImplPortal::CompleteOpenOnMainThread, + base::Unretained(this), std::move(info), + std::move(paths))); +} + +void SelectFileDialogImplPortal::CancelOpen(scoped_refptr<DialogInfo> info) { + info->response_handle->Detach(); + info->main_task_runner->PostTask( + FROM_HERE, + base::BindOnce(&SelectFileDialogImplPortal::CancelOpenOnMainThread, + base::Unretained(this), std::move(info))); +} + +void SelectFileDialogImplPortal::CompleteOpenOnMainThread( + scoped_refptr<DialogInfo> info, + std::vector<base::FilePath> paths) { + UnparentOnMainThread(info.get()); + + if (listener_) { + if (info->type == SELECT_OPEN_MULTI_FILE) { + listener_->MultiFilesSelected(paths, info->listener_params); + } else if (paths.size() > 1) { + LOG(ERROR) << "Got >1 file URI from a single-file chooser"; + } else { + // The meaning of the index isn't clear, and we can't determine what + // filter was selected regardless, see select_file_dialog_impl_kde.cc. + listener_->FileSelected(paths.front(), 1, info->listener_params); + } + } +} + +void SelectFileDialogImplPortal::CancelOpenOnMainThread( + scoped_refptr<DialogInfo> info) { + UnparentOnMainThread(info.get()); + + if (listener_) + listener_->FileSelectionCanceled(info->listener_params); +} + +void SelectFileDialogImplPortal::UnparentOnMainThread(DialogInfo* info) { + if (info->parent) { + parents_.erase(*info->parent); + info->parent.reset(); + } +} + +void SelectFileDialogImplPortal::OnCallResponse( + dbus::Bus* bus, + scoped_refptr<DialogInfo> info, + dbus::Response* response, + dbus::ErrorResponse* error_response) { + if (response) { + dbus::MessageReader reader(response); + dbus::ObjectPath actual_handle_path; + if (!reader.PopObjectPath(&actual_handle_path)) { + LOG(ERROR) << "Invalid portal response"; + } else { + if (info->response_handle->object_path() != actual_handle_path) { + VLOG(1) << "Re-attaching response handle to " + << actual_handle_path.value(); + + info->response_handle->Detach(); + info->response_handle = + bus->GetObjectProxy(kXdgPortalService, actual_handle_path); + ConnectToHandle(info); + } + + // Return before the operation is cancelled. + return; + } + } else if (error_response) { + std::string error_name = error_response->GetErrorName(); + std::string error_message; + dbus::MessageReader reader(error_response); + reader.PopString(&error_message); + + LOG(ERROR) << "Portal returned error: " << error_name << ": " + << error_message; + } else { + NOTREACHED(); + } + + // All error paths end up here. + CancelOpen(std::move(info)); +} + +void SelectFileDialogImplPortal::OnResponseSignalConnected( + scoped_refptr<DialogInfo> info, + const std::string& interface, + const std::string& signal, + bool connected) { + if (!connected) { + LOG(ERROR) << "Could not connect to Response signal"; + CancelOpen(std::move(info)); + } +} + +void SelectFileDialogImplPortal::OnResponseSignalEmitted( + scoped_refptr<DialogInfo> info, + dbus::Signal* signal) { + dbus::MessageReader reader(signal); + + std::vector<std::string> uris; + if (!CheckResponseCode(&reader) || !ReadResponseResults(&reader, &uris)) { + CancelOpen(std::move(info)); + return; + } + + std::vector<base::FilePath> paths = ConvertUrisToPaths(uris); + if (!paths.empty()) + CompleteOpen(std::move(info), std::move(paths)); + else + CancelOpen(std::move(info)); +} + +bool SelectFileDialogImplPortal::CheckResponseCode( + dbus::MessageReader* reader) { + std::uint32_t response = 0; + if (!reader->PopUint32(&response)) { + LOG(ERROR) << "Failed to read response ID"; + return false; + } else if (response != 0) { + return false; + } + + return true; +} + +bool SelectFileDialogImplPortal::ReadResponseResults( + dbus::MessageReader* reader, + std::vector<std::string>* uris) { + dbus::MessageReader results_reader(nullptr); + if (!reader->PopArray(&results_reader)) { + LOG(ERROR) << "Failed to read file chooser variant"; + return false; + } + + while (results_reader.HasMoreData()) { + dbus::MessageReader entry_reader(nullptr); + std::string key; + if (!results_reader.PopDictEntry(&entry_reader) || + !entry_reader.PopString(&key)) { + LOG(ERROR) << "Failed to read response entry"; + return false; + } + + if (key == "uris") { + dbus::MessageReader uris_reader(nullptr); + if (!entry_reader.PopVariant(&uris_reader) || + !uris_reader.PopArrayOfStrings(uris)) { + LOG(ERROR) << "Failed to read response entry value"; + return false; + } + + break; + } + } + + return true; +} + +std::vector<base::FilePath> SelectFileDialogImplPortal::ConvertUrisToPaths( + const std::vector<std::string>& uris) { + std::vector<base::FilePath> paths; + for (const std::string& uri : uris) { + if (!base::StartsWith(uri, kFileUriPrefix, base::CompareCase::SENSITIVE)) { + LOG(WARNING) << "Ignoring unknown file chooser URI: " << uri; + continue; + } + + base::StringPiece encoded_path(uri); + encoded_path.remove_prefix(strlen(kFileUriPrefix)); + + url::RawCanonOutputT<char16_t> decoded_path; + url::DecodeURLEscapeSequences(encoded_path.data(), encoded_path.size(), + url::DecodeURLMode::kUTF8OrIsomorphic, + &decoded_path); + paths.emplace_back(base::UTF16ToUTF8( + base::StringPiece16(decoded_path.data(), decoded_path.length()))); + } + + return paths; +} + +bool SelectFileDialogImplPortal::is_portal_available_ = false; +int SelectFileDialogImplPortal::handle_token_counter_ = 0; + +} // namespace gtk diff --git a/ui/gtk/select_file_dialog_impl_portal.h b/ui/gtk/select_file_dialog_impl_portal.h new file mode 100644 index 0000000000000..62fce7255b2e5 --- /dev/null +++ b/ui/gtk/select_file_dialog_impl_portal.h @@ -0,0 +1,202 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_GTK_SELECT_FILE_DIALOG_IMPL_PORTAL_H_ +#define UI_GTK_SELECT_FILE_DIALOG_IMPL_PORTAL_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_refptr.h" +#include "base/sequenced_task_runner.h" +#include "base/synchronization/atomic_flag.h" +#include "dbus/bus.h" +#include "dbus/message.h" +#include "dbus/object_proxy.h" +#include "ui/gtk/select_file_dialog_impl.h" + +namespace gtk { + +// Implementation of SelectFileDialog that has the XDG file chooser portal show +// a platform-dependent file selection dialog. This acts as a modal dialog. +class SelectFileDialogImplPortal : public SelectFileDialogImpl { + public: + SelectFileDialogImplPortal(Listener* listener, + std::unique_ptr<ui::SelectFilePolicy> policy); + + SelectFileDialogImplPortal(const SelectFileDialogImplPortal& other) = delete; + SelectFileDialogImplPortal& operator=( + const SelectFileDialogImplPortal& other) = delete; + + // Starts running a test to check for the presence of the file chooser portal + // on the D-Bus task runner. This should only be called once, preferably + // around program start. + static void StartAvailabilityTestInBackground(); + + // Checks if the file chooser portal is available. Blocks if the availability + // test from above has not yet completed (which should generally not happen). + static bool IsPortalAvailable(); + + // Destroys the connection to the bus. + static void DestroyPortalConnection(); + + protected: + ~SelectFileDialogImplPortal() override; + + // BaseShellDialog implementation: + bool IsRunning(gfx::NativeWindow parent_window) const override; + + // SelectFileDialog implementation. + // |params| is user data we pass back via the Listener interface. + void SelectFileImpl(Type type, + const std::u16string& title, + const base::FilePath& default_path, + const FileTypeInfo* file_types, + int file_type_index, + const base::FilePath::StringType& default_extension, + gfx::NativeWindow owning_window, + void* params) override; + + bool HasMultipleFileTypeChoicesImpl() override; + + private: + // A named set of patterns used as a dialog filter. + struct PortalFilter { + PortalFilter(); + PortalFilter(const PortalFilter& other); + PortalFilter(PortalFilter&& other); + ~PortalFilter(); + + PortalFilter& operator=(const PortalFilter& other) = default; + PortalFilter& operator=(PortalFilter&& other) = default; + + std::string name; + std::set<std::string> patterns; + }; + + // A set of PortalFilters, potentially with a default. + struct PortalFilterSet { + PortalFilterSet(); + PortalFilterSet(const PortalFilterSet& other); + PortalFilterSet(PortalFilterSet&& other); + ~PortalFilterSet(); + + PortalFilterSet& operator=(const PortalFilterSet& other) = default; + PortalFilterSet& operator=(PortalFilterSet&& other) = default; + + std::vector<PortalFilter> filters; + absl::optional<PortalFilter> default_filter; + }; + + // A wrapper over some shared contextual information that needs to be passed + // around between various handler functions. This is ref-counted due to some + // of the locations its used in having slightly unclear or error-prone + // lifetimes. + struct DialogInfo : base::RefCountedThreadSafe<DialogInfo> { + DialogInfo(); + + // The response object handle that the portal will send a signal to upon the + // dialog's completion. + dbus::ObjectProxy* response_handle = nullptr; + absl::optional<gfx::AcceleratedWidget> parent; + Type type; + // The task runner the SelectFileImpl method was called on. + scoped_refptr<base::SequencedTaskRunner> main_task_runner; + // The untyped params to pass to the listener. + void* listener_params = nullptr; + + private: + friend class base::RefCountedThreadSafe<DialogInfo>; + + ~DialogInfo(); + }; + + static scoped_refptr<dbus::Bus>* AcquireBusStorageOnBusThread(); + static dbus::Bus* AcquireBusOnBusThread(); + + static void DestroyBusOnBusThread(); + + static void CheckPortalAvailabilityOnBusThread(); + + static bool IsPortalRunningOnBusThread(dbus::ObjectProxy* dbus_proxy); + static bool IsPortalActivatableOnBusThread(dbus::ObjectProxy* dbus_proxy); + + // Returns a flag, written by the D-Bus thread and read by the UI thread, + // indicating whether or not the availability test has completed. + static base::AtomicFlag* GetAvailabilityTestCompletionFlag(); + + PortalFilterSet BuildFilterSet(); + + void SelectFileImplWithParentHandle( + scoped_refptr<DialogInfo> info, + std::u16string title, + base::FilePath default_path, + PortalFilterSet filter_set, + base::FilePath::StringType default_extension, + std::string parent_handle); + + void SelectFileImplOnBusThread(scoped_refptr<DialogInfo> info, + std::u16string title, + base::FilePath default_path, + PortalFilterSet filter_set, + base::FilePath::StringType default_extension, + std::string parent_handle); + + void AppendOptions(dbus::MessageWriter* writer, + Type type, + const std::string& response_handle_token, + const base::FilePath& default_path, + const PortalFilterSet& filter_set); + void AppendFiltersOption(dbus::MessageWriter* writer, + const std::vector<PortalFilter>& filters); + void AppendFilterStruct(dbus::MessageWriter* writer, + const PortalFilter& filter); + + // Sets up listeners for the response handle's signals. + void ConnectToHandle(scoped_refptr<DialogInfo> info); + + // Completes an open call, notifying the listener with the given paths, and + // marks the dialog as closed. + void CompleteOpen(scoped_refptr<DialogInfo> info, + std::vector<base::FilePath> paths); + // Completes an open call, notifying the listener with a cancellation, and + // marks the dialog as closed. + void CancelOpen(scoped_refptr<DialogInfo> info); + + void CompleteOpenOnMainThread(scoped_refptr<DialogInfo> info, + std::vector<base::FilePath> paths); + void CancelOpenOnMainThread(scoped_refptr<DialogInfo> info); + + // Removes the DialogInfo parent. Must be called on the UI task runner. + void UnparentOnMainThread(DialogInfo* info); + + void OnCallResponse(dbus::Bus* bus, + scoped_refptr<DialogInfo> info, + dbus::Response* response, + dbus::ErrorResponse* error_response); + + void OnResponseSignalConnected(scoped_refptr<DialogInfo> info, + const std::string& interface, + const std::string& signal, + bool connected); + + void OnResponseSignalEmitted(scoped_refptr<DialogInfo> info, + dbus::Signal* signal); + + bool CheckResponseCode(dbus::MessageReader* reader); + bool ReadResponseResults(dbus::MessageReader* reader, + std::vector<std::string>* uris); + std::vector<base::FilePath> ConvertUrisToPaths( + const std::vector<std::string>& uris); + + std::set<gfx::AcceleratedWidget> parents_; + + // Written by the D-Bus thread and read by the UI thread. + static bool is_portal_available_; + + // Used by the D-Bus thread to generate unique handle tokens. + static int handle_token_counter_; +}; + +} // namespace gtk + +#endif // UI_GTK_SELECT_FILE_DIALOG_IMPL_PORTAL_H_ diff --git a/ui/gtk/wayland/gtk_ui_platform_wayland.cc b/ui/gtk/wayland/gtk_ui_platform_wayland.cc index d7d503d824583..05e5d59cc54ed 100644 --- a/ui/gtk/wayland/gtk_ui_platform_wayland.cc +++ b/ui/gtk/wayland/gtk_ui_platform_wayland.cc @@ -38,9 +38,9 @@ GdkWindow* GtkUiPlatformWayland::GetGdkWindow( return nullptr; } -bool GtkUiPlatformWayland::SetGtkWidgetTransientFor( - GtkWidget* widget, - gfx::AcceleratedWidget parent) { +bool GtkUiPlatformWayland::ExportWindowHandle( + gfx::AcceleratedWidget window_id, + base::OnceCallback<void(std::string)> callback) { if (!gtk::GtkCheckVersion(3, 22)) { LOG(WARNING) << "set_transient_for_exported not supported in GTK version " << gtk_get_major_version() << '.' << gtk_get_minor_version() @@ -48,8 +48,17 @@ bool GtkUiPlatformWayland::SetGtkWidgetTransientFor( return false; } - return ui::LinuxUiDelegate::GetInstance()->SetWidgetTransientFor( - parent, base::BindOnce(&GtkUiPlatformWayland::OnHandle, + return ui::LinuxUiDelegate::GetInstance()->ExportWindowHandle( + window_id, + base::BindOnce(&GtkUiPlatformWayland::OnHandleForward, + weak_factory_.GetWeakPtr(), std::move(callback))); +} + +bool GtkUiPlatformWayland::SetGtkWidgetTransientFor( + GtkWidget* widget, + gfx::AcceleratedWidget parent) { + return ui::LinuxUiDelegate::GetInstance()->ExportWindowHandle( + parent, base::BindOnce(&GtkUiPlatformWayland::OnHandleSetTransient, weak_factory_.GetWeakPtr(), widget)); } @@ -63,8 +72,8 @@ void GtkUiPlatformWayland::ShowGtkWindow(GtkWindow* window) { gtk_window_present(window); } -void GtkUiPlatformWayland::OnHandle(GtkWidget* widget, - const std::string& handle) { +void GtkUiPlatformWayland::OnHandleSetTransient(GtkWidget* widget, + const std::string& handle) { char* parent = const_cast<char*>(handle.c_str()); if (gtk::GtkCheckVersion(4)) { auto* toplevel = GlibCast<GdkToplevel>( @@ -77,6 +86,12 @@ void GtkUiPlatformWayland::OnHandle(GtkWidget* widget, } } +void GtkUiPlatformWayland::OnHandleForward( + base::OnceCallback<void(std::string)> callback, + const std::string& handle) { + std::move(callback).Run("wayland:" + handle); +} + int GtkUiPlatformWayland::GetGdkKeyState() { return ui::LinuxUiDelegate::GetInstance()->GetKeyState(); } diff --git a/ui/gtk/wayland/gtk_ui_platform_wayland.h b/ui/gtk/wayland/gtk_ui_platform_wayland.h index 41eae190208d6..4156367a1cafc 100644 --- a/ui/gtk/wayland/gtk_ui_platform_wayland.h +++ b/ui/gtk/wayland/gtk_ui_platform_wayland.h @@ -24,6 +24,9 @@ class GtkUiPlatformWayland : public GtkUiPlatform { void OnInitialized(GtkWidget* widget) override; GdkKeymap* GetGdkKeymap() override; GdkWindow* GetGdkWindow(gfx::AcceleratedWidget window_id) override; + bool ExportWindowHandle( + gfx::AcceleratedWidget window_id, + base::OnceCallback<void(std::string)> callback) override; bool SetGtkWidgetTransientFor(GtkWidget* widget, gfx::AcceleratedWidget parent) override; void ClearTransientFor(gfx::AcceleratedWidget parent) override; @@ -33,7 +36,9 @@ class GtkUiPlatformWayland : public GtkUiPlatform { private: // Called when xdg-foreign exports a parent window passed in // SetGtkWidgetTransientFor. - void OnHandle(GtkWidget* widget, const std::string& handle); + void OnHandleSetTransient(GtkWidget* widget, const std::string& handle); + void OnHandleForward(base::OnceCallback<void(std::string)> callback, + const std::string& handle); base::WeakPtrFactory<GtkUiPlatformWayland> weak_factory_{this}; }; diff --git a/ui/gtk/x/gtk_ui_platform_x11.cc b/ui/gtk/x/gtk_ui_platform_x11.cc index a61f472e7a878..6c47556fbcbba 100644 --- a/ui/gtk/x/gtk_ui_platform_x11.cc +++ b/ui/gtk/x/gtk_ui_platform_x11.cc @@ -6,6 +6,7 @@ #include "base/check.h" #include "base/environment.h" +#include "base/strings/stringprintf.h" #include "ui/base/x/x11_util.h" #include "ui/events/platform/x11/x11_event_source.h" #include "ui/gfx/native_widget_types.h" @@ -59,6 +60,13 @@ GdkWindow* GtkUiPlatformX11::GetGdkWindow(gfx::AcceleratedWidget window_id) { return gdk_window; } +bool GtkUiPlatformX11::ExportWindowHandle( + gfx::AcceleratedWidget window_id, + base::OnceCallback<void(std::string)> callback) { + std::move(callback).Run(base::StringPrintf("x11:%#x", window_id)); + return true; +} + bool GtkUiPlatformX11::SetGtkWidgetTransientFor(GtkWidget* widget, gfx::AcceleratedWidget parent) { auto x11_window = static_cast<x11::Window>( diff --git a/ui/gtk/x/gtk_ui_platform_x11.h b/ui/gtk/x/gtk_ui_platform_x11.h index e96d888f7661c..a899fd3ef4654 100644 --- a/ui/gtk/x/gtk_ui_platform_x11.h +++ b/ui/gtk/x/gtk_ui_platform_x11.h @@ -27,6 +27,9 @@ class GtkUiPlatformX11 : public GtkUiPlatform { void OnInitialized(GtkWidget* widget) override; GdkKeymap* GetGdkKeymap() override; GdkWindow* GetGdkWindow(gfx::AcceleratedWidget window_id) override; + bool ExportWindowHandle( + gfx::AcceleratedWidget window_id, + base::OnceCallback<void(std::string)> callback) override; bool SetGtkWidgetTransientFor(GtkWidget* widget, gfx::AcceleratedWidget parent) override; void ClearTransientFor(gfx::AcceleratedWidget parent) override; diff --git a/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.cc b/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.cc index 34e539bcede7f..480de9625ca31 100644 --- a/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.cc +++ b/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.cc @@ -29,7 +29,7 @@ LinuxUiBackend LinuxUiDelegateWayland::GetBackend() const { return LinuxUiBackend::kWayland; } -bool LinuxUiDelegateWayland::SetWidgetTransientFor( +bool LinuxUiDelegateWayland::ExportWindowHandle( gfx::AcceleratedWidget parent, base::OnceCallback<void(const std::string&)> callback) { auto* parent_window = diff --git a/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.h b/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.h index 3c9d9c4bb3d99..8195ffc137a78 100644 --- a/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.h +++ b/ui/ozone/platform/wayland/host/linux_ui_delegate_wayland.h @@ -19,7 +19,7 @@ class LinuxUiDelegateWayland : public LinuxUiDelegate { // LinuxUiDelegate: LinuxUiBackend GetBackend() const override; - bool SetWidgetTransientFor( + bool ExportWindowHandle( gfx::AcceleratedWidget parent, base::OnceCallback<void(const std::string&)> callback) override; int GetKeyState() override;