// 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 #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 version; explicit FileChooserProperties(dbus::ObjectProxy* object_proxy) : dbus::PropertySet(object_proxy, kFileChooserInterfaceName, {}) { RegisterProperty("version", &version); } ~FileChooserProperties() override = default; }; base::AtomicFlag* GetAvailabilityTestCompletionFlag() { static base::NoDestructor 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 bus = base::MakeRefCounted(options); dbus_utils::CheckForServiceAndStart( bus, kXdgPortalService, base::BindOnce( [](scoped_refptr bus, base::AtomicFlag* flag, std::optional 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