chromium/ui/ozone/platform/drm/test/ui_controls_system_input_injector.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 "ui/ozone/platform/drm/test/ui_controls_system_input_injector.h"

#include "base/functional/callback.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "ui/base/test/ui_controls_aura.h"
#include "ui/display/screen.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/events/ozone/events_ozone.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/ozone/public/system_input_injector.h"

namespace {

// An implementation of UIControlsAura injecting evdev level inputs.
class UIControlsSystemInputInjector : public ui_controls::UIControlsAura {
 public:
  UIControlsSystemInputInjector() = default;

  UIControlsSystemInputInjector(const UIControlsSystemInputInjector&) = delete;
  UIControlsSystemInputInjector& operator=(
      const UIControlsSystemInputInjector&) = delete;

  ~UIControlsSystemInputInjector() override = default;

  bool SendKeyEvents(gfx::NativeWindow window,
                     ui::KeyboardCode key,
                     int key_event_types,
                     int accelerator_state) override;

  bool SendKeyEventsNotifyWhenDone(gfx::NativeWindow window,
                                   ui::KeyboardCode key,
                                   int key_event_types,
                                   base::OnceClosure closure,
                                   int accelerator_state,
                                   ui_controls::KeyEventType wait_for) override;

  bool SendMouseMove(int screen_x, int screen_y) override;

  bool SendMouseMoveNotifyWhenDone(int screen_x,
                                   int screen_y,
                                   base::OnceClosure closure) override;

  bool SendMouseEvents(ui_controls::MouseButton type,
                       int button_state,
                       int accelerator_state) override;

  bool SendMouseEventsNotifyWhenDone(ui_controls::MouseButton type,
                                     int button_state,
                                     base::OnceClosure closure,
                                     int accelerator_state) override;

  bool SendMouseClick(ui_controls::MouseButton type) override;

#if BUILDFLAG(IS_CHROMEOS)
  bool SendTouchEvents(int action, int id, int x, int y) override {
    NOTIMPLEMENTED();
    return false;
  }
  bool SendTouchEventsNotifyWhenDone(int action,
                                     int id,
                                     int x,
                                     int y,
                                     base::OnceClosure task) override {
    NOTIMPLEMENTED();
    return false;
  }
#endif

  bool ScreenToHostCoord(gfx::PointF* location);

  void InjectAcceleratorEvents(int accelerator_state, bool down);

  void EnsureInputInjector();

