chromium/ash/picker/picker_clipboard_insertion.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/picker/picker_clipboard_insertion.h"

#include <memory>
#include <optional>
#include <utility>

#include "ash/clipboard/clipboard_history_util.h"
#include "ash/public/cpp/clipboard_history_controller.h"
#include "ash/public/cpp/scoped_clipboard_history_pause.h"
#include "ash/root_window_controller.h"
#include "ash/shell.h"
#include "ash/wm/window_util.h"
#include "base/check.h"
#include "base/check_deref.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "build/buildflag.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/clipboard/clipboard_data.h"
#include "ui/base/clipboard/clipboard_non_backed.h"
#include "ui/base/ime/text_input_client.h"
#include "ui/events/event_constants.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
#include "ui/events/ozone/layout/xkb/xkb_keyboard_layout_engine.h"
#include "ui/events/types/event_type.h"

#if BUILDFLAG(USE_XKBCOMMON)
#include "ui/events/keycodes/xkb_keysym.h"
#endif

namespace ash {

namespace {

// Fork of ash/clipboard/clipboard_history_controller_impl.cc.

#if BUILDFLAG(USE_XKBCOMMON)
// Looks up the DomCode assigned to the keysym. In some edge cases,
// such as Dvorak layout, the original DomCode may be different
// from US standard layout.
ui::DomCode LookUpXkbDomCode(int keysym) {
  if (!base::SysInfo::IsRunningOnChromeOS()) {
    // On linux-chromeos, stub layout engine is used.
    return ui::DomCode::NONE;
  }
  ui::KeyboardLayoutEngine* layout_engine =
      ui::KeyboardLayoutEngineManager::GetKeyboardLayoutEngine();
  if (!layout_engine) {
    return ui::DomCode::NONE;
  }
  return static_cast<ui::XkbKeyboardLayoutEngine*>(layout_engine)
      ->GetDomCodeByKeysym(keysym, /*modifiers=*/std::nullopt);
}
#endif

ui::KeyEvent SyntheticCtrlV(ui::EventType type) {
  ui::DomCode dom_code = ui::DomCode::NONE;
#if BUILDFLAG(USE_XKBCOMMON)
  dom_code = LookUpXkbDomCode(XKB_KEY_v);
#endif
  return dom_code == ui::DomCode::NONE
             ? ui::KeyEvent(type, ui::VKEY_V, ui::EF_CONTROL_DOWN)
             : ui::KeyEvent(type, ui::VKEY_V, dom_code, ui::EF_CONTROL_DOWN);
}

ui::KeyEvent SyntheticCtrl(ui::EventType type) {
  int flags =
      type == ui::EventType::kKeyPressed ? ui::EF_CONTROL_DOWN : ui::EF_NONE;
  ui::DomCode dom_code = ui::DomCode::NONE;
#if BUILDFLAG(USE_XKBCOMMON)
  dom_code = LookUpXkbDomCode(XKB_KEY_Control_L);
#endif
  return dom_code == ui::DomCode::NONE
             ? ui::KeyEvent(type, ui::VKEY_CONTROL, flags)
             : ui::KeyEvent(type, ui::VKEY_CONTROL, dom_code, flags);
}

std::unique_ptr<ui::ClipboardData> ReplaceClipboard(
    std::unique_ptr<ui::ClipboardData> data) {
  // Pause changes to clipboard history while manipulating the clipboard.
  std::unique_ptr<ash::ScopedClipboardHistoryPause> pause_history =
      CHECK_DEREF(ash::ClipboardHistoryController::Get()).CreateScopedPause();
  return CHECK_DEREF(ui::ClipboardNonBacked::GetForCurrentThread())
      .WriteClipboardData(std::move(data));
}

// `intended_window` must be non-null.
// Forked from `PasteClipboardHistoryItem`.
void InsertClipboardDataImpl(aura::Window* intended_window,
                             std::unique_ptr<ui::ClipboardData> data,
                             base::OnceClosure do_web_paste,
                             base::OnceCallback<void(bool)> done_callback) {
  if (intended_window != window_util::GetActiveWindow()) {
    std::move(done_callback).Run(false);
    return;
  }

  std::unique_ptr<ui::ClipboardData> replaced_data =
      ReplaceClipboard(std::move(data));

  if (!do_web_paste.is_null()) {
    std::move(do_web_paste).Run();
  } else {
    // "Synthetic paste" using key events.
    aura::WindowTreeHost* host = intended_window->GetHost();
    CHECK(host);

    ui::KeyEvent ctrl_press = SyntheticCtrl(ui::EventType::kKeyPressed);
    host->DeliverEventToSink(&ctrl_press);

    ui::KeyEvent v_press = SyntheticCtrlV(ui::EventType::kKeyPressed);
    host->DeliverEventToSink(&v_press);

    ui::KeyEvent v_release = SyntheticCtrlV(ui::EventType::kKeyReleased);
    host->DeliverEventToSink(&v_release);

    ui::KeyEvent ctrl_release = SyntheticCtrl(ui::EventType::kKeyReleased);
    host->DeliverEventToSink(&ctrl_release);
  }

  if (!replaced_data) {
    // No was on the clipboard.
    return;
  }

  // Restore the clipboard data asynchronously. Some apps take a long time to
  // receive the paste event, and some apps will read from the clipboard
  // multiple times per paste. Wait a bit before writing `replaced_data` back to
  // the clipboard.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(
          [](std::unique_ptr<ui::ClipboardData> data,
             base::OnceCallback<void(bool)> done_callback) {
            ReplaceClipboard(std::move(data));
            std::move(done_callback).Run(true);
          },
          std::move(replaced_data), std::move(done_callback)),
      base::Milliseconds(200));
}

}  // namespace

// Forked from `ClipboardHistoryControllerImpl::MaybePastePostTask`.
void InsertClipboardData(std::unique_ptr<ui::ClipboardData> data,
                         base::OnceClosure do_web_paste,
                         base::OnceCallback<void(bool)> done_callback) {
  aura::Window* active_window = window_util::GetActiveWindow();
  if (active_window == nullptr) {
    std::move(done_callback).Run(false);
    return;
  }

  if (!do_web_paste.is_null()) {
    // `do_web_paste` needs to be called synchronously. If it is non-null, we
    // are guaranteed to not be pasting into an ARC window.
    InsertClipboardDataImpl(active_window, std::move(data),
                            std::move(do_web_paste), std::move(done_callback));
    return;
  }

  // Paste asynchronously to ensure ARC windows handle paste events correctly.
  base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&InsertClipboardDataImpl, active_window, std::move(data),
                     std::move(do_web_paste), std::move(done_callback)));
}

}  // namespace ash