blob: c487f7da19e2d05696a8eb72f2fa3e12972149f3 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_SHELL_DIALOGS_SELECT_FILE_DIALOG_LINUX_PORTAL_H_
#define UI_SHELL_DIALOGS_SELECT_FILE_DIALOG_LINUX_PORTAL_H_
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/synchronization/atomic_flag.h"
#include "base/task/sequenced_task_runner.h"
#include "dbus/bus.h"
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "ui/shell_dialogs/select_file_dialog_linux.h"
namespace ui {
using OnSelectFileExecutedCallback =
base::OnceCallback<void(std::vector<base::FilePath> paths,
std::string current_filter)>;
using OnSelectFileCanceledCallback = base::OnceCallback<void()>;
// Implementation of SelectFileDialog that has the XDG file chooser portal show
// a platform-dependent file selection dialog. This acts as a modal dialog.
class SelectFileDialogLinuxPortal : public SelectFileDialogLinux {
public:
SelectFileDialogLinuxPortal(Listener* listener,
std::unique_ptr<ui::SelectFilePolicy> policy);
SelectFileDialogLinuxPortal(const SelectFileDialogLinuxPortal& other) =
delete;
SelectFileDialogLinuxPortal& operator=(
const SelectFileDialogLinuxPortal& 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:
~SelectFileDialogLinuxPortal() 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,
const GURL* caller) 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::vector<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;
std::optional<PortalFilter> default_filter;
};
// A wrapper over some shared contextual information that needs to be passed
// around between SelectFileDialogLinuxPortal and the Portal via D-Bus.
// This is ref-counted due to sharing between the 2 sequences: dbus sequence
// and main sequence.
// Usage: SelectFileDialogLinuxPortal instantiates DialogInfo with the 2
// callbacks, sets the public members and then call SelectFileImplOnDbus().
// DialogInfo notifies the end result via one of the callbacks.
class DialogInfo : public base::RefCountedThreadSafe<DialogInfo> {
public:
DialogInfo(base::OnceClosure created_callback,
OnSelectFileExecutedCallback selected_callback,
OnSelectFileCanceledCallback canceled_callback);
// Sets up listeners for the response handle's signals.
void SelectFileImplOnBusThread(std::u16string title,
base::FilePath default_path,
const bool default_path_exists,
PortalFilterSet filter_set,
base::FilePath::StringType default_extension,
std::string parent_handle);
Type type;
// The task runner the SelectFileImpl method was called on.
scoped_refptr<base::SequencedTaskRunner> main_task_runner;
private:
friend class base::RefCountedThreadSafe<DialogInfo>;
~DialogInfo();
// Should run on D-Bus thread.
void ConnectToHandle();
void OnCallResponse(dbus::Bus* bus,
dbus::Response* response,
dbus::ErrorResponse* error_response);
void OnResponseSignalEmitted(dbus::Signal* signal);
bool CheckResponseCode(dbus::MessageReader* reader);
bool ReadResponseResults(dbus::MessageReader* reader,
std::vector<std::string>* uris,
std::string* current_filter);
void OnResponseSignalConnected(const std::string& interface,
const std::string& signal,
bool connected);
void AppendFiltersOption(dbus::MessageWriter* writer,
const std::vector<PortalFilter>& filters);
void AppendOptions(dbus::MessageWriter* writer,
const std::string& response_handle_token,
const base::FilePath& default_path,
const bool derfault_path_exists,
const PortalFilterSet& filter_set);
void AppendFilterStruct(dbus::MessageWriter* writer,
const PortalFilter& filter);
std::vector<base::FilePath> ConvertUrisToPaths(
const std::vector<std::string>& uris);
// Completes an open call, notifying the listener with the given paths, and
// marks the dialog as closed.
void CompleteOpen(std::vector<base::FilePath> paths,
std::string current_filter);
// Completes an open call, notifying the listener with a cancellation, and
// marks the dialog as closed.
void CancelOpen();
// These callbacks should run on main thread.
// It will point to SelectFileDialogPortal::DialogCreatedOnMainThread.
base::OnceClosure created_callback_;
// It will point to SelectFileDialogPortal::CompleteOpenOnMainThread.
OnSelectFileExecutedCallback selected_callback_;
// It will point to SelectFileDialogPortal::CancelOpenOnMainThread.
OnSelectFileCanceledCallback canceled_callback_;
// The response object handle that the portal will send a signal to upon the
// dialog's completion.
raw_ptr<dbus::ObjectProxy, DanglingUntriaged> response_handle_ = nullptr;
// `response_handle_` owns callbacks with methods bound to `this`. To
// prevent leaking, the callbacks are bound with weak references to `this`.
base::WeakPtrFactory<DialogInfo> weak_factory_{this};
};
// D-Bus configuration and initialization.
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(
std::u16string title,
base::FilePath default_path,
PortalFilterSet filter_set,
base::FilePath::StringType default_extension,
std::string parent_handle);
void DialogCreatedOnMainThread();
void CompleteOpenOnMainThread(std::vector<base::FilePath> paths,
std::string current_filter);
void CancelOpenOnMainThread();
// Removes the DialogInfo parent. Must be called on the UI task runner.
void UnparentOnMainThread();
// This should be used in the main thread.
base::WeakPtr<aura::WindowTreeHost> host_;
// The untyped params to pass to the listener, it should be used in the main
// thread.
raw_ptr<void> listener_params_ = nullptr;
// Data shared across main thread and D-Bus thread.
scoped_refptr<DialogInfo> info_;
// 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_;
std::vector<PortalFilter> filters_;
// Event handling on the parent window is disabled while the dialog is active
// to make the dialog modal. This closure should be run when the dialog is
// closed to reenable event handling.
base::OnceClosure reenable_window_event_handling_;
// `DialogInfo` keeps callbacks to methods bound to `this`, and `this` keeps
// a strong reference to `DialogInfo`. To prevent a reference cycle, the
// `DialogInfo` is instead passed callbacks that weakly reference `this`.
base::WeakPtrFactory<SelectFileDialogLinuxPortal> weak_factory_{this};
};
} // namespace ui
#endif // UI_SHELL_DIALOGS_SELECT_FILE_DIALOG_LINUX_PORTAL_H_