blob: 13a8f7b147a89812aa9420c46928be06e89e9964 [file] [log] [blame]
// Copyright 2023 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/events/peripheral_customization_event_rewriter.h"
#include <linux/input.h>
#include <algorithm>
#include <memory>
#include <variant>
#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/constants/ash_features.h"
#include "ash/public/cpp/accelerator_actions.h"
#include "ash/public/cpp/accelerator_keycode_lookup_cache.h"
#include "ash/public/cpp/input_device_settings_controller.h"
#include "ash/public/cpp/test/mock_input_device_settings_controller.h"
#include "ash/public/mojom/input_device_settings.mojom-shared.h"
#include "ash/public/mojom/input_device_settings.mojom.h"
#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "base/containers/adapters.h"
#include "base/containers/flat_map.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "ui/events/base_event_utils.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_sink.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/dom_key.h"
#include "ui/events/keycodes/keyboard_codes_posix.h"
#include "ui/events/ozone/evdev/mouse_button_property.h"
#include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
#include "ui/events/ozone/layout/scoped_keyboard_layout_engine.h"
#include "ui/events/ozone/layout/stub/stub_keyboard_layout_engine.h"
#include "ui/events/test/test_event_rewriter_continuation.h"
#include "ui/events/test/test_event_source.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/geometry/vector2d_f.h"
namespace ash {
namespace {
constexpr int kMouseDeviceId = 1;
constexpr int kGraphicsTabletDeviceId = 2;
constexpr int kRandomKeyboardDeviceId = 3;
class TestEventSink : public ui::EventSink {
public:
TestEventSink() = default;
TestEventSink(const TestEventSink&) = delete;
TestEventSink& operator=(const TestEventSink&) = delete;
~TestEventSink() override = default;
// Returns the recorded events.
std::vector<std::unique_ptr<ui::Event>> TakeEvents() {
return std::move(events_);
}
// ui::EventSink:
ui::EventDispatchDetails OnEventFromSource(ui::Event* event) override {
events_.emplace_back(event->Clone());
return ui::EventDispatchDetails();
}
private:
std::vector<std::unique_ptr<ui::Event>> events_;
};
struct TestKeyEvent {
ui::EventType type;
ui::DomCode code;
ui::DomKey key;
ui::KeyboardCode keycode;
ui::EventFlags flags = ui::EF_NONE;
bool operator==(const TestKeyEvent&) const = default;
};
struct TestMouseEvent {
ui::EventType type;
ui::EventFlags flags;
ui::EventFlags changed_button_flags;
uint32_t linux_key_code;
bool operator==(const TestMouseEvent&) const = default;
};
struct TestMouseScrollEvent {
bool direction;
ui::EventFlags flags;
bool operator==(const TestMouseScrollEvent&) const = default;
};
using TestEventVariant =
std::variant<TestKeyEvent, TestMouseEvent, TestMouseScrollEvent>;
std::string ConvertToString(const TestMouseScrollEvent& mouse_scroll_event) {
std::string direction_name = mouse_scroll_event.direction ? "Left" : "Right";
return base::StringPrintf("MouseScrollEvent direction=%s",
direction_name.c_str());
}
std::string ConvertToString(const TestMouseEvent& mouse_event) {
std::string flags_name =
base::JoinString(ui::EventFlagsNames(mouse_event.flags), "|");
std::string changed_button_flags_name = base::JoinString(
ui::EventFlagsNames(mouse_event.changed_button_flags), "|");
return base::StringPrintf(
"MouseEvent type=%d flags=%s(0x%X) changed_button_flags=%s(0x%X)",
mouse_event.type, flags_name.c_str(), mouse_event.flags,
changed_button_flags_name.c_str(), mouse_event.changed_button_flags);
}
std::string ConvertToString(const TestKeyEvent& key_event) {
std::string flags_name =
base::JoinString(ui::EventFlagsNames(key_event.flags), "|");
return base::StringPrintf(
"KeyboardEvent type=%d code=%s(0x%06X) flags=%s(0x%X) vk=0x%02X "
"key=%s(0x%08X)",
key_event.type,
ui::KeycodeConverter::DomCodeToCodeString(key_event.code).c_str(),
static_cast<uint32_t>(key_event.code), flags_name.c_str(),
key_event.flags, key_event.keycode,
ui::KeycodeConverter::DomKeyToKeyString(key_event.key).c_str(),
static_cast<uint32_t>(key_event.key));
}
std::string ConvertToString(const TestEventVariant& event) {
return std::visit([](auto&& event) { return ConvertToString(event); }, event);
}
inline std::ostream& operator<<(std::ostream& os,
const TestEventVariant& event) {
return os << ConvertToString(event);
}
// Factory template of TestKeyEvents just to reduce a lot of code/data
// duplication.
template <ui::DomCode code,
ui::DomKey::Base key,
ui::KeyboardCode keycode,
ui::EventFlags modifier_flag = ui::EF_NONE,
ui::DomKey::Base shifted_key = key>
struct TestKey {
// Returns press key event.
static constexpr TestKeyEvent Pressed(ui::EventFlags flags = ui::EF_NONE) {
return {ui::ET_KEY_PRESSED, code,
(flags & ui::EF_SHIFT_DOWN) ? shifted_key : key, keycode,
flags | modifier_flag};
}
// Returns release key event.
static constexpr TestKeyEvent Released(ui::EventFlags flags = ui::EF_NONE) {
// Note: modifier flag should not be present on release events.
return {ui::ET_KEY_RELEASED, code,
(flags & ui::EF_SHIFT_DOWN) ? shifted_key : key, keycode, flags};
}
// Returns press then release key events.
static std::vector<TestEventVariant> Typed(
ui::EventFlags flags = ui::EF_NONE) {
return {Pressed(flags), Released(flags)};
}
};
// Short cut of TestKey construction for Character keys.
template <ui::DomCode code,
char key,
ui::KeyboardCode keycode,
char shifted_key = key>
using TestCharKey = TestKey<code,
ui::DomKey::Constant<key>::Character,
keycode,
ui::EF_NONE,
ui::DomKey::Constant<shifted_key>::Character>;
template <ui::EventFlags changed_button_flag, uint32_t linux_key_code = 0>
struct TestButton {
// Returns press button event.
static constexpr TestMouseEvent Pressed(ui::EventFlags flags = ui::EF_NONE) {
return {ui::ET_MOUSE_PRESSED, flags | changed_button_flag,
changed_button_flag, linux_key_code};
}
// Returns release button events.
static constexpr TestMouseEvent Released(ui::EventFlags flags = ui::EF_NONE) {
return {ui::ET_MOUSE_RELEASED, flags | changed_button_flag,
changed_button_flag, linux_key_code};
}
// Returns press then release button events.
static std::vector<TestEventVariant> Typed(
ui::EventFlags flags = ui::EF_NONE) {
return {Pressed(flags), Released(flags)};
}
};
template <bool direction>
struct TestScroll {
// Returns scroll event.
static constexpr std::vector<TestEventVariant> Typed(
ui::EventFlags flags = ui::EF_NONE) {
return std::vector<TestEventVariant>{
TestMouseScrollEvent{direction, flags}};
}
};
using ButtonLeft = TestButton<ui::EF_LEFT_MOUSE_BUTTON>;
using ButtonRight = TestButton<ui::EF_RIGHT_MOUSE_BUTTON>;
using ButtonMiddle = TestButton<ui::EF_MIDDLE_MOUSE_BUTTON>;
using ButtonForward = TestButton<ui::EF_FORWARD_MOUSE_BUTTON, BTN_FORWARD>;
using ButtonBack = TestButton<ui::EF_BACK_MOUSE_BUTTON, BTN_BACK>;
using ButtonExtra = TestButton<ui::EF_FORWARD_MOUSE_BUTTON, BTN_EXTRA>;
using ButtonSide = TestButton<ui::EF_BACK_MOUSE_BUTTON, BTN_SIDE>;
using ScrollLeft = TestScroll<true>;
using ScrollRight = TestScroll<false>;
using KeyA = TestCharKey<ui::DomCode::US_A, 'a', ui::VKEY_A, 'A'>;
using KeyB = TestCharKey<ui::DomCode::US_B, 'b', ui::VKEY_B, 'B'>;
using KeyC = TestCharKey<ui::DomCode::US_C, 'c', ui::VKEY_C, 'C'>;
using KeyD = TestCharKey<ui::DomCode::US_D, 'd', ui::VKEY_D, 'D'>;
using KeyM = TestCharKey<ui::DomCode::US_M, 'm', ui::VKEY_M, 'M'>;
using KeyN = TestCharKey<ui::DomCode::US_N, 'n', ui::VKEY_N, 'N'>;
using KeyV = TestCharKey<ui::DomCode::US_V, 'v', ui::VKEY_V, 'V'>;
using KeyZ = TestCharKey<ui::DomCode::US_Z, 'z', ui::VKEY_Z, 'Z'>;
using KeyComma = TestCharKey<ui::DomCode::COMMA, ',', ui::VKEY_OEM_COMMA, '<'>;
using KeyPeriod =
TestCharKey<ui::DomCode::PERIOD, '.', ui::VKEY_OEM_PERIOD, '>'>;
using KeyDigit1 = TestCharKey<ui::DomCode::DIGIT1, '1', ui::VKEY_1, '!'>;
using KeyDigit2 = TestCharKey<ui::DomCode::DIGIT2, '2', ui::VKEY_2, '@'>;
using KeyDigit3 = TestCharKey<ui::DomCode::DIGIT3, '3', ui::VKEY_3, '#'>;
using KeyDigit4 = TestCharKey<ui::DomCode::DIGIT4, '4', ui::VKEY_4, '$'>;
using KeyDigit5 = TestCharKey<ui::DomCode::DIGIT5, '5', ui::VKEY_5, '%'>;
using KeyDigit6 = TestCharKey<ui::DomCode::DIGIT6, '6', ui::VKEY_6, '^'>;
using KeyDigit7 = TestCharKey<ui::DomCode::DIGIT7, '7', ui::VKEY_7, '&'>;
using KeyDigit8 = TestCharKey<ui::DomCode::DIGIT8, '8', ui::VKEY_8, '*'>;
using KeyDigit9 = TestCharKey<ui::DomCode::DIGIT9, '9', ui::VKEY_9, '('>;
using KeyDigit0 = TestCharKey<ui::DomCode::DIGIT0, '0', ui::VKEY_0, ')'>;
using KeyMinus = TestCharKey<ui::DomCode::MINUS, '-', ui::VKEY_OEM_MINUS, '_'>;
using KeyEqual = TestCharKey<ui::DomCode::EQUAL, '=', ui::VKEY_OEM_PLUS, '+'>;
using KeyArrowLeft =
TestKey<ui::DomCode::ARROW_LEFT, ui::DomKey::ARROW_LEFT, ui::VKEY_LEFT>;
using KeyArrowRight =
TestKey<ui::DomCode::ARROW_RIGHT, ui::DomKey::ARROW_RIGHT, ui::VKEY_RIGHT>;
using KeyArrowUp =
TestKey<ui::DomCode::ARROW_UP, ui::DomKey::ARROW_UP, ui::VKEY_UP>;
using KeyArrowDown =
TestKey<ui::DomCode::ARROW_DOWN, ui::DomKey::ARROW_DOWN, ui::VKEY_DOWN>;
using KeyBrowserBack = TestKey<ui::DomCode::BROWSER_BACK,
ui::DomKey::BROWSER_BACK,
ui::VKEY_BROWSER_BACK>;
using KeyBrowserForward = TestKey<ui::DomCode::BROWSER_FORWARD,
ui::DomKey::BROWSER_FORWARD,
ui::VKEY_BROWSER_FORWARD>;
// Modifier keys.
using KeyLShift = TestKey<ui::DomCode::SHIFT_LEFT,
ui::DomKey::SHIFT,
ui::VKEY_SHIFT,
ui::EF_SHIFT_DOWN>;
using KeyRShift = TestKey<ui::DomCode::SHIFT_RIGHT,
ui::DomKey::SHIFT,
ui::VKEY_RSHIFT,
ui::EF_SHIFT_DOWN>;
using KeyLMeta = TestKey<ui::DomCode::META_LEFT,
ui::DomKey::META,
ui::VKEY_LWIN,
ui::EF_COMMAND_DOWN>;
using KeyRMeta = TestKey<ui::DomCode::META_RIGHT,
ui::DomKey::META,
ui::VKEY_RWIN,
ui::EF_COMMAND_DOWN>;
using KeyLControl = TestKey<ui::DomCode::CONTROL_LEFT,
ui::DomKey::CONTROL,
ui::VKEY_CONTROL,
ui::EF_CONTROL_DOWN>;
using KeyRControl = TestKey<ui::DomCode::CONTROL_RIGHT,
ui::DomKey::CONTROL,
ui::VKEY_RCONTROL,
ui::EF_CONTROL_DOWN>;
using KeyLAlt = TestKey<ui::DomCode::ALT_LEFT,
ui::DomKey::ALT,
ui::VKEY_MENU,
ui::EF_ALT_DOWN>;
using KeyRAlt = TestKey<ui::DomCode::ALT_RIGHT,
ui::DomKey::ALT,
ui::VKEY_RMENU,
ui::EF_ALT_DOWN>;
class TestEventRewriterContinuation
: public ui::test::TestEventRewriterContinuation {
public:
TestEventRewriterContinuation() = default;
TestEventRewriterContinuation(const TestEventRewriterContinuation&) = delete;
TestEventRewriterContinuation& operator=(
const TestEventRewriterContinuation&) = delete;
~TestEventRewriterContinuation() override = default;
ui::EventDispatchDetails SendEvent(const ui::Event* event) override {
passthrough_event = event->Clone();
return ui::EventDispatchDetails();
}
ui::EventDispatchDetails SendEventFinally(const ui::Event* event) override {
rewritten_event = event->Clone();
return ui::EventDispatchDetails();
}
ui::EventDispatchDetails DiscardEvent() override {
return ui::EventDispatchDetails();
}
void reset() {
passthrough_event.reset();
rewritten_event.reset();
}
bool discarded() { return !(passthrough_event || rewritten_event); }
std::unique_ptr<ui::Event> passthrough_event;
std::unique_ptr<ui::Event> rewritten_event;
base::WeakPtrFactory<TestEventRewriterContinuation> weak_ptr_factory_{this};
};
class TestInputDeviceSettingsController
: public MockInputDeviceSettingsController {
public:
void OnMouseButtonPressed(DeviceId device_id,
const mojom::Button& button) override {
pressed_mouse_buttons_[device_id].push_back(button.Clone());
}
void OnGraphicsTabletButtonPressed(DeviceId device_id,
const mojom::Button& button) override {
pressed_graphics_tablet_buttons_[device_id].push_back(button.Clone());
}
const base::flat_map<int, std::vector<mojom::ButtonPtr>>&
pressed_mouse_buttons() const {
return pressed_mouse_buttons_;
}
const base::flat_map<int, std::vector<mojom::ButtonPtr>>&
pressed_graphics_tablet_buttons() const {
return pressed_graphics_tablet_buttons_;
}
private:
base::flat_map<int, std::vector<mojom::ButtonPtr>> pressed_mouse_buttons_;
base::flat_map<int, std::vector<mojom::ButtonPtr>>
pressed_graphics_tablet_buttons_;
};
class TestAcceleratorObserver : public AcceleratorController::Observer {
public:
TestAcceleratorObserver() {
Shell::Get()->accelerator_controller()->AddObserver(this);
}
~TestAcceleratorObserver() override {
Shell::Get()->accelerator_controller()->RemoveObserver(this);
}
void OnActionPerformed(AcceleratorAction action) override {
action_performed_ = action;
}
bool has_action_performed() const { return action_performed_.has_value(); }
AcceleratorAction action_performed() const { return *action_performed_; }
void reset() { action_performed_.reset(); }
private:
std::optional<AcceleratorAction> action_performed_;
};
struct EventRewriterTestData {
std::vector<TestEventVariant> incoming_events;
std::vector<TestEventVariant> rewritten_events;
std::optional<mojom::Button> pressed_button;
EventRewriterTestData(std::vector<TestEventVariant> incoming_events,
std::vector<TestEventVariant> rewritten_events)
: incoming_events(incoming_events),
rewritten_events(rewritten_events),
pressed_button(std::nullopt) {}
EventRewriterTestData(std::vector<TestEventVariant> incoming_events,
std::vector<TestEventVariant> rewritten_events,
mojom::CustomizableButton button)
: incoming_events(incoming_events), rewritten_events(rewritten_events) {
pressed_button = mojom::Button();
pressed_button->set_customizable_button(button);
}
EventRewriterTestData(std::vector<TestEventVariant> incoming_events,
std::vector<TestEventVariant> rewritten_events,
ui::KeyboardCode key_code)
: incoming_events(incoming_events), rewritten_events(rewritten_events) {
pressed_button = mojom::Button();
pressed_button->set_vkey(key_code);
}
EventRewriterTestData(const EventRewriterTestData& data) = default;
};
// Before test suites are initialized, paraterized data gets generated.
// `ui::KeyEvent` structs rely on the keyboard layout engine being setup.
// Therefore, before any suites are initialized, the keyboard layout engine
// must be configured before using/creating any `ui::KeyEvent` structs. Once a
// suite is setup, this function will be disabled which will stop any further
// layout engines from being created.
std::unique_ptr<ui::ScopedKeyboardLayoutEngine> CreateLayoutEngine(
bool disable_permanently = false) {
static bool disabled = false;
if (disable_permanently || disabled) {
disabled = true;
return nullptr;
}
return std::make_unique<ui::ScopedKeyboardLayoutEngine>(
std::make_unique<ui::StubKeyboardLayoutEngine>());
}
ui::MouseEvent CreateMouseButtonEvent(ui::EventType type,
int flags,
int changed_button_flags,
int device_id = kMouseDeviceId) {
ui::MouseEvent mouse_event(type, /*location=*/gfx::PointF{},
/*root_location=*/gfx::PointF{},
/*time_stamp=*/{}, flags, changed_button_flags);
mouse_event.set_source_device_id(device_id);
return mouse_event;
}
std::string ConvertToString(const ui::MouseEvent& mouse_event) {
return base::StringPrintf(
"MouseEvent type=%d flags=0x%X changed_button_flags=0x%X",
mouse_event.type(), mouse_event.flags(),
mouse_event.changed_button_flags());
}
std::string ConvertToString(const ui::KeyEvent& key_event) {
auto engine = CreateLayoutEngine();
return base::StringPrintf(
"KeyboardEvent type=%d code=0x%06X flags=0x%X vk=0x%02X key=0x%08X "
"scan=0x%08X",
key_event.type(), static_cast<uint32_t>(key_event.code()),
key_event.flags(), key_event.key_code(),
static_cast<uint32_t>(key_event.GetDomKey()), key_event.scan_code());
}
std::string ConvertToString(const ui::Event& event) {
if (event.IsMouseEvent()) {
return ConvertToString(*event.AsMouseEvent());
}
if (event.IsKeyEvent()) {
return ConvertToString(*event.AsKeyEvent());
}
NOTREACHED_NORETURN();
}
mojom::Button GetButton(ui::KeyboardCode key_code) {
mojom::Button button;
button.set_vkey(key_code);
return button;
}
mojom::Button GetButton(mojom::CustomizableButton customizable_button) {
mojom::Button button;
button.set_customizable_button(customizable_button);
return button;
}
} // namespace
class PeripheralCustomizationEventRewriterTest : public AshTestBase {
public:
PeripheralCustomizationEventRewriterTest() {
CreateLayoutEngine(/*disable_permanently=*/true);
}
PeripheralCustomizationEventRewriterTest(
const PeripheralCustomizationEventRewriterTest&) = delete;
PeripheralCustomizationEventRewriterTest& operator=(
const PeripheralCustomizationEventRewriterTest&) = delete;
~PeripheralCustomizationEventRewriterTest() override = default;
// testing::Test:
void SetUp() override {
scoped_feature_list_.InitWithFeatures({features::kPeripheralCustomization,
features::kInputDeviceSettingsSplit},
{});
AshTestBase::SetUp();
controller_scoped_resetter_ = std::make_unique<
InputDeviceSettingsController::ScopedResetterForTest>();
controller_ = std::make_unique<
testing::NiceMock<TestInputDeviceSettingsController>>();
mouse_settings_ = mojom::MouseSettings::New();
graphics_tablet_settings_ = mojom::GraphicsTabletSettings::New();
ON_CALL(*controller_, GetMouseSettings(testing::_))
.WillByDefault(testing::Return(nullptr));
ON_CALL(*controller_, GetGraphicsTabletSettings(testing::_))
.WillByDefault(testing::Return(nullptr));
ON_CALL(*controller_, GetMouseSettings(kMouseDeviceId))
.WillByDefault(testing::Return(mouse_settings_.get()));
ON_CALL(*controller_, GetGraphicsTabletSettings(kGraphicsTabletDeviceId))
.WillByDefault(testing::Return(graphics_tablet_settings_.get()));
rewriter_ = std::make_unique<PeripheralCustomizationEventRewriter>(
controller_.get());
metrics_manager_ = std::make_unique<InputDeviceSettingsMetricsManager>();
source_.AddEventRewriter(rewriter_.get());
}
void TearDown() override {
source_.RemoveEventRewriter(rewriter_.get());
rewriter_.reset();
controller_.reset();
controller_scoped_resetter_.reset();
AshTestBase::TearDown();
scoped_feature_list_.Reset();
metrics_manager_.reset();
}
std::vector<TestEventVariant> RunRewriter(
const std::vector<TestEventVariant>& events,
ui::EventFlags extra_flags = ui::EF_NONE,
int device_id = kMouseDeviceId) {
struct ModifierInfo {
ui::EventFlags flag;
ui::DomCode code;
ui::DomKey key;
ui::KeyboardCode keycode;
};
// We'll use modifier keys at left side heuristically.
static constexpr ModifierInfo kModifierList[] = {
{ui::EF_SHIFT_DOWN, ui::DomCode::SHIFT_LEFT, ui::DomKey::SHIFT,
ui::VKEY_SHIFT},
{ui::EF_CONTROL_DOWN, ui::DomCode::CONTROL_LEFT, ui::DomKey::CONTROL,
ui::VKEY_CONTROL},
{ui::EF_ALT_DOWN, ui::DomCode::ALT_LEFT, ui::DomKey::ALT,
ui::VKEY_MENU},
{ui::EF_COMMAND_DOWN, ui::DomCode::META_LEFT, ui::DomKey::META,
ui::VKEY_LWIN},
{ui::EF_MOD3_DOWN, ui::DomCode::CAPS_LOCK, ui::DomKey::CAPS_LOCK,
ui::VKEY_CAPITAL},
};
// Send modifier key press events to update rewriter's modifier flag state.
ui::EventFlags current_flags = 0;
for (const auto& modifier : kModifierList) {
if (!(extra_flags & modifier.flag)) {
continue;
}
current_flags |= modifier.flag;
SendKeyEvent(TestKeyEvent{ui::ET_KEY_PRESSED, modifier.code, modifier.key,
modifier.keycode, current_flags},
kRandomKeyboardDeviceId);
}
CHECK_EQ(current_flags, extra_flags);
// Add extra_flags to each event.
std::vector<TestEventVariant> events_with_added_flags;
for (const auto& event : events) {
if (const auto* test_key_event = std::get_if<TestKeyEvent>(&event)) {
TestKeyEvent new_event = *test_key_event;
new_event.flags = new_event.flags | current_flags;
events_with_added_flags.push_back(new_event);
} else if (const auto* test_mouse_event =
std::get_if<TestMouseEvent>(&event)) {
TestMouseEvent new_event = *test_mouse_event;
new_event.flags = new_event.flags | current_flags;
events_with_added_flags.push_back(new_event);
} else {
const auto* test_scroll_event =
std::get_if<TestMouseScrollEvent>(&event);
TestMouseScrollEvent new_event = *test_scroll_event;
new_event.flags = new_event.flags | current_flags;
events_with_added_flags.push_back(new_event);
}
}
auto result = SendKeyEvents(events_with_added_flags, device_id);
// Send modifier key release events to unset rewriter'.s modifier flag
// state.
for (const auto& modifier : base::Reversed(kModifierList)) {
if (!(extra_flags & modifier.flag)) {
continue;
}
current_flags &= ~modifier.flag;
SendKeyEvent(TestKeyEvent{ui::ET_KEY_RELEASED, modifier.code,
modifier.key, modifier.keycode, current_flags},
kRandomKeyboardDeviceId);
}
CHECK_EQ(current_flags, 0);
return result;
}
// Sends a KeyEvent to the rewriter, returns the rewritten events.
// Note: one event may be rewritten into multiple events.
std::vector<TestEventVariant> SendKeyEvent(const TestKeyEvent& event,
int device_id) {
return SendKeyEvents({event}, device_id);
}
std::vector<TestEventVariant> SendKeyEvents(
const std::vector<TestEventVariant>& events,
int device_id) {
// Just in case some events may be there.
if (!sink_.TakeEvents().empty()) {
ADD_FAILURE() << "Rewritten events were left";
}
// Convert TestKeyEvent into ui::KeyEvent, then dispatch it to the
// rewriter.
for (const TestEventVariant& event : events) {
if (const auto* test_key_event = std::get_if<TestKeyEvent>(&event)) {
ui::KeyEvent key_event(test_key_event->type, test_key_event->keycode,
test_key_event->code, test_key_event->flags,
test_key_event->key, ui::EventTimeForNow());
key_event.set_source_device_id(device_id);
ui::EventDispatchDetails details = source_.Send(&key_event);
CHECK(!details.dispatcher_destroyed);
} else if (const auto* test_mouse_event =
std::get_if<TestMouseEvent>(&event)) {
ui::MouseEvent mouse_event(test_mouse_event->type,
/*location=*/gfx::PointF{},
/*root_location=*/gfx::PointF{},
/*time_stamp=*/ui::EventTimeForNow(),
test_mouse_event->flags,
test_mouse_event->changed_button_flags);
mouse_event.set_source_device_id(device_id);
if (test_mouse_event->linux_key_code) {
ui::SetForwardBackMouseButtonProperty(
mouse_event, test_mouse_event->linux_key_code);
}
ui::EventDispatchDetails details = source_.Send(&mouse_event);
CHECK(!details.dispatcher_destroyed);
} else {
const auto* test_scroll_event =
std::get_if<TestMouseScrollEvent>(&event);
CHECK(test_scroll_event);
// Left is negative, right is positive.
const gfx::Vector2d offset(test_scroll_event->direction ? -1 : 1, 0);
ui::MouseWheelEvent scroll_event(offset, /*location=*/gfx::PointF{},
/*root_location=*/gfx::PointF{},
/*time_stamp=*/ui::EventTimeForNow(),
test_scroll_event->flags, ui::EF_NONE);
scroll_event.set_source_device_id(device_id);
ui::EventDispatchDetails details = source_.Send(&scroll_event);
CHECK(!details.dispatcher_destroyed);
}
}
// Convert the rewritten ui::Events back to TestKeyEvent.
auto rewritten_events = sink_.TakeEvents();
std::vector<TestEventVariant> result;
for (const auto& rewritten_event : rewritten_events) {
if (rewritten_event->IsKeyEvent()) {
auto* rewritten_key_event = rewritten_event->AsKeyEvent();
result.push_back(TestKeyEvent{
rewritten_key_event->type(), rewritten_key_event->code(),
rewritten_key_event->GetDomKey(), rewritten_key_event->key_code(),
rewritten_key_event->flags()});
// MouseWheelEvent must be checked before MouseEvent as its a subset of
// mouse events.
} else if (rewritten_event->IsMouseWheelEvent()) {
auto* rewritten_scroll_event = rewritten_event->AsMouseWheelEvent();
CHECK_EQ(0, rewritten_scroll_event->y_offset());
result.push_back(TestMouseScrollEvent{
// Left is negative, right is positive.
(rewritten_scroll_event->x_offset() < 0 ? true : false),
rewritten_scroll_event->flags()});
} else if (rewritten_event->IsMouseEvent()) {
auto* rewritten_mouse_event = rewritten_event->AsMouseEvent();
auto property = ui::GetForwardBackMouseButtonProperty(*rewritten_event);
result.push_back(TestMouseEvent{
rewritten_mouse_event->type(), rewritten_mouse_event->flags(),
rewritten_mouse_event->changed_button_flags(),
property.value_or(0)});
} else {
ADD_FAILURE() << "Unexpected rewritten event: "
<< rewritten_event->ToString();
continue;
}
}
return result;
}
protected:
std::unique_ptr<PeripheralCustomizationEventRewriter> rewriter_;
std::unique_ptr<InputDeviceSettingsController::ScopedResetterForTest>
controller_scoped_resetter_;
std::unique_ptr<testing::NiceMock<TestInputDeviceSettingsController>>
controller_;
base::test::ScopedFeatureList scoped_feature_list_;
mojom::MouseSettingsPtr mouse_settings_;
mojom::GraphicsTabletSettingsPtr graphics_tablet_settings_;
std::unique_ptr<InputDeviceSettingsMetricsManager> metrics_manager_;
TestEventSink sink_;
ui::test::TestEventSource source_{&sink_};
};
TEST_F(PeripheralCustomizationEventRewriterTest, MouseButtonWithoutObserving) {
EXPECT_EQ(ButtonBack::Typed(), RunRewriter(ButtonBack::Typed()));
}
TEST_F(PeripheralCustomizationEventRewriterTest,
InvalidEventTypeMouseObserving) {
TestEventRewriterContinuation continuation;
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowCustomizations);
ui::MouseEvent event =
CreateMouseButtonEvent(ui::ET_MOUSE_DRAGGED, ui::EF_NONE, ui::EF_NONE);
rewriter_->RewriteEvent(event, continuation.weak_ptr_factory_.GetWeakPtr());
ASSERT_TRUE(continuation.passthrough_event);
ASSERT_TRUE(continuation.passthrough_event->IsMouseEvent());
EXPECT_EQ(ConvertToString(event),
ConvertToString(*continuation.passthrough_event));
}
TEST_F(PeripheralCustomizationEventRewriterTest, KeyEventActionRewriting) {
TestAcceleratorObserver accelerator_observer;
TestEventRewriterContinuation continuation;
mouse_settings_->button_remappings.push_back(
mojom::ButtonRemapping::New("", mojom::Button::NewVkey(ui::VKEY_A),
mojom::RemappingAction::NewAcceleratorAction(
AcceleratorAction::kBrightnessDown)));
EXPECT_EQ(std::vector<TestEventVariant>{}, RunRewriter({KeyA::Pressed()}));
ASSERT_TRUE(accelerator_observer.has_action_performed());
accelerator_observer.reset();
EXPECT_EQ(std::vector<TestEventVariant>{}, RunRewriter({KeyA::Released()}));
ASSERT_FALSE(accelerator_observer.has_action_performed());
}
TEST_F(PeripheralCustomizationEventRewriterTest, MouseEventActionRewriting) {
TestAcceleratorObserver accelerator_observer;
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"",
mojom::Button::NewCustomizableButton(mojom::CustomizableButton::kMiddle),
mojom::RemappingAction::NewAcceleratorAction(
AcceleratorAction::kLaunchApp0)));
EXPECT_EQ(std::vector<TestEventVariant>{},
RunRewriter({ButtonMiddle::Pressed()}));
ASSERT_TRUE(accelerator_observer.has_action_performed());
accelerator_observer.reset();
EXPECT_EQ(std::vector<TestEventVariant>{},
RunRewriter({ButtonMiddle::Released()}));
ASSERT_FALSE(accelerator_observer.has_action_performed());
}
TEST_F(PeripheralCustomizationEventRewriterTest, ScrollEventActionRewriting) {
TestAcceleratorObserver accelerator_observer;
mouse_settings_->button_remappings.push_back(
mojom::ButtonRemapping::New("",
mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kScrollLeft),
mojom::RemappingAction::NewAcceleratorAction(
AcceleratorAction::kLaunchApp0)));
EXPECT_EQ(std::vector<TestEventVariant>{}, RunRewriter(ScrollLeft::Typed()));
ASSERT_TRUE(accelerator_observer.has_action_performed());
accelerator_observer.reset();
EXPECT_EQ(ScrollRight::Typed(), RunRewriter(ScrollRight::Typed()));
ASSERT_FALSE(accelerator_observer.has_action_performed());
}
TEST_F(PeripheralCustomizationEventRewriterTest, MouseWheelDuringObserving) {
TestEventRewriterContinuation continuation;
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowAlphabetKeyEventRewrites);
gfx::Vector2d expected_offset(/*x=*/100, /*y=*/50);
ui::MouseWheelEvent event =
ui::MouseWheelEvent(expected_offset, gfx::PointF{}, gfx::PointF{},
/*time_stamp=*/{}, ui::EF_NONE, ui::EF_NONE);
event.set_source_device_id(kMouseDeviceId);
rewriter_->RewriteEvent(event, continuation.weak_ptr_factory_.GetWeakPtr());
ASSERT_TRUE(continuation.passthrough_event);
ASSERT_TRUE(continuation.passthrough_event->IsMouseWheelEvent());
EXPECT_EQ(expected_offset,
continuation.passthrough_event->AsMouseWheelEvent()->offset());
}
TEST_F(PeripheralCustomizationEventRewriterTest,
MouseEventFlagAppliedOnRelease) {
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
/*name=*/"",
mojom::Button::NewCustomizableButton(mojom::CustomizableButton::kMiddle),
mojom::RemappingAction::NewStaticShortcutAction(
mojom::StaticShortcutAction::kDisable)));
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
/*name=*/"", mojom::Button::NewVkey(ui::VKEY_0),
mojom::RemappingAction::NewStaticShortcutAction(
mojom::StaticShortcutAction::kMiddleClick)));
EXPECT_EQ(ButtonMiddle::Typed(), RunRewriter(KeyDigit0::Typed()));
}
TEST_F(PeripheralCustomizationEventRewriterTest,
KeyEventFlagNotAppliedOnRelease) {
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
/*name=*/"", mojom::Button::NewVkey(ui::VKEY_0),
mojom::RemappingAction::NewKeyEvent(mojom::KeyEvent::New(
ui::VKEY_CONTROL, static_cast<int>(ui::DomCode::CONTROL_LEFT),
static_cast<int>(ui::DomKey::CONTROL), ui::EF_CONTROL_DOWN,
/*key_display=*/""))));
EXPECT_EQ(KeyLControl::Typed(), RunRewriter(KeyDigit0::Typed()));
}
TEST_F(PeripheralCustomizationEventRewriterTest,
SwitchingLayoutsUpdatesDomKey) {
std::unique_ptr<ui::StubKeyboardLayoutEngine> layout_engine =
std::make_unique<ui::StubKeyboardLayoutEngine>();
ui::KeyboardLayoutEngineManager::ResetKeyboardLayoutEngine();
ui::KeyboardLayoutEngineManager::SetKeyboardLayoutEngine(layout_engine.get());
const std::vector<ui::StubKeyboardLayoutEngine::CustomLookupEntry> us_table =
{{ui::DomCode::MINUS, /*character=*/u'-', /*character_shifted=*/u'_',
ui::KeyboardCode::VKEY_OEM_MINUS},
{ui::DomCode::BRACKET_LEFT, /*character=*/u'[',
/*character_shifted=*/u'{', ui::KeyboardCode::VKEY_OEM_4}};
// Provide a custom layout that mimics behavior of a de-DE keyboard.
// In the German keyboard, VKEY_OEM_4 is located at DomCode position MINUS
// with DomKey `ß`. With positional remapping, VKEY_OEM_4 is remapped to
// search for DomCode BRACKET_LEFT, resulting in DomKey `ü`.
const std::vector<ui::StubKeyboardLayoutEngine::CustomLookupEntry> de_table =
{{ui::DomCode::MINUS, /*character=*/u'ß', /*character_shifted=*/u'?',
ui::KeyboardCode::VKEY_OEM_4},
{ui::DomCode::BRACKET_LEFT, /*character=*/u'ü',
/*character_shifted=*/u'Ü', ui::KeyboardCode::VKEY_OEM_1}};
layout_engine->SetCustomLookupTableForTesting(us_table);
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
/*name=*/"", mojom::Button::NewVkey(ui::VKEY_0),
mojom::RemappingAction::NewKeyEvent(mojom::KeyEvent::New(
ui::VKEY_OEM_MINUS, static_cast<int>(ui::DomCode::MINUS),
static_cast<int>(ui::DomKey::Constant<'-'>::Character), ui::EF_NONE,
/*key_display=*/""))));
EXPECT_EQ(KeyMinus::Typed(), RunRewriter(KeyDigit0::Typed()));
// Switch to German (DE) layout table and expect the remapped button to have a
// different VKEY and DomKey.
layout_engine->SetCustomLookupTableForTesting(de_table);
ash::AcceleratorKeycodeLookupCache::Get()->Clear();
EXPECT_EQ((TestKey<ui::DomCode::MINUS, ui::DomKey::Constant<u'ß'>::Character,
ui::VKEY_OEM_4>::Typed()),
RunRewriter(KeyDigit0::Typed()));
}
TEST_F(PeripheralCustomizationEventRewriterTest,
ModifiersAffectComputedDomKeyKeyEvent) {
TestEventRewriterContinuation continuation;
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
/*name=*/"", mojom::Button::NewVkey(ui::VKEY_0),
mojom::RemappingAction::NewKeyEvent(mojom::KeyEvent::New(
ui::VKEY_A, static_cast<int>(ui::DomCode::US_A),
static_cast<int>(ui::DomKey::Constant<'a'>::Character), ui::EF_NONE,
/*key_display=*/""))));
EXPECT_EQ(KeyA::Typed(ui::EF_SHIFT_DOWN),
RunRewriter(KeyDigit0::Typed(ui::EF_SHIFT_DOWN)));
}
TEST_F(PeripheralCustomizationEventRewriterTest,
ModifiersAffectComputedDomKeyMouseEvent) {
TestEventRewriterContinuation continuation;
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
/*name=*/"",
mojom::Button::NewCustomizableButton(mojom::CustomizableButton::kForward),
mojom::RemappingAction::NewKeyEvent(mojom::KeyEvent::New(
ui::VKEY_A, static_cast<int>(ui::DomCode::US_A),
static_cast<int>(ui::DomKey::Constant<'a'>::Character), ui::EF_NONE,
/*key_display=*/""))));
EXPECT_EQ(KeyA::Typed(ui::EF_SHIFT_DOWN),
RunRewriter(ButtonForward::Typed(ui::EF_SHIFT_DOWN)));
}
TEST_F(PeripheralCustomizationEventRewriterTest,
ModifierPressedAffectsDomKeyOnOtherDevices) {
TestEventRewriterContinuation continuation;
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
/*name=*/"",
mojom::Button::NewCustomizableButton(mojom::CustomizableButton::kForward),
mojom::RemappingAction::NewKeyEvent(mojom::KeyEvent::New(
ui::VKEY_SHIFT, static_cast<int>(ui::DomCode::SHIFT_LEFT),
static_cast<int>(ui::DomKey::SHIFT), ui::EF_SHIFT_DOWN,
/*key_display=*/""))));
EXPECT_EQ(std::vector<TestEventVariant>{KeyLShift::Pressed()},
RunRewriter({ButtonForward::Pressed()}));
EXPECT_EQ(KeyA::Typed(ui::EF_SHIFT_DOWN),
RunRewriter(KeyA::Typed(), ui::EF_NONE, kRandomKeyboardDeviceId));
}
class MouseButtonObserverTest
: public PeripheralCustomizationEventRewriterTest,
public testing::WithParamInterface<EventRewriterTestData> {};
INSTANTIATE_TEST_SUITE_P(
All,
MouseButtonObserverTest,
testing::ValuesIn(std::vector<EventRewriterTestData>{
// MouseEvent tests:
{
ButtonBack::Typed(),
{},
mojom::CustomizableButton::kBack,
},
{
ButtonForward::Typed(),
{},
mojom::CustomizableButton::kForward,
},
{
ButtonMiddle::Typed(),
{},
mojom::CustomizableButton::kMiddle,
},
{
ButtonMiddle::Typed(ui::EF_LEFT_MOUSE_BUTTON),
{},
mojom::CustomizableButton::kMiddle,
},
// Observer notified only when mouse button pressed.
{{ButtonBack::Released()},
/*rewritten_events=*/std::vector<TestEventVariant>()},
// Left click ignored for buttons from a mouse.
{ButtonLeft::Typed(), ButtonLeft::Typed()},
// Right click ignored for buttons from a mouse.
{
ButtonRight::Typed(),
ButtonRight::Typed(),
},
// Remapped flags are ignored when included in the event with other
// buttons.
{ButtonLeft::Typed(ui::EF_BACK_MOUSE_BUTTON), ButtonLeft::Typed()},
{
ButtonRight::Typed(ui::EF_MIDDLE_MOUSE_BUTTON),
ButtonRight::Typed(),
},
// KeyEvent tests:
{
KeyA::Typed(ui::EF_COMMAND_DOWN),
std::vector<TestEventVariant>(),
ui::VKEY_A,
},
{
KeyB::Typed(),
std::vector<TestEventVariant>(),
ui::VKEY_B,
},
// Scroll tests:
{
ScrollLeft::Typed(),
std::vector<TestEventVariant>(),
mojom::CustomizableButton::kScrollLeft,
},
{
ScrollRight::Typed(),
std::vector<TestEventVariant>(),
mojom::CustomizableButton::kScrollRight,
},
}),
[](const testing::TestParamInfo<EventRewriterTestData>& info) {
std::string name = ConvertToString(info.param.incoming_events.front());
std::replace(name.begin(), name.end(), ' ', '_');
std::replace(name.begin(), name.end(), '=', '_');
std::replace(name.begin(), name.end(), '_', '_');
std::replace(name.begin(), name.end(), '(', '_');
std::replace(name.begin(), name.end(), ')', '_');
std::replace(name.begin(), name.end(), '|', '_');
return name;
});
TEST_P(MouseButtonObserverTest, EventRewriting) {
auto data = GetParam();
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowCustomizations);
auto rewritten_events = RunRewriter(data.incoming_events);
if (data.rewritten_events.empty()) {
ASSERT_TRUE(rewritten_events.empty());
if (data.pressed_button) {
const auto& actual_pressed_buttons =
controller_->pressed_mouse_buttons().at(kMouseDeviceId);
ASSERT_EQ(1u, actual_pressed_buttons.size());
EXPECT_EQ(*data.pressed_button, *actual_pressed_buttons[0]);
}
} else {
ASSERT_FALSE(rewritten_events.empty());
EXPECT_EQ(data.rewritten_events, rewritten_events);
}
rewriter_->StopObserving();
// After we stop observing, the passthrough event should be an identity of the
// original.
EXPECT_EQ(data.incoming_events, RunRewriter(data.incoming_events));
}
TEST_F(MouseButtonObserverTest, MouseBackButtonRecognition) {
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowCustomizations);
EXPECT_EQ(std::vector<TestEventVariant>{}, RunRewriter(ButtonBack::Typed()));
const auto& actual_pressed_buttons =
controller_->pressed_mouse_buttons().at(kMouseDeviceId);
ASSERT_EQ(1u, actual_pressed_buttons.size());
EXPECT_EQ(
*mojom::Button::NewCustomizableButton(mojom::CustomizableButton::kBack),
*actual_pressed_buttons[0]);
}
TEST_F(MouseButtonObserverTest, MouseSideButtonRecognition) {
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowCustomizations);
EXPECT_EQ(std::vector<TestEventVariant>{}, RunRewriter(ButtonSide::Typed()));
const auto& actual_pressed_buttons =
controller_->pressed_mouse_buttons().at(kMouseDeviceId);
ASSERT_EQ(1u, actual_pressed_buttons.size());
EXPECT_EQ(
*mojom::Button::NewCustomizableButton(mojom::CustomizableButton::kSide),
*actual_pressed_buttons[0]);
}
TEST_F(MouseButtonObserverTest, MouseForwardButtonRecognition) {
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowCustomizations);
EXPECT_EQ(std::vector<TestEventVariant>{},
RunRewriter(ButtonForward::Typed()));
const auto& actual_pressed_buttons =
controller_->pressed_mouse_buttons().at(kMouseDeviceId);
ASSERT_EQ(1u, actual_pressed_buttons.size());
EXPECT_EQ(*mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kForward),
*actual_pressed_buttons[0]);
}
TEST_F(MouseButtonObserverTest, MouseExtraButtonRecognition) {
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowCustomizations);
EXPECT_EQ(std::vector<TestEventVariant>{}, RunRewriter(ButtonExtra::Typed()));
const auto& actual_pressed_buttons =
controller_->pressed_mouse_buttons().at(kMouseDeviceId);
ASSERT_EQ(1u, actual_pressed_buttons.size());
EXPECT_EQ(
*mojom::Button::NewCustomizableButton(mojom::CustomizableButton::kExtra),
*actual_pressed_buttons[0]);
}
TEST_F(MouseButtonObserverTest, kDisableKeyEventRewritesRestriction) {
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kDisableKeyEventRewrites);
// Key events should not be modified if no key event customizations are
// allowed.
EXPECT_EQ(KeyA::Typed(ui::EF_COMMAND_DOWN),
RunRewriter(KeyA::Typed(ui::EF_COMMAND_DOWN)));
// Mouse event should be discarded if only key event rewrites aren't allowed.
EXPECT_EQ(std::vector<TestEventVariant>{}, RunRewriter(ButtonSide::Typed()));
}
TEST_F(MouseButtonObserverTest, AllowCustomizationsRestriction) {
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowCustomizations);
// kAllowCustomizations should swallow both key events and mouse events.
EXPECT_EQ(std::vector<TestEventVariant>{},
RunRewriter(KeyA::Typed(ui::EF_COMMAND_DOWN)));
EXPECT_EQ(std::vector<TestEventVariant>{}, RunRewriter(ButtonBack::Typed()));
}
TEST_F(PeripheralCustomizationEventRewriterTest,
RewriteEventFromButtonEmitMetrics) {
base::HistogramTester histogram_tester;
mouse_settings_->button_remappings.push_back(
mojom::ButtonRemapping::New("", mojom::Button::NewVkey(ui::VKEY_A),
mojom::RemappingAction::NewAcceleratorAction(
AcceleratorAction::kBrightnessDown)));
graphics_tablet_settings_->pen_button_remappings.push_back(
mojom::ButtonRemapping::New(
"", mojom::Button::NewVkey(ui::VKEY_Z),
mojom::RemappingAction::NewKeyEvent(mojom::KeyEvent::New(
ui::KeyboardCode::VKEY_M, (int)ui::DomCode::US_M,
(int)ui::DomKey::FromCharacter('M'),
(int)ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN,
/*key_display=*/""))));
histogram_tester.ExpectTotalCount(
"ChromeOS.Settings.Device.Mouse.ButtonRemapping.AcceleratorAction."
"Pressed",
/*expected_count=*/0);
RunRewriter(KeyA::Typed(), ui::EF_NONE, kMouseDeviceId);
histogram_tester.ExpectTotalCount(
"ChromeOS.Settings.Device.Mouse.ButtonRemapping.AcceleratorAction."
"Pressed",
/*expected_count=*/1u);
histogram_tester.ExpectTotalCount(
"ChromeOS.Settings.Device.GraphicsTabletPen.ButtonRemapping.KeyEvent."
"Pressed",
/*expected_count=*/0);
RunRewriter(KeyZ::Typed(), ui::EF_NONE, kGraphicsTabletDeviceId);
histogram_tester.ExpectTotalCount(
"ChromeOS.Settings.Device.GraphicsTabletPen.ButtonRemapping.KeyEvent."
"Pressed",
/*expected_count=*/1u);
}
TEST_F(MouseButtonObserverTest, RewriteAlphabetKeyEvent) {
TestEventRewriterContinuation continuation;
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowAlphabetKeyEventRewrites);
// Key event shouldn't be discarded if the key code is not alphabet letter.
EXPECT_EQ(KeyArrowLeft::Typed(ui::EF_COMMAND_DOWN),
RunRewriter(KeyArrowLeft::Typed(ui::EF_COMMAND_DOWN)));
// New key event should be discarded if the key code is alphabet letter.
EXPECT_EQ(std::vector<TestEventVariant>{},
RunRewriter(KeyA::Typed(ui::EF_COMMAND_DOWN)));
}
TEST_F(MouseButtonObserverTest, RewriteAlphabetOrNumberKeyEvent) {
TestEventRewriterContinuation continuation;
rewriter_->StartObservingMouse(
kMouseDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowAlphabetOrNumberKeyEventRewrites);
// Key event shouldn't be discarded if the key code is not alphabet letter or
// number.
EXPECT_EQ(KeyArrowLeft::Typed(ui::EF_COMMAND_DOWN),
RunRewriter(KeyArrowLeft::Typed(ui::EF_COMMAND_DOWN)));
// New key event should be discarded if the key code is alphabet letter.
EXPECT_EQ(std::vector<TestEventVariant>{}, RunRewriter(KeyA::Typed()));
// New key event should be discarded if the key code is a number.
EXPECT_EQ(std::vector<TestEventVariant>{},
RunRewriter(KeyDigit0::Typed(ui::EF_COMMAND_DOWN)));
}
class GraphicsTabletButtonObserverTest
: public PeripheralCustomizationEventRewriterTest,
public testing::WithParamInterface<EventRewriterTestData> {};
INSTANTIATE_TEST_SUITE_P(
All,
GraphicsTabletButtonObserverTest,
testing::ValuesIn(std::vector<EventRewriterTestData>{
{
ButtonRight::Typed(),
{},
mojom::CustomizableButton::kRight,
},
{
ButtonBack::Typed(),
{},
mojom::CustomizableButton::kBack,
},
{
ButtonForward::Typed(),
{},
mojom::CustomizableButton::kForward,
},
{
ButtonMiddle::Typed(),
{},
mojom::CustomizableButton::kMiddle,
},
{
ButtonMiddle::Typed(ui::EF_LEFT_MOUSE_BUTTON),
{},
mojom::CustomizableButton::kMiddle,
},
// Left click ignored for buttons from a graphics tablet.
{
ButtonLeft::Typed(),
ButtonLeft::Typed(),
},
// Other flags are ignored when included in the event with other
// buttons.
{
ButtonLeft::Typed(ui::EF_BACK_MOUSE_BUTTON),
ButtonLeft::Typed(),
},
{
ButtonLeft::Typed(ui::EF_MIDDLE_MOUSE_BUTTON),
ButtonLeft::Typed(),
},
// KeyEvent tests:
{
KeyA::Typed(ui::EF_COMMAND_DOWN),
{},
ui::VKEY_A,
},
{
KeyB::Typed(),
{},
ui::VKEY_B,
},
}),
[](const testing::TestParamInfo<EventRewriterTestData>& info) {
std::string name = ConvertToString(info.param.incoming_events.front());
std::replace(name.begin(), name.end(), ' ', '_');
std::replace(name.begin(), name.end(), '=', '_');
std::replace(name.begin(), name.end(), '_', '_');
std::replace(name.begin(), name.end(), '(', '_');
std::replace(name.begin(), name.end(), ')', '_');
std::replace(name.begin(), name.end(), '|', '_');
return name;
});
TEST_P(GraphicsTabletButtonObserverTest, RewriteEvent) {
auto data = GetParam();
rewriter_->StartObservingGraphicsTablet(
kGraphicsTabletDeviceId,
/*customization_restriction=*/mojom::CustomizationRestriction::
kAllowCustomizations);
auto rewritten_events =
RunRewriter(data.incoming_events, ui::EF_NONE, kGraphicsTabletDeviceId);
if (data.rewritten_events.empty()) {
ASSERT_TRUE(rewritten_events.empty());
if (data.pressed_button) {
const auto& actual_pressed_buttons =
controller_->pressed_graphics_tablet_buttons().at(
kGraphicsTabletDeviceId);
ASSERT_EQ(1u, actual_pressed_buttons.size());
EXPECT_EQ(*data.pressed_button, *actual_pressed_buttons[0]);
}
} else {
ASSERT_FALSE(rewritten_events.empty());
EXPECT_EQ(data.rewritten_events, rewritten_events);
}
rewriter_->StopObserving();
// After we stop observing, the passthrough event should be an identity of the
// original.
EXPECT_EQ(data.incoming_events, RunRewriter(data.incoming_events, ui::EF_NONE,
kGraphicsTabletDeviceId));
}
class ButtonRewritingTest
: public PeripheralCustomizationEventRewriterTest,
public testing::WithParamInterface<
std::tuple<std::pair<mojom::Button, mojom::KeyEvent>,
EventRewriterTestData>> {};
INSTANTIATE_TEST_SUITE_P(
All,
ButtonRewritingTest,
testing::ValuesIn(std::vector<
std::tuple<std::pair<mojom::Button, mojom::KeyEvent>,
EventRewriterTestData>>{
// KeyEvent rewriting test cases:
// Remap A -> B.
{{GetButton(ui::VKEY_A),
mojom::KeyEvent(
ui::VKEY_B,
static_cast<int>(ui::DomCode::US_B),
static_cast<int>(ui::DomKey::Constant<'b'>::Character),
ui::EF_NONE,
/*key_display=*/"")},
{KeyA::Typed(), KeyB::Typed()}},
// Remap A -> B, Pressing B is no-op.
{{GetButton(ui::VKEY_A),
mojom::KeyEvent(
ui::VKEY_B,
static_cast<int>(ui::DomCode::US_B),
static_cast<int>(ui::DomKey::Constant<'b'>::Character),
ui::EF_NONE,
/*key_display=*/"")},
{KeyB::Typed(), KeyB::Typed()}},
// Remap CTRL -> ALT.
{{GetButton(ui::VKEY_CONTROL),
mojom::KeyEvent(ui::VKEY_MENU,
static_cast<int>(ui::DomCode::ALT_LEFT),
static_cast<int>(ui::DomKey::ALT),
ui::EF_ALT_DOWN,
/*key_display=*/"")},
{KeyLControl::Typed(), KeyLAlt::Typed()}},
// Remap CTRL -> ALT and press with shift down.
{{GetButton(ui::VKEY_CONTROL),
mojom::KeyEvent(ui::VKEY_MENU,
static_cast<int>(ui::DomCode::ALT_LEFT),
static_cast<int>(ui::DomKey::ALT),
ui::EF_ALT_DOWN,
/*key_display=*/"")},
{KeyLControl::Typed(ui::EF_SHIFT_DOWN),
KeyLAlt::Typed(ui::EF_SHIFT_DOWN)}},
// Remap A -> CTRL + SHIFT + B.
{{GetButton(ui::VKEY_A),
mojom::KeyEvent(
ui::VKEY_B,
static_cast<int>(ui::DomCode::US_B),
static_cast<int>(ui::DomKey::Constant<'b'>::Character),
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
/*key_display=*/"")},
{KeyA::Typed(),
std::vector<TestEventVariant>{
KeyLControl::Pressed(), KeyLShift::Pressed(ui::EF_CONTROL_DOWN),
KeyB::Pressed(ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN),
KeyB::Released(ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN),
KeyLShift::Released(ui::EF_CONTROL_DOWN),
KeyLControl::Released()}}},
// MouseEvent rewriting test cases:
// Remap Middle -> CTRL + SHIFT + B.
{{GetButton(mojom::CustomizableButton::kMiddle),
mojom::KeyEvent(
ui::VKEY_B,
static_cast<int>(ui::DomCode::US_B),
static_cast<int>(ui::DomKey::Constant<'b'>::Character),
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
/*key_display=*/"")},
{ButtonMiddle::Typed(),
std::vector<TestEventVariant>{
KeyLControl::Pressed(), KeyLShift::Pressed(ui::EF_CONTROL_DOWN),
KeyB::Pressed(ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN),
KeyB::Released(ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN),
KeyLShift::Released(ui::EF_CONTROL_DOWN),
KeyLControl::Released()}}},
// Remap Middle -> CTRL + SHIFT + B with ALT down.
{{GetButton(mojom::CustomizableButton::kMiddle),
mojom::KeyEvent(
ui::VKEY_B,
static_cast<int>(ui::DomCode::US_B),
static_cast<int>(ui::DomKey::Constant<'b'>::Character),
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
/*key_display=*/"")},
{ButtonMiddle::Typed(ui::EF_ALT_DOWN),
std::vector<TestEventVariant>{
KeyLControl::Pressed(ui::EF_ALT_DOWN),
KeyLShift::Pressed(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN),
KeyB::Pressed(ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN |
ui::EF_ALT_DOWN),
KeyB::Released(ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN |
ui::EF_ALT_DOWN),
KeyLShift::Released(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN),
KeyLControl::Released(ui::EF_ALT_DOWN)}}},
// Remap Back -> Meta.
{{GetButton(mojom::CustomizableButton::kBack),
mojom::KeyEvent(ui::VKEY_LWIN,
static_cast<int>(ui::DomCode::META_LEFT),
static_cast<int>(ui::DomKey::META),
ui::EF_COMMAND_DOWN,
/*key_display=*/"")},
{ButtonBack::Typed(), KeyLMeta::Typed()}},
// Remap Middle -> B and check left mouse button is a no-op.
{{GetButton(mojom::CustomizableButton::kMiddle),
mojom::KeyEvent(
ui::VKEY_B,
static_cast<int>(ui::DomCode::US_B),
static_cast<int>(ui::DomKey::Constant<'b'>::Character),
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN,
/*key_display=*/"")},
{ButtonLeft::Typed(ui::EF_ALT_DOWN),
ButtonLeft::Typed(ui::EF_ALT_DOWN)}},
// Scroll Wheel tests:
{{GetButton(mojom::CustomizableButton::kScrollLeft),
mojom::KeyEvent(
ui::VKEY_Z,
static_cast<int>(ui::DomCode::US_Z),
static_cast<int>(ui::DomKey::Constant<'z'>::Character),
ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN |
ui::EF_ALT_DOWN,
/*key_display=*/"")},
{ScrollLeft::Typed(),
std::vector<TestEventVariant>{
KeyLMeta::Pressed(), KeyLControl::Pressed(ui::EF_COMMAND_DOWN),
KeyLAlt::Pressed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN),
KeyLShift::Pressed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN |
ui::EF_ALT_DOWN),
KeyZ::Pressed(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN |
ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN),
KeyZ::Released(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN |
ui::EF_ALT_DOWN | ui::EF_SHIFT_DOWN),
KeyLShift::Released(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN |
ui::EF_ALT_DOWN),
KeyLAlt::Released(ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN),
KeyLControl::Released(ui::EF_COMMAND_DOWN),
KeyLMeta::Released()}}},
{{GetButton(mojom::CustomizableButton::kScrollLeft),
mojom::KeyEvent(
ui::VKEY_Z,
static_cast<int>(ui::DomCode::US_Z),
static_cast<int>(ui::DomKey::Constant<'z'>::Character),
ui::EF_COMMAND_DOWN,
/*key_display=*/"")},
{ScrollLeft::Typed(),
std::vector<TestEventVariant>{
KeyLMeta::Pressed(), KeyZ::Pressed(ui::EF_COMMAND_DOWN),
KeyZ::Released(ui::EF_COMMAND_DOWN), KeyLMeta::Released()}}},
}));
TEST_P(ButtonRewritingTest, GraphicsPenRewriteEvent) {
auto [tuple, data] = GetParam();
auto& [button, key_event] = tuple;
graphics_tablet_settings_->pen_button_remappings.push_back(
mojom::ButtonRemapping::New(
"", button.Clone(),
mojom::RemappingAction::NewKeyEvent(key_event.Clone())));
EXPECT_EQ(
data.rewritten_events,
RunRewriter(data.incoming_events, ui::EF_NONE, kGraphicsTabletDeviceId));
}
TEST_P(ButtonRewritingTest, GraphicsTabletRewriteEvent) {
auto [tuple, data] = GetParam();
auto& [button, key_event] = tuple;
graphics_tablet_settings_->tablet_button_remappings.push_back(
mojom::ButtonRemapping::New(
"", button.Clone(),
mojom::RemappingAction::NewKeyEvent(key_event.Clone())));
EXPECT_EQ(
data.rewritten_events,
RunRewriter(data.incoming_events, ui::EF_NONE, kGraphicsTabletDeviceId));
}
TEST_P(ButtonRewritingTest, MouseRewriteEvent) {
auto [tuple, data] = GetParam();
auto& [button, key_event] = tuple;
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"", button.Clone(),
mojom::RemappingAction::NewKeyEvent(key_event.Clone())));
EXPECT_EQ(data.rewritten_events,
RunRewriter(data.incoming_events));
}
class ModifierRewritingTest : public PeripheralCustomizationEventRewriterTest,
public testing::WithParamInterface<TestKeyEvent> {
};
INSTANTIATE_TEST_SUITE_P(All,
ModifierRewritingTest,
testing::ValuesIn(std::vector<TestKeyEvent>({
KeyLMeta::Pressed(),
KeyRMeta::Pressed(),
KeyLShift::Pressed(),
KeyRShift::Pressed(),
KeyLControl::Pressed(),
KeyRControl::Pressed(),
KeyLAlt::Pressed(),
KeyRAlt::Pressed(),
})));
TEST_P(ModifierRewritingTest, ModifierKeyCombo) {
const auto& data = GetParam();
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"", mojom::Button::NewVkey(ui::VKEY_0),
mojom::RemappingAction::NewKeyEvent(mojom::KeyEvent::New(
data.keycode, (int)data.code, (int)data.key, data.flags,
/*key_display=*/""))));
auto modifier_pressed_event = data;
auto modifier_released_event = data;
modifier_released_event.type = ui::ET_KEY_RELEASED;
modifier_released_event.flags = ui::EF_NONE;
// Press down remapped button that maps to a modifier.
EXPECT_EQ((std::vector<TestEventVariant>{modifier_pressed_event}),
RunRewriter(std::vector<TestEventVariant>{KeyDigit0::Pressed()}));
// When a key is pressed it should have the remapped modifier flag.
EXPECT_EQ((KeyA::Typed(data.flags)), RunRewriter(KeyA::Typed()));
// Release the remapped modifier button.
EXPECT_EQ(std::vector<TestEventVariant>{modifier_released_event},
RunRewriter(std::vector<TestEventVariant>{KeyDigit0::Released()}));
// Other pressed key should no longer have the remapped flag.
EXPECT_EQ((KeyA::Typed()), RunRewriter(KeyA::Typed()));
}
TEST_P(ModifierRewritingTest, MultiModifierKeyCombo) {
const auto& data = GetParam();
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"", mojom::Button::NewVkey(ui::VKEY_0),
mojom::RemappingAction::NewKeyEvent(mojom::KeyEvent::New(
data.keycode, (int)data.code, (int)data.key, data.flags,
/*key_display=*/""))));
const ui::EventFlags test_flag = data.flags == ui::EF_COMMAND_DOWN
? ui::EF_SHIFT_DOWN
: ui::EF_COMMAND_DOWN;
auto modifier_pressed_event = data;
auto modifier_released_event = data;
modifier_released_event.type = ui::ET_KEY_RELEASED;
modifier_released_event.flags = ui::EF_NONE;
// Press down remapped button that maps to a modifier.
EXPECT_EQ((std::vector<TestEventVariant>{modifier_pressed_event}),
RunRewriter(std::vector<TestEventVariant>{KeyDigit0::Pressed()}));
// When a key is pressed it should have the remapped modifier flag as well as
// the additional test flag.
EXPECT_EQ((KeyA::Typed(data.flags | test_flag)),
RunRewriter(KeyA::Typed(test_flag)));
// Release the remapped modifier button.
EXPECT_EQ(std::vector<TestEventVariant>{modifier_released_event},
RunRewriter(std::vector<TestEventVariant>{KeyDigit0::Released()}));
// Other pressed key should no longer have the remapped flag.
EXPECT_EQ((KeyA::Typed(test_flag)), RunRewriter(KeyA::Typed(test_flag)));
}
TEST_P(ModifierRewritingTest, MouseEvent) {
const auto& data = GetParam();
const ui::EventFlags test_flag = data.flags == ui::EF_COMMAND_DOWN
? ui::EF_SHIFT_DOWN
: ui::EF_COMMAND_DOWN;
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"", mojom::Button::NewVkey(ui::VKEY_0),
mojom::RemappingAction::NewKeyEvent(mojom::KeyEvent::New(
data.keycode, (int)data.code, (int)data.key, data.flags,
/*key_display=*/""))));
auto modifier_pressed_event = data;
auto modifier_released_event = data;
modifier_released_event.type = ui::ET_KEY_RELEASED;
modifier_released_event.flags = ui::EF_NONE;
// Press down remapped button that maps to a modifier.
EXPECT_EQ((std::vector<TestEventVariant>{modifier_pressed_event}),
RunRewriter(std::vector<TestEventVariant>{KeyDigit0::Pressed()}));
// When a button is pressed it should have the remapped modifier flag as well
// as the additional test flag.
EXPECT_EQ((ButtonForward::Typed(data.flags | test_flag)),
RunRewriter(ButtonForward::Typed(test_flag)));
// Release the remapped modifier button.
EXPECT_EQ(std::vector<TestEventVariant>{modifier_released_event},
RunRewriter(std::vector<TestEventVariant>{KeyDigit0::Released()}));
// Other pressed button should no longer have the remapped flag.
EXPECT_EQ((KeyA::Typed(test_flag)), RunRewriter(KeyA::Typed(test_flag)));
}
class StaticShortcutActionRewritingTest
: public PeripheralCustomizationEventRewriterTest,
public testing::WithParamInterface<
std::tuple<mojom::StaticShortcutAction,
std::vector<TestEventVariant>>> {};
INSTANTIATE_TEST_SUITE_P(
All,
StaticShortcutActionRewritingTest,
testing::ValuesIn(std::vector<std::tuple<mojom::StaticShortcutAction,
std::vector<TestEventVariant>>>({
{mojom::StaticShortcutAction::kCopy,
{KeyLControl::Pressed(), KeyC::Pressed(ui::EF_CONTROL_DOWN),
KeyC::Released(ui::EF_CONTROL_DOWN), KeyLControl::Released()}},
{mojom::StaticShortcutAction::kPaste,
{KeyLControl::Pressed(), KeyV::Pressed(ui::EF_CONTROL_DOWN),
KeyV::Released(ui::EF_CONTROL_DOWN), KeyLControl::Released()}},
{mojom::StaticShortcutAction::kUndo,
{KeyLControl::Pressed(), KeyZ::Pressed(ui::EF_CONTROL_DOWN),
KeyZ::Released(ui::EF_CONTROL_DOWN), KeyLControl::Released()}},
{mojom::StaticShortcutAction::kRedo,
{KeyLControl::Pressed(), KeyLShift::Pressed(ui::EF_CONTROL_DOWN),
KeyZ::Pressed(ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN),
KeyZ::Released(ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN),
KeyLShift::Released(ui::EF_CONTROL_DOWN), KeyLControl::Released()}},
{mojom::StaticShortcutAction::kZoomIn,
{KeyLControl::Pressed(), KeyEqual::Pressed(ui::EF_CONTROL_DOWN),
KeyEqual::Released(ui::EF_CONTROL_DOWN), KeyLControl::Released()}},
{mojom::StaticShortcutAction::kZoomOut,
{KeyLControl::Pressed(), KeyMinus::Pressed(ui::EF_CONTROL_DOWN),
KeyMinus::Released(ui::EF_CONTROL_DOWN), KeyLControl::Released()}},
{mojom::StaticShortcutAction::kPreviousPage, KeyBrowserBack::Typed()},
{mojom::StaticShortcutAction::kNextPage, KeyBrowserForward::Typed()},
})));
TEST_F(StaticShortcutActionRewritingTest, StaticShortcutDisableMouseRewriting) {
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"",
mojom::Button::NewCustomizableButton(mojom::CustomizableButton::kForward),
mojom::RemappingAction::NewStaticShortcutAction(
mojom::StaticShortcutAction::kDisable)));
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"", mojom::Button::NewVkey(ui::VKEY_A),
mojom::RemappingAction::NewStaticShortcutAction(
mojom::StaticShortcutAction::kDisable)));
EXPECT_EQ(std::vector<TestEventVariant>{}, RunRewriter({KeyA::Typed()}));
EXPECT_EQ(std::vector<TestEventVariant>{},
RunRewriter({ButtonForward::Typed()}));
}
TEST_P(StaticShortcutActionRewritingTest, StaticShortcutMouseRewriting) {
const auto& [static_shortcut_action, expected_key_events] = GetParam();
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"",
mojom::Button::NewCustomizableButton(mojom::CustomizableButton::kForward),
mojom::RemappingAction::NewStaticShortcutAction(static_shortcut_action)));
EXPECT_EQ(expected_key_events, (RunRewriter(ButtonForward::Typed())));
}
TEST_P(StaticShortcutActionRewritingTest, StaticShortcutMouseWheelRewriting) {
const auto& [static_shortcut_action, expected_key_events] = GetParam();
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"",
mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kScrollLeft),
mojom::RemappingAction::NewStaticShortcutAction(static_shortcut_action)));
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"",
mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kScrollRight),
mojom::RemappingAction::NewStaticShortcutAction(static_shortcut_action)));
EXPECT_EQ(expected_key_events, (RunRewriter(ScrollLeft::Typed())));
EXPECT_EQ(expected_key_events, (RunRewriter(ScrollRight::Typed())));
}
TEST_P(StaticShortcutActionRewritingTest,
StaticShortcutGraphicsTabletRewriting) {
const auto& [static_shortcut_action, expected_key_events] = GetParam();
graphics_tablet_settings_->pen_button_remappings.push_back(
mojom::ButtonRemapping::New(
"",
mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kForward),
mojom::RemappingAction::NewStaticShortcutAction(
static_shortcut_action)));
graphics_tablet_settings_->tablet_button_remappings.push_back(
mojom::ButtonRemapping::New(
"",
mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kBack),
mojom::RemappingAction::NewStaticShortcutAction(
static_shortcut_action)));
EXPECT_EQ(expected_key_events,
(RunRewriter(ButtonForward::Typed(), ui::EF_NONE,
kGraphicsTabletDeviceId)));
EXPECT_EQ(expected_key_events, (RunRewriter(ButtonBack::Typed(), ui::EF_NONE,
kGraphicsTabletDeviceId)));
}
class StaticShortcutActionMouseButtonRewritingTest
: public PeripheralCustomizationEventRewriterTest,
public testing::WithParamInterface<
std::tuple<mojom::StaticShortcutAction,
std::vector<TestEventVariant>>> {};
INSTANTIATE_TEST_SUITE_P(
All,
StaticShortcutActionMouseButtonRewritingTest,
testing::ValuesIn(std::vector<std::tuple<mojom::StaticShortcutAction,
std::vector<TestEventVariant>>>{
{mojom::StaticShortcutAction::kLeftClick, ButtonLeft::Typed()},
{mojom::StaticShortcutAction::kRightClick, ButtonRight::Typed()},
{mojom::StaticShortcutAction::kMiddleClick, ButtonMiddle::Typed()},
}));
TEST_P(StaticShortcutActionMouseButtonRewritingTest, RewriteEvent) {
const auto [static_shortcut_action, expected_events] = GetParam();
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"",
mojom::Button::NewCustomizableButton(mojom::CustomizableButton::kForward),
mojom::RemappingAction::NewStaticShortcutAction(static_shortcut_action)));
EXPECT_EQ(expected_events, RunRewriter({ButtonForward::Typed()}));
}
TEST_P(StaticShortcutActionMouseButtonRewritingTest, ScrollEventRewriteEvent) {
const auto [static_shortcut_action, expected_events] = GetParam();
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"",
mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kScrollLeft),
mojom::RemappingAction::NewStaticShortcutAction(static_shortcut_action)));
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"",
mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kScrollRight),
mojom::RemappingAction::NewStaticShortcutAction(static_shortcut_action)));
EXPECT_EQ(expected_events, RunRewriter({ScrollLeft::Typed()}));
EXPECT_EQ(expected_events, RunRewriter({ScrollRight::Typed()}));
}
TEST_P(StaticShortcutActionMouseButtonRewritingTest, KeyEventRewrite) {
auto [static_shortcut_action, expected_events] = GetParam();
mouse_settings_->button_remappings.push_back(mojom::ButtonRemapping::New(
"", mojom::Button::NewVkey(ui::VKEY_A),
mojom::RemappingAction::NewStaticShortcutAction(static_shortcut_action)));
for (auto& event : expected_events) {
auto* test_mouse_event = std::get_if<TestMouseEvent>(&event);
ASSERT_TRUE(test_mouse_event);
test_mouse_event->flags = test_mouse_event->flags | ui::EF_COMMAND_DOWN;
}
EXPECT_EQ(expected_events, RunRewriter({KeyA::Typed()}, ui::EF_COMMAND_DOWN));
}
} // namespace ash