910e62b5创建于 1月15日历史提交
// 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/functional/bind.h"
#include "base/memory/raw_ptr.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/scoped_gsignal.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.
  void OnResponse(GtkWidget* dialog, int response_id);

  void RunCallback(FileChooser::Result result);
  void CleanUp();

  raw_ptr<GObject> file_dialog_ = nullptr;
  scoped_refptr<base::SequencedTaskRunner> caller_task_runner_;
  base::WeakPtr<FileChooserLinux> file_chooser_linux_;
  ScopedGSignal signal_;
};

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_.get()),
                                       false);
  signal_ =
      ScopedGSignal(GTK_WIDGET(file_dialog_.get()), "response",
                    base::BindRepeating(&GtkFileChooserOnUiThread::OnResponse,
                                        base::Unretained(this)));

#if GTK_CHECK_VERSION(3, 90, 0)
  gtk_native_dialog_show(GTK_NATIVE_DIALOG(file_dialog_.get()));
#else
  gtk_widget_show_all(GTK_WIDGET(file_dialog_.get()));
#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_.get());
#else
    gtk_widget_destroy(GTK_WIDGET(file_dialog_.get()));
#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