blob: 857b1ef9955dec5f12097f0c16a0cd4c40277011 [file] [log] [blame]
// Copyright 2019 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 "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/threading/sequence_bound.h"
#include "base/threading/sequenced_task_runner_handle.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();
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_;
DISALLOW_COPY_AND_ASSIGN(GtkFileChooserOnUiThread);
};
class FileChooserLinux : public FileChooser {
public:
FileChooserLinux(scoped_refptr<base::SequencedTaskRunner> ui_task_runner,
ResultCallback callback);
~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_;
DISALLOW_COPY_AND_ASSIGN(FileChooserLinux);
};
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)), weak_ptr_factory_(this) {
gtk_file_chooser_on_ui_thread_ =
base::SequenceBound<GtkFileChooserOnUiThread>(
ui_task_runner, base::SequencedTaskRunnerHandle::Get(),
weak_ptr_factory_.GetWeakPtr());
}
void FileChooserLinux::Show() {
gtk_file_chooser_on_ui_thread_.Post(FROM_HERE,
&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