 private:
  std::unique_ptr<ui::SystemInputInjector> input_injector_;
  scoped_refptr<base::SingleThreadTaskRunner> user_input_task_runner_;
};

bool UIControlsSystemInputInjector::SendKeyEvents(gfx::NativeWindow window,
                                                  ui::KeyboardCode key,
                                                  int key_event_types,
                                                  int accelerator_state) {
  return SendKeyEventsNotifyWhenDone(window, key, key_event_types,
                                     base::OnceClosure(), accelerator_state,
                                     ui_controls::KeyEventType::kKeyRelease);
}

bool UIControlsSystemInputInjector::SendKeyEventsNotifyWhenDone(
    gfx::NativeWindow window,
    ui::KeyboardCode key,
    int key_event_types,
    base::OnceClosure closure,
    int accelerator_state,
    ui_controls::KeyEventType wait_for) {
  CHECK(wait_for == ui_controls::KeyEventType::kKeyPress ||
        wait_for == ui_controls::KeyEventType::kKeyRelease);

  const bool has_press = key_event_types & ui_controls::kKeyPress;
  const bool has_release = key_event_types & ui_controls::kKeyRelease;

  const ui::DomCode key_dom = ui::UsLayoutKeyboardCodeToDomCode(key);

  EnsureInputInjector();
  if (has_press) {
    InjectAcceleratorEvents(accelerator_state, true);
    input_injector_->InjectKeyEvent(key_dom, true,
                                    /*suppress_auto_repeat=*/true);
  }

  if (has_release) {
    input_injector_->InjectKeyEvent(key_dom, false,
                                    /*suppress_auto_repeat=*/true);
    InjectAcceleratorEvents(accelerator_state, false);
  }

  if (closure) {
    user_input_task_runner_->PostTask(FROM_HERE, std::move(closure));
  }

  return true;
}

bool UIControlsSystemInputInjector::SendMouseMove(int screen_x, int screen_y) {
  return SendMouseMoveNotifyWhenDone(screen_x, screen_y, base::OnceClosure());
}

bool UIControlsSystemInputInjector::SendMouseMoveNotifyWhenDone(
    int screen_x,
    int screen_y,
    base::OnceClosure closure) {
  gfx::PointF host_location(screen_x, screen_y);
  if (!ScreenToHostCoord(&host_location)) {
    return false;
  }

  EnsureInputInjector();
  input_injector_->MoveCursorTo(host_location);
  if (closure) {
    user_input_task_runner_->PostTask(FROM_HERE, std::move(closure));
  }

  return true;
}

bool UIControlsSystemInputInjector::SendMouseEvents(
    ui_controls::MouseButton type,
    int button_state,
    int accelerator_state) {
  return SendMouseEventsNotifyWhenDone(type, button_state, base::OnceClosure(),
                                       accelerator_state);
}

bool UIControlsSystemInputInjector::SendMouseEventsNotifyWhenDone(
    ui_controls::MouseButton type,
    int button_state,
    base::OnceClosure closure,
    int accelerator_state) {
  int changed_button_flag = 0;
  switch (type) {
    case ui_controls::LEFT:
      changed_button_flag = ui::EF_LEFT_MOUSE_BUTTON;
      break;
    case ui_controls::MIDDLE:
      changed_button_flag = ui::EF_MIDDLE_MOUSE_BUTTON;
      break;
    case ui_controls::RIGHT:
      changed_button_flag = ui::EF_RIGHT_MOUSE_BUTTON;
      break;
    default:
      NOTREACHED_IN_MIGRATION();
      break;
  }

  const bool has_press = button_state & ui_controls::DOWN;
  const bool has_release = button_state & ui_controls::UP;

  EnsureInputInjector();
  if (has_press) {
    InjectAcceleratorEvents(accelerator_state, true);
    input_injector_->InjectMouseButton(changed_button_flag, true);
  }

  if (has_release) {
    input_injector_->InjectMouseButton(changed_button_flag, false);
    InjectAcceleratorEvents(accelerator_state, false);
  }

  if (closure) {
    user_input_task_runner_->PostTask(FROM_HERE, std::move(closure));
  }

  return true;
}

bool UIControlsSystemInputInjector::SendMouseClick(
    ui_controls::MouseButton type) {
  return SendMouseEvents(type, ui_controls::UP | ui_controls::DOWN,
                         ui_controls::kNoAccelerator);
}

bool UIControlsSystemInputInjector::ScreenToHostCoord(gfx::PointF* location) {
  // The location needs to be in display's coordinate.
  const display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestPoint(
          gfx::ToFlooredPoint(*location));
  if (!display.is_valid()) {
    LOG(ERROR) << "Failed to find the display for " << location->ToString();
    return false;
  }
  *location -= display.bounds().OffsetFromOrigin();
  location->Scale(display.device_scale_factor());
  return true;
}

void UIControlsSystemInputInjector::InjectAcceleratorEvents(
    int accelerator_state,
    bool down) {
  const bool has_control = accelerator_state & ui_controls::kControl;
  const bool has_shift = accelerator_state & ui_controls::kShift;
  const bool has_command = accelerator_state & ui_controls::kCommand;
  const bool has_alt = accelerator_state & ui_controls::kAlt;

  if (has_control) {
    input_injector_->InjectKeyEvent(ui::DomCode::CONTROL_LEFT, down,
                                    /*suppress_auto_repeat=*/true);
  }
  if (has_shift) {
    input_injector_->InjectKeyEvent(ui::DomCode::SHIFT_LEFT, down,
                                    /*suppress_auto_repeat=*/true);
  }

  if (has_alt) {
    input_injector_->InjectKeyEvent(ui::DomCode::ALT_LEFT, down,
                                    /*suppress_auto_repeat=*/true);
  }

  if (has_command) {
    input_injector_->InjectKeyEvent(ui::DomCode::META_LEFT, down,
                                    /*suppress_auto_repeat=*/true);
  }
}

void UIControlsSystemInputInjector::EnsureInputInjector() {
  if (!input_injector_) {
    input_injector_ =
        ui::OzonePlatform::GetInstance()->CreateSystemInputInjector();
    // SystemInputInjector posts tasks to the UI thread, so we also need
    // to post closure to it.
    user_input_task_runner_ = base::SingleThreadTaskRunner::GetCurrentDefault();
  }
}

}  // namespace

namespace ui::test {

void EnableUIControlsSystemInputInjector() {
  ui_controls::InstallUIControlsAura(new UIControlsSystemInputInjector());
}

}  // namespace ui::test