| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remoting/host/file_transfer/file_chooser.h" |
| |
| #include <gtk/gtk.h> |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/threading/sequence_bound.h" |
| #include "remoting/base/string_resources.h" |
| #include "ui/base/glib/glib_signal.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace remoting { |
| |
| namespace { |
| |
| class FileChooserLinux; |
| |
| class GtkFileChooserOnUiThread { |
| public: |
| GtkFileChooserOnUiThread( |
| scoped_refptr<base::SequencedTaskRunner> caller_task_runner, |
| base::WeakPtr<FileChooserLinux> file_chooser_linux); |
| |
| GtkFileChooserOnUiThread(const GtkFileChooserOnUiThread&) = delete; |
| GtkFileChooserOnUiThread& operator=(const GtkFileChooserOnUiThread&) = delete; |
| |
| ~GtkFileChooserOnUiThread(); |
| |
| void Show(); |
| |
| private: |
| // Callback for when the user responds to the Open File dialog. |
| CHROMEG_CALLBACK_1(GtkFileChooserOnUiThread, |
| void, |
| OnResponse, |
| GtkWidget*, |
| int); |
| |
| void RunCallback(FileChooser::Result result); |
| void CleanUp(); |
| |
| GObject* file_dialog_ = nullptr; |
| scoped_refptr<base::SequencedTaskRunner> caller_task_runner_; |
| base::WeakPtr<FileChooserLinux> file_chooser_linux_; |
| }; |
| |
| class FileChooserLinux : public FileChooser { |
| public: |
| FileChooserLinux(scoped_refptr<base::SequencedTaskRunner> ui_task_runner, |
| ResultCallback callback); |
| |
| FileChooserLinux(const FileChooserLinux&) = delete; |
| FileChooserLinux& operator=(const FileChooserLinux&) = delete; |
| |
| ~FileChooserLinux() override; |
| |
| // FileChooser implementation. |
| void Show() override; |
| |
| void RunCallback(FileChooser::Result result); |
| |
| private: |
| FileChooser::ResultCallback callback_; |
| base::SequenceBound<GtkFileChooserOnUiThread> gtk_file_chooser_on_ui_thread_; |
| base::WeakPtrFactory<FileChooserLinux> weak_ptr_factory_{this}; |
| }; |
| |
| GtkFileChooserOnUiThread::GtkFileChooserOnUiThread( |
| scoped_refptr<base::SequencedTaskRunner> caller_task_runner, |
| base::WeakPtr<FileChooserLinux> file_chooser_linux) |
| : caller_task_runner_(std::move(caller_task_runner)), |
| file_chooser_linux_(std::move(file_chooser_linux)) {} |
| |
| GtkFileChooserOnUiThread::~GtkFileChooserOnUiThread() { |
| // Delete the dialog if it hasn't been already. |
| CleanUp(); |
| } |
| |
| void GtkFileChooserOnUiThread::Show() { |
| #if GTK_CHECK_VERSION(3, 90, 0) |
| // GTK+ 4.0 removes the stock items for the open and cancel buttons, with the |
| // idea that one would instead use _("_Cancel") and _("_Open") directly (using |
| // gettext to pull the appropriate translated strings from the translations |
| // that ship with GTK+). To avoid needing to pull in the translated strings |
| // from GTK+ using gettext, we can just use GtkFileChooserNative (available |
| // since 3.20), and GTK+ will provide default, localized buttons. |
| file_dialog_ = G_OBJECT(gtk_file_chooser_native_new( |
| l10n_util::GetStringUTF8(IDS_DOWNLOAD_FILE_DIALOG_TITLE).c_str(), nullptr, |
| GTK_FILE_CHOOSER_ACTION_OPEN, nullptr, nullptr)); |
| #else |
| // For older versions of GTK+, we can use GtkFileChooserDialog with stock |
| // items for the buttons, and GTK+ will fetch the appropriate localized |
| // strings for us. The stock items have been deprecated since 3.10, though, so |
| // we need to suppress the warnings. |
| G_GNUC_BEGIN_IGNORE_DEPRECATIONS; |
| file_dialog_ = G_OBJECT(gtk_file_chooser_dialog_new( |
| l10n_util::GetStringUTF8(IDS_DOWNLOAD_FILE_DIALOG_TITLE).c_str(), nullptr, |
| GTK_FILE_CHOOSER_ACTION_OPEN, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, |
| GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, nullptr)); |
| G_GNUC_END_IGNORE_DEPRECATIONS; |
| #endif |
| |
| gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_dialog_), false); |
| g_signal_connect(file_dialog_, "response", G_CALLBACK(OnResponseThunk), this); |
| |
| #if GTK_CHECK_VERSION(3, 90, 0) |
| gtk_native_dialog_show(GTK_NATIVE_DIALOG(file_dialog_)); |
| #else |
| gtk_widget_show_all(GTK_WIDGET(file_dialog_)); |
| #endif |
| } |
| |
| void GtkFileChooserOnUiThread::RunCallback(FileChooser::Result result) { |
| caller_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&FileChooserLinux::RunCallback, |
| file_chooser_linux_, std::move(result))); |
| } |
| |
| void GtkFileChooserOnUiThread::CleanUp() { |
| if (file_dialog_) { |
| #if GTK_CHECK_VERSION(3, 90, 0) |
| g_object_unref(file_dialog_); |
| #else |
| gtk_widget_destroy(GTK_WIDGET(file_dialog_)); |
| #endif |
| file_dialog_ = nullptr; |
| } |
| } |
| |
| void GtkFileChooserOnUiThread::OnResponse(GtkWidget* dialog, int response_id) { |
| gchar* filename = nullptr; |
| if (response_id == GTK_RESPONSE_ACCEPT) { |
| filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); |
| } |
| |
| if (filename) { |
| RunCallback(base::FilePath(filename)); |
| g_free(filename); |
| } else { |
| RunCallback(protocol::MakeFileTransferError( |
| FROM_HERE, protocol::FileTransfer_Error_Type_CANCELED)); |
| } |
| CleanUp(); |
| } |
| |
| FileChooserLinux::FileChooserLinux( |
| scoped_refptr<base::SequencedTaskRunner> ui_task_runner, |
| ResultCallback callback) |
| : callback_(std::move(callback)) { |
| gtk_file_chooser_on_ui_thread_ = |
| base::SequenceBound<GtkFileChooserOnUiThread>( |
| ui_task_runner, base::SequencedTaskRunner::GetCurrentDefault(), |
| weak_ptr_factory_.GetWeakPtr()); |
| } |
| |
| void FileChooserLinux::Show() { |
| gtk_file_chooser_on_ui_thread_.AsyncCall(&GtkFileChooserOnUiThread::Show); |
| } |
| |
| void FileChooserLinux::RunCallback(FileChooser::Result result) { |
| std::move(callback_).Run(std::move(result)); |
| } |
| |
| FileChooserLinux::~FileChooserLinux() = default; |
| |
| } // namespace |
| |
| std::unique_ptr<FileChooser> FileChooser::Create( |
| scoped_refptr<base::SequencedTaskRunner> ui_task_runner, |
| ResultCallback callback) { |
| return std::make_unique<FileChooserLinux>(std::move(ui_task_runner), |
| std::move(callback)); |
| } |
| |
| } // namespace remoting |