// 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 <linux/input.h>
#include <iterator>
#include <memory>
#include <optional>
#include "ash/accelerators/accelerator_controller_impl.h"
#include "ash/constants/ash_features.h"
#include "ash/events/event_rewriter_controller_impl.h"
#include "ash/public/cpp/accelerators_util.h"
#include "ash/public/cpp/input_device_settings_controller.h"
#include "ash/public/mojom/input_device_settings.mojom-forward.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/system/input_device_settings/input_device_settings_controller_impl.h"
#include "ash/system/input_device_settings/input_device_settings_logging.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/containers/fixed_flat_map.h"
#include "base/containers/span.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "ui/aura/env.h"
#include "ui/base/accelerators/ash/right_alt_event_property.h"
#include "ui/base/ui_base_features.h"
#include "ui/display/manager/display_manager.h"
#include "ui/display/screen.h"
#include "ui/events/ash/mojom/modifier_key.mojom-shared.h"
#include "ui/events/event.h"
#include "ui/events/event_constants.h"
#include "ui/events/event_dispatcher.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.h"
#include "ui/events/ozone/layout/keyboard_layout_engine_manager.h"
#include "ui/events/types/event_type.h"
#include "ui/gfx/geometry/point_f.h"
namespace ash {
namespace {
using RemappingActionResult =
PeripheralCustomizationEventRewriter::RemappingActionResult;
constexpr int kMouseRemappableFlags = ui::EF_BACK_MOUSE_BUTTON |
ui::EF_FORWARD_MOUSE_BUTTON |
ui::EF_MIDDLE_MOUSE_BUTTON;
constexpr int kGraphicsTabletRemappableFlags =
ui::EF_RIGHT_MOUSE_BUTTON | ui::EF_BACK_MOUSE_BUTTON |
ui::EF_FORWARD_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON;
constexpr auto kStaticActionToMouseButtonFlag =
base::MakeFixedFlatMap<mojom::StaticShortcutAction, ui::EventFlags>({
{mojom::StaticShortcutAction::kLeftClick, ui::EF_LEFT_MOUSE_BUTTON},
{mojom::StaticShortcutAction::kRightClick, ui::EF_RIGHT_MOUSE_BUTTON},
{mojom::StaticShortcutAction::kMiddleClick, ui::EF_MIDDLE_MOUSE_BUTTON},
});
mojom::KeyEvent GetStaticShortcutAction(mojom::StaticShortcutAction action) {
mojom::KeyEvent key_event;
switch (action) {
case mojom::StaticShortcutAction::kDisable:
case mojom::StaticShortcutAction::kLeftClick:
case mojom::StaticShortcutAction::kRightClick:
case mojom::StaticShortcutAction::kMiddleClick:
NOTREACHED();
case mojom::StaticShortcutAction::kCopy:
key_event =
mojom::KeyEvent(ui::VKEY_C, static_cast<int>(ui::DomCode::US_C),
static_cast<int>(ui::DomKey::FromCharacter('c')),
ui::EF_CONTROL_DOWN, /*key_display=*/"");
break;
case mojom::StaticShortcutAction::kPaste:
key_event =
mojom::KeyEvent(ui::VKEY_V, static_cast<int>(ui::DomCode::US_V),
static_cast<int>(ui::DomKey::FromCharacter('v')),
ui::EF_CONTROL_DOWN, /*key_display=*/"");
break;
case mojom::StaticShortcutAction::kUndo:
key_event =
mojom::KeyEvent(ui::VKEY_Z, static_cast<int>(ui::DomCode::US_Z),
static_cast<int>(ui::DomKey::FromCharacter('z')),
ui::EF_CONTROL_DOWN, /*key_display=*/"");
break;
case mojom::StaticShortcutAction::kRedo:
key_event = mojom::KeyEvent(
ui::VKEY_Z, static_cast<int>(ui::DomCode::US_Z),
static_cast<int>(ui::DomKey::FromCharacter('z')),
ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN, /*key_display=*/"");
break;
case mojom::StaticShortcutAction::kZoomIn:
key_event = mojom::KeyEvent(
ui::VKEY_OEM_PLUS, static_cast<int>(ui::DomCode::EQUAL),
static_cast<int>(ui::DomKey::FromCharacter('=')), ui::EF_CONTROL_DOWN,
/*key_display=*/"");
break;
case mojom::StaticShortcutAction::kZoomOut:
key_event = mojom::KeyEvent(
ui::VKEY_OEM_MINUS, static_cast<int>(ui::DomCode::MINUS),
static_cast<int>(ui::DomKey::FromCharacter('-')), ui::EF_CONTROL_DOWN,
/*key_display=*/"");
break;
case mojom::StaticShortcutAction::kPreviousPage:
key_event = mojom::KeyEvent(ui::VKEY_BROWSER_BACK,
static_cast<int>(ui::DomCode::BROWSER_BACK),
static_cast<int>(ui::DomKey::BROWSER_BACK),
ui::EF_NONE, /*key_display=*/"");
break;
case mojom::StaticShortcutAction::kNextPage:
key_event =
mojom::KeyEvent(ui::VKEY_BROWSER_FORWARD,
static_cast<int>(ui::DomCode::BROWSER_FORWARD),
static_cast<int>(ui::DomKey::BROWSER_FORWARD),
ui::EF_NONE, /*key_display=*/"");
break;
}
return key_event;
}
int ConvertKeyCodeToFlags(ui::KeyboardCode key_code) {
switch (key_code) {
case ui::VKEY_LWIN:
case ui::VKEY_RWIN:
return ui::EF_COMMAND_DOWN;
case ui::VKEY_CONTROL:
case ui::VKEY_RCONTROL:
return ui::EF_CONTROL_DOWN;
case ui::VKEY_SHIFT:
case ui::VKEY_LSHIFT:
case ui::VKEY_RSHIFT:
return ui::EF_SHIFT_DOWN;
case ui::VKEY_MENU:
case ui::VKEY_RMENU:
return ui::EF_ALT_DOWN;
default:
return ui::EF_NONE;
}
}
int ConvertModifierKeyToFlags(ui::mojom::ModifierKey modifier_key) {
switch (modifier_key) {
case ui::mojom::ModifierKey::kMeta:
return ui::EF_COMMAND_DOWN;
case ui::mojom::ModifierKey::kControl:
return ui::EF_CONTROL_DOWN;
case ui::mojom::ModifierKey::kAlt:
return ui::EF_ALT_DOWN;
case ui::mojom::ModifierKey::kFunction:
return ui::EF_FUNCTION_DOWN;
case ui::mojom::ModifierKey::kEscape:
case ui::mojom::ModifierKey::kBackspace:
case ui::mojom::ModifierKey::kAssistant:
case ui::mojom::ModifierKey::kCapsLock:
case ui::mojom::ModifierKey::kVoid:
case ui::mojom::ModifierKey::kIsoLevel5ShiftMod3:
case ui::mojom::ModifierKey::kRightAlt:
return ui::EF_NONE;
}
}
bool AreScrollWheelEventRewritesAllowed(
mojom::CustomizationRestriction customization_restriction) {
switch (customization_restriction) {
case mojom::CustomizationRestriction::kDisallowCustomizations:
case mojom::CustomizationRestriction::kDisableKeyEventRewrites:
case mojom::CustomizationRestriction::kAllowAlphabetKeyEventRewrites:
case mojom::CustomizationRestriction::
kAllowAlphabetOrNumberKeyEventRewrites:
case mojom::CustomizationRestriction::kAllowTabEventRewrites:
case mojom::CustomizationRestriction::kAllowFKeyRewrites:
return false;
case mojom::CustomizationRestriction::kAllowHorizontalScrollWheelRewrites:
case mojom::CustomizationRestriction::kAllowCustomizations:
return true;
}
}
template <typename Iterator>
std::vector<std::unique_ptr<ui::Event>> RewriteModifiers(
const ui::Event& event,
uint32_t modifiers_to_press,
uint32_t modifiers_already_pressed,
bool pressed,
Iterator begin,
Iterator end) {
std::vector<std::unique_ptr<ui::Event>> rewritten_events;
// Keeps track of the modifier flags that must be applied to the next
// rewritten event. For key presses, this should start at
// `0` and go to `modifiers_to_press`. For releases, it should do the opposite
// as we start will all modifiers down.
uint32_t modifiers_pressed = pressed ? 0u : modifiers_to_press;
for (auto iter = begin; iter != end; iter++) {
if (!(iter->flag & modifiers_to_press)) {
continue;
}
if (pressed) {
modifiers_pressed += iter->flag;
} else {
modifiers_pressed -= iter->flag;
}
const ui::EventType pressed_or_released_type =
pressed ? ui::EventType::kKeyPressed : ui::EventType::kKeyReleased;
auto rewritten_modifier_event = std::make_unique<ui::KeyEvent>(
pressed_or_released_type, iter->key_code, iter->dom_code,
modifiers_pressed | modifiers_already_pressed |
ui::EF_IS_CUSTOMIZED_FROM_BUTTON,
event.time_stamp());
rewritten_modifier_event->set_source_device_id(event.source_device_id());
rewritten_events.push_back(std::move(rewritten_modifier_event));
}
// Verify our modifier rewriting worked as expected.
if (pressed) {
CHECK_EQ(modifiers_to_press, modifiers_pressed);
} else {
CHECK_EQ(0u, modifiers_pressed);
}
return rewritten_events;
}
std::vector<std::unique_ptr<ui::Event>> GenerateFullKeyEventSequence(
const ui::Event& event,
uint32_t modifiers_to_press,
uint32_t modifiers_already_pressed,
bool pressed,
std::unique_ptr<ui::Event> rewritten_event) {
static constexpr struct {
ui::KeyboardCode key_code;
ui::DomCode dom_code;
ui::EventFlags flag;
} kModifiers[] = {
{ui::VKEY_LWIN, ui::DomCode::META_LEFT, ui::EF_COMMAND_DOWN},
{ui::VKEY_CONTROL, ui::DomCode::CONTROL_LEFT, ui::EF_CONTROL_DOWN},
{ui::VKEY_MENU, ui::DomCode::ALT_LEFT, ui::EF_ALT_DOWN},
{ui::VKEY_SHIFT, ui::DomCode::SHIFT_LEFT, ui::EF_SHIFT_DOWN},
};
static constexpr auto kModifierSpan = base::make_span(kModifiers);
CHECK(rewritten_event);
std::vector<std::unique_ptr<ui::Event>> rewritten_events;
if (pressed) {
// If it is a key press, we rewrite the modifiers in forward order and then
// add the main rewritten event after the press events.
rewritten_events =
RewriteModifiers(event, modifiers_to_press, modifiers_already_pressed,
pressed, kModifierSpan.begin(), kModifierSpan.end());
rewritten_events.push_back(std::move(rewritten_event));
} else {
// For key releases, we add the main rewritten event first and then append
// the rewritten modifiers after. The modifiers must be rewritten in reverse
// order from the `kModifiers` array.
rewritten_events.push_back(std::move(rewritten_event));
auto modifier_events =
RewriteModifiers(event, modifiers_to_press, modifiers_already_pressed,
pressed, kModifierSpan.rbegin(), kModifierSpan.rend());
rewritten_events.insert(rewritten_events.end(),
std::make_move_iterator(modifier_events.begin()),
std::make_move_iterator(modifier_events.end()));
}
return rewritten_events;
}
std::vector<std::unique_ptr<ui::Event>> RewriteEventToKeyEvents(
const ui::Event& event,
const mojom::KeyEvent& key_event,
int flags_to_release,
bool key_press) {
const ui::EventType event_type =
key_press ? ui::EventType::kKeyPressed : ui::EventType::kKeyReleased;
const uint32_t modifier_key_flag = ConvertKeyCodeToFlags(key_event.vkey);
// `other_modifiers_to_apply` symbolizes the flags that are not handled by
// `modifier_key_flag` or flags that are already included in the event. The
// flags remaining in `other_modifiers_to_apply` are the modifiers we must
// write events for in addition to the main key event.
// On release events, we should release the flags passed in instead of our
// computed set of flags in case some modifiers were released since we
// originally pressed them down.
// Mouse wheel is an exception here since presses and releases happen
// atomically. Therefore always release every key you originally pressed.
const uint32_t other_modifiers_to_apply =
(key_press || event.type() == ui::EventType::kMousewheel)
? (key_event.modifiers & ~event.flags() & ~modifier_key_flag)
: (flags_to_release & ~event.flags() & ~modifier_key_flag);
uint32_t applied_modifier_key_flag = modifier_key_flag;
// Do not apply modifier flags when the key is a modifier and it is a release
// event. Modifier keys do not apply their flag on release.
if (event_type == ui::EventType::kKeyReleased &&
key_event.modifiers == modifier_key_flag) {
applied_modifier_key_flag = ui::EF_NONE;
}
const bool is_rewrite_to_right_alt = key_event.vkey == ui::VKEY_RIGHT_ALT;
ui::KeyboardCode key_code = key_event.vkey;
if (is_rewrite_to_right_alt) {
key_code = ui::VKEY_ASSISTANT;
}
// Use ui::DomKey::NONE so the DomKey is recomputed with applicable flags.
auto rewritten_event = std::make_unique<ui::KeyEvent>(
event_type, key_code, static_cast<ui::DomCode>(key_event.dom_code),
applied_modifier_key_flag | other_modifiers_to_apply | event.flags() |
ui::EF_IS_CUSTOMIZED_FROM_BUTTON,
ui::DomKey::NONE, event.time_stamp());
rewritten_event->set_source_device_id(event.source_device_id());
if (is_rewrite_to_right_alt) {
ui::SetRightAltProperty(rewritten_event.get());
}
return GenerateFullKeyEventSequence(
event, other_modifiers_to_apply, event.flags(),
/*pressed=*/event_type == ui::EventType::kKeyPressed,
std::move(rewritten_event));
}
std::vector<std::unique_ptr<ui::Event>> RewriteEventToKeyEvents(
const ui::Event& event,
const mojom::KeyEvent& key_event,
int flags_to_release) {
// If the original event is a mouse scroll event, we must generate both a
// press and release from the single event.
const bool should_press_and_release =
event.type() == ui::EventType::kMousewheel;
const bool key_press = should_press_and_release ||
event.type() == ui::EventType::kMousePressed ||
event.type() == ui::EventType::kKeyPressed;
std::vector<std::unique_ptr<ui::Event>> rewritten_events =
RewriteEventToKeyEvents(event, key_event, flags_to_release, key_press);
if (should_press_and_release) {
std::vector<std::unique_ptr<ui::Event>> release_rewritten_events =
RewriteEventToKeyEvents(event, key_event, flags_to_release, false);
rewritten_events.reserve(rewritten_events.size() +
release_rewritten_events.size());
rewritten_events.insert(
rewritten_events.end(),
std::make_move_iterator(release_rewritten_events.begin()),
std::make_move_iterator(release_rewritten_events.end()));
}
return rewritten_events;
}
// TODO(b/339754921): Add integration test for when the display is rotated and
// adjusted via overscan boundaries.
gfx::PointF GetCurrentCursorLocation() {
auto* screen = display::Screen::GetScreen();
CHECK(screen);
const display::Display display =
screen->GetDisplayNearestPoint(screen->GetCursorScreenPoint());
// Returns the physical point on the display not considering display
// orientation.
gfx::PointF physical_screen_location =
gfx::PointF(screen->GetCursorScreenPoint() -
display.bounds().origin().OffsetFromOrigin());
// Transpose/flip the point based on the orientation of device.
auto& display_size = display.size();
switch (display.rotation()) {
case display::Display::ROTATE_0:
break;
case display::Display::ROTATE_90:
physical_screen_location.Transpose();
physical_screen_location.set_x(display_size.height() -
physical_screen_location.x());
break;
case display::Display::ROTATE_180: {
physical_screen_location.set_x(display_size.width() -
physical_screen_location.x());
physical_screen_location.set_y(display_size.height() -
physical_screen_location.y());
break;
}
case display::Display::ROTATE_270:
physical_screen_location.Transpose();
physical_screen_location.set_y(display_size.width() -
physical_screen_location.y());
break;
}
// Scale the location to match the users chosen scaling factor then apply
// overscan insets. Overscan insets are stored as post-scaled values so they
// must be applied after scaling the original location.
auto scaled_location =
gfx::ScalePoint(physical_screen_location, display.device_scale_factor());
auto overscan_insets =
Shell::Get()->display_manager()->GetOverscanInsets(display.id());
scaled_location.set_x(scaled_location.x() + overscan_insets.left());
scaled_location.set_y(scaled_location.y() + overscan_insets.top());
return scaled_location;
}
std::vector<std::unique_ptr<ui::Event>> RewriteEventToMouseButtonEvents(
const ui::Event& event,
mojom::StaticShortcutAction action) {
// If the original event is a mouse scroll event, we must generate both a
// press and release from the single event.
const bool should_press_and_release =
event.type() == ui::EventType::kMousewheel;
std::vector<std::unique_ptr<ui::Event>> rewritten_events;
auto flag_iter = kStaticActionToMouseButtonFlag.find(action);
CHECK(flag_iter != kStaticActionToMouseButtonFlag.end());
const int characteristic_flag = flag_iter->second;
const gfx::PointF location = GetCurrentCursorLocation();
const ui::EventType type = (should_press_and_release ||
event.type() == ui::EventType::kMousePressed ||
event.type() == ui::EventType::kKeyPressed)
? ui::EventType::kMousePressed
: ui::EventType::kMouseReleased;
rewritten_events.push_back(std::make_unique<ui::MouseEvent>(
type, location, location, event.time_stamp(),
event.flags() | characteristic_flag, characteristic_flag));
rewritten_events.back()->set_source_device_id(event.source_device_id());
if (should_press_and_release) {
rewritten_events.push_back(std::make_unique<ui::MouseEvent>(
ui::EventType::kMouseReleased, location, location, event.time_stamp(),
event.flags() | characteristic_flag, characteristic_flag));
rewritten_events.back()->set_source_device_id(event.source_device_id());
}
return rewritten_events;
}
bool IsMouseButtonEvent(const ui::MouseEvent& mouse_event) {
return mouse_event.type() == ui::EventType::kMousePressed ||
mouse_event.type() == ui::EventType::kMouseReleased;
}
bool IsMouseRemappableButton(int flags) {
return (flags & kMouseRemappableFlags) != 0;
}
bool IsGraphicsTabletRemappableButton(int flags) {
return (flags & kGraphicsTabletRemappableFlags) != 0;
}
int GetRemappableMouseEventFlags(
PeripheralCustomizationEventRewriter::DeviceType device_type) {
switch (device_type) {
case PeripheralCustomizationEventRewriter::DeviceType::kMouse:
return kMouseRemappableFlags;
case PeripheralCustomizationEventRewriter::DeviceType::kGraphicsTablet:
return kGraphicsTabletRemappableFlags;
}
}
mojom::ButtonPtr GetButtonFromMouseEvent(const ui::MouseEvent& mouse_event) {
switch (mouse_event.changed_button_flags()) {
case ui::EF_LEFT_MOUSE_BUTTON:
return mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kLeft);
case ui::EF_RIGHT_MOUSE_BUTTON:
return mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kRight);
case ui::EF_MIDDLE_MOUSE_BUTTON:
return mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kMiddle);
case ui::EF_FORWARD_MOUSE_BUTTON:
case ui::EF_BACK_MOUSE_BUTTON:
break;
}
CHECK(mouse_event.changed_button_flags() == ui::EF_FORWARD_MOUSE_BUTTON ||
mouse_event.changed_button_flags() == ui::EF_BACK_MOUSE_BUTTON);
auto linux_key_code = ui::GetForwardBackMouseButtonProperty(mouse_event);
if (!linux_key_code) {
return (mouse_event.changed_button_flags() == ui::EF_FORWARD_MOUSE_BUTTON)
? mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kForward)
: mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kBack);
}
switch (*linux_key_code) {
case BTN_FORWARD:
return mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kForward);
case BTN_BACK:
return mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kBack);
case BTN_SIDE:
return mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kSide);
case BTN_EXTRA:
return mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kExtra);
}
NOTREACHED();
}
// Returns the customizable button for the scroll wheel event. Will return null
// if the scroll event is not a horizontal scroll event.
mojom::ButtonPtr GetButtonFromMouseWheelEvent(
const ui::MouseWheelEvent& mouse_wheel_event) {
if (mouse_wheel_event.x_offset() == 0) {
return nullptr;
}
if (mouse_wheel_event.x_offset() > 0) {
return mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kScrollRight);
}
return mojom::Button::NewCustomizableButton(
mojom::CustomizableButton::kScrollLeft);
}
int ConvertButtonToFlags(const mojom::Button& button) {
if (button.is_customizable_button()) {
switch (button.get_customizable_button()) {
case mojom::CustomizableButton::kLeft:
return ui::EF_LEFT_MOUSE_BUTTON;
case mojom::CustomizableButton::kRight:
return ui::EF_RIGHT_MOUSE_BUTTON;
case mojom::CustomizableButton::kMiddle:
return ui::EF_MIDDLE_MOUSE_BUTTON;
case mojom::CustomizableButton::kForward:
case mojom::CustomizableButton::kExtra:
return ui::EF_FORWARD_MOUSE_BUTTON;
case mojom::CustomizableButton::kBack:
case mojom::CustomizableButton::kSide:
return ui::EF_BACK_MOUSE_BUTTON;
case mojom::CustomizableButton::kScrollLeft:
case mojom::CustomizableButton::kScrollRight:
return ui::EF_NONE;
}
}
if (button.is_vkey()) {
return ConvertKeyCodeToFlags(button.get_vkey());
}
return ui::EF_NONE;
}
std::optional<ui::mojom::ModifierKey> ConvertDomCodeToModifierKey(
ui::DomCode code) {
switch (code) {
case ui::DomCode::META_LEFT:
case ui::DomCode::META_RIGHT:
return ui::mojom::ModifierKey::kMeta;
case ui::DomCode::CONTROL_LEFT:
case ui::DomCode::CONTROL_RIGHT:
return ui::mojom::ModifierKey::kControl;
case ui::DomCode::ALT_LEFT:
case ui::DomCode::ALT_RIGHT:
return ui::mojom::ModifierKey::kAlt;
case ui::DomCode::CAPS_LOCK:
return ui::mojom::ModifierKey::kCapsLock;
case ui::DomCode::BACKSPACE:
return ui::mojom::ModifierKey::kBackspace;
case ui::DomCode::LAUNCH_ASSISTANT:
return ui::mojom::ModifierKey::kAssistant;
case ui::DomCode::ESCAPE:
return ui::mojom::ModifierKey::kEscape;
default:
return std::nullopt;
}
}
std::optional<PeripheralCustomizationEventRewriter::RemappingActionResult>
GetRemappingActionFromMouseSettings(const mojom::Button& button,
const mojom::MouseSettings& settings) {
const auto button_remapping_iter = base::ranges::find(
settings.button_remappings, button,
[](const mojom::ButtonRemappingPtr& entry) { return *entry->button; });
if (button_remapping_iter != settings.button_remappings.end()) {
const mojom::ButtonRemapping& button_remapping = *(*button_remapping_iter);
if (!button_remapping.remapping_action) {
return std::nullopt;
}
auto result = PeripheralCustomizationEventRewriter::RemappingActionResult(
*button_remapping.remapping_action,
InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType::
kMouse);
return result;
}
return std::nullopt;
}
std::optional<PeripheralCustomizationEventRewriter::RemappingActionResult>
GetRemappingActionFromGraphicsTabletSettings(
const mojom::Button& button,
const mojom::GraphicsTabletSettings& settings) {
const auto pen_button_remapping_iter = base::ranges::find(
settings.pen_button_remappings, button,
[](const mojom::ButtonRemappingPtr& entry) { return *entry->button; });
if (pen_button_remapping_iter != settings.pen_button_remappings.end()) {
const mojom::ButtonRemapping& button_remapping =
*(*pen_button_remapping_iter);
if (!button_remapping.remapping_action) {
return std::nullopt;
}
auto pen_action =
PeripheralCustomizationEventRewriter::RemappingActionResult(
*button_remapping.remapping_action,
InputDeviceSettingsMetricsManager::
PeripheralCustomizationMetricsType::kGraphicsTabletPen);
return std::move(pen_action);
}
const auto tablet_button_remapping_iter = base::ranges::find(
settings.tablet_button_remappings, button,
[](const mojom::ButtonRemappingPtr& entry) { return *entry->button; });
if (tablet_button_remapping_iter != settings.tablet_button_remappings.end()) {
const mojom::ButtonRemapping& button_remapping =
*(*tablet_button_remapping_iter);
if (!button_remapping.remapping_action) {
return std::nullopt;
}
auto tablet_action =
PeripheralCustomizationEventRewriter::RemappingActionResult(
*button_remapping.remapping_action,
InputDeviceSettingsMetricsManager::
PeripheralCustomizationMetricsType::kGraphicsTablet);
return std::move(tablet_action);
}
return std::nullopt;
}
int GetRemappedModifiersFromMouseSettings(
const mojom::MouseSettings& settings) {
int modifiers = 0;
for (const auto& button_remapping : settings.button_remappings) {
if (button_remapping->remapping_action) {
modifiers |= ConvertButtonToFlags(*button_remapping->button);
}
}
return modifiers;
}
int GetRemappedModifiersFromGraphicsTabletSettings(
const mojom::GraphicsTabletSettings& settings) {
int modifiers = 0;
for (const auto& button_remapping : settings.pen_button_remappings) {
modifiers |= ConvertButtonToFlags(*button_remapping->button);
}
for (const auto& button_remapping : settings.tablet_button_remappings) {
modifiers |= ConvertButtonToFlags(*button_remapping->button);
}
return modifiers;
}
// Verify if the keyboard code is an alpha key or punctuation.
bool IsAlphaKeyEvent(const ui::KeyEvent& key_event) {
return GetKeyInputTypeFromKeyEvent(key_event) ==
AcceleratorKeyInputType::kAlpha;
}
// Verify if the keyboard code is a number.
bool IsNumberKeyEvent(const ui::KeyEvent& key_event) {
return GetKeyInputTypeFromKeyEvent(key_event) ==
AcceleratorKeyInputType::kDigit;
}
void RecordMouseInvalidKeyPressed(InputDeviceSettingsController* controller,
const ui::KeyEvent& key_event) {
if (key_event.type() == ui::EventType::kKeyReleased ||
key_event.is_repeat()) {
return;
}
auto* mouse = controller->GetMouse(key_event.source_device_id());
auto* keyboard = controller->GetKeyboard(key_event.source_device_id());
if (!mouse) {
return;
}
if (mouse && keyboard) {
base::UmaHistogramSparse("ChromeOS.Inputs.Mouse.InvalidRegistration.Combo",
key_event.key_code());
return;
}
if (mouse) {
base::UmaHistogramSparse(
"ChromeOS.Inputs.Mouse.InvalidRegistration.NonCombo",
key_event.key_code());
}
LOG(WARNING) << base::StringPrintf(
"Mouse '%s' with identifier '%s' attempted to register keyboard code "
"'%04x'.",
mouse->name.c_str(), mouse->device_key.c_str(), key_event.key_code());
base::UmaHistogramSparse("ChromeOS.Inputs.Mouse.InvalidRegistration",
key_event.key_code());
}
} // namespace
// Compares the `DeviceIdButton` struct based on first the device id, and then
// the button stored within the `DeviceIdButton` struct.
bool operator<(
const PeripheralCustomizationEventRewriter::DeviceIdButton& left,
const PeripheralCustomizationEventRewriter::DeviceIdButton& right) {
if (right.device_id != left.device_id) {
return left.device_id < right.device_id;
}
// If both are VKeys, compare them.
if (left.button->is_vkey() && right.button->is_vkey()) {
return left.button->get_vkey() < right.button->get_vkey();
}
// If both are customizable buttons, compare them.
if (left.button->is_customizable_button() &&
right.button->is_customizable_button()) {
return left.button->get_customizable_button() <
right.button->get_customizable_button();
}
// Otherwise, return true if the lhs is a VKey as they mismatch and VKeys
// should be considered less than customizable buttons.
return left.button->is_vkey();
}
PeripheralCustomizationEventRewriter::DeviceIdButton::DeviceIdButton(
int device_id,
mojom::ButtonPtr button)
: device_id(device_id), button(std::move(button)) {}
PeripheralCustomizationEventRewriter::DeviceIdButton::DeviceIdButton(
DeviceIdButton&& device_id_button)
: device_id(device_id_button.device_id),
button(std::move(device_id_button.button)) {}
PeripheralCustomizationEventRewriter::DeviceIdButton::~DeviceIdButton() =
default;
PeripheralCustomizationEventRewriter::DeviceIdButton&
PeripheralCustomizationEventRewriter::DeviceIdButton::operator=(
PeripheralCustomizationEventRewriter::DeviceIdButton&& device_id_button) =
default;
PeripheralCustomizationEventRewriter::RemappingActionResult::
RemappingActionResult(
mojom::RemappingAction& remapping_action,
InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType
peripheral_kind)
: remapping_action(remapping_action), peripheral_kind(peripheral_kind) {}
PeripheralCustomizationEventRewriter::RemappingActionResult::
RemappingActionResult(RemappingActionResult&& result)
: remapping_action(std::move(result.remapping_action)),
peripheral_kind(result.peripheral_kind) {}
PeripheralCustomizationEventRewriter::RemappingActionResult::
~RemappingActionResult() = default;
PeripheralCustomizationEventRewriter::PeripheralCustomizationEventRewriter(
InputDeviceSettingsController* input_device_settings_controller)
: input_device_settings_controller_(input_device_settings_controller) {
metrics_manager_ = std::make_unique<InputDeviceSettingsMetricsManager>();
}
PeripheralCustomizationEventRewriter::~PeripheralCustomizationEventRewriter() =
default;
std::optional<PeripheralCustomizationEventRewriter::DeviceType>
PeripheralCustomizationEventRewriter::GetDeviceTypeToObserve(int device_id) {
if (mice_to_observe_.contains(device_id)) {
return DeviceType::kMouse;
}
if (graphics_tablets_to_observe_.contains(device_id)) {
return DeviceType::kGraphicsTablet;
}
return std::nullopt;
}
void PeripheralCustomizationEventRewriter::StartObservingMouse(
int device_id,
mojom::CustomizationRestriction customization_restriction) {
mice_to_observe_.insert_or_assign(device_id, customization_restriction);
}
void PeripheralCustomizationEventRewriter::StartObservingGraphicsTablet(
int device_id,
mojom::CustomizationRestriction customization_restriction) {
graphics_tablets_to_observe_.insert_or_assign(device_id,
customization_restriction);
}
void PeripheralCustomizationEventRewriter::StopObserving() {
graphics_tablets_to_observe_.clear();
mice_to_observe_.clear();
}
bool PeripheralCustomizationEventRewriter::NotifyMouseWheelEventObserving(
const ui::MouseWheelEvent& mouse_wheel_event,
DeviceType device_type) {
const auto customization_restriction_iter =
mice_to_observe_.find(mouse_wheel_event.source_device_id());
if (customization_restriction_iter == mice_to_observe_.end()) {
return false;
}
auto customization_restriction = customization_restriction_iter->second;
if (!AreScrollWheelEventRewritesAllowed(customization_restriction)) {
return false;
}
const mojom::ButtonPtr button =
GetButtonFromMouseWheelEvent(mouse_wheel_event);
if (!button) {
return false;
}
switch (device_type) {
case DeviceType::kMouse:
input_device_settings_controller_->OnMouseButtonPressed(
mouse_wheel_event.source_device_id(), *button);
break;
case DeviceType::kGraphicsTablet:
input_device_settings_controller_->OnGraphicsTabletButtonPressed(
mouse_wheel_event.source_device_id(), *button);
break;
}
return true;
}
bool PeripheralCustomizationEventRewriter::NotifyMouseEventObserving(
const ui::MouseEvent& mouse_event,
DeviceType device_type) {
if (!IsMouseButtonEvent(mouse_event)) {
return false;
}
// Make sure the button is remappable for the current `device_type`.
switch (device_type) {
case DeviceType::kMouse:
if (!IsMouseRemappableButton(mouse_event.changed_button_flags())) {
return false;
}
break;
case DeviceType::kGraphicsTablet:
if (!IsGraphicsTabletRemappableButton(
mouse_event.changed_button_flags())) {
return false;
}
break;
}
if (mouse_event.type() != ui::EventType::kMousePressed) {
return true;
}
const auto button = GetButtonFromMouseEvent(mouse_event);
switch (device_type) {
case DeviceType::kMouse:
input_device_settings_controller_->OnMouseButtonPressed(
mouse_event.source_device_id(), *button);
break;
case DeviceType::kGraphicsTablet:
input_device_settings_controller_->OnGraphicsTabletButtonPressed(
mouse_event.source_device_id(), *button);
break;
}
return true;
}
bool PeripheralCustomizationEventRewriter::IsButtonCustomizable(
const ui::KeyEvent& key_event) {
const auto iter = mice_to_observe_.find(key_event.source_device_id());
if (iter == mice_to_observe().end()) {
return false;
}
const auto customization_restriction = iter->second;
// There are several cases for the customization restriction:
// 1. If restriction is kAllowCustomizations, mice are allowed to observe
// key events.
// 2. If restriction is kAllowAlphabetKeyEventRewrites, mice are allowed to
// observe only alphabet or punctuation events.
// 3. If restriction is kAllowAlphabetOrNumberKeyEventRewrites, mice are
// allowed to observe alphabet, punctuation, or number key event.
// 4. Mice are not allowed to observe key event in other cases.
switch (customization_restriction) {
case mojom::CustomizationRestriction::kAllowCustomizations:
return true;
case mojom::CustomizationRestriction::kAllowAlphabetKeyEventRewrites:
return IsAlphaKeyEvent(key_event);
case mojom::CustomizationRestriction::
kAllowAlphabetOrNumberKeyEventRewrites:
return IsAlphaKeyEvent(key_event) || IsNumberKeyEvent(key_event);
case mojom::CustomizationRestriction::kAllowTabEventRewrites:
return key_event.key_code() == ui::VKEY_TAB;
case mojom::CustomizationRestriction::kAllowFKeyRewrites:
return key_event.key_code() >= ui::VKEY_F1 &&
key_event.key_code() <= ui::VKEY_F15;
case mojom::CustomizationRestriction::kDisallowCustomizations:
case mojom::CustomizationRestriction::kDisableKeyEventRewrites:
case mojom::CustomizationRestriction::kAllowHorizontalScrollWheelRewrites:
return false;
}
}
bool PeripheralCustomizationEventRewriter::NotifyKeyEventObserving(
const ui::KeyEvent& key_event,
DeviceType device_type) {
if (device_type == DeviceType::kMouse && !IsButtonCustomizable(key_event)) {
RecordMouseInvalidKeyPressed(input_device_settings_controller_, key_event);
return false;
}
// Observers should only be notified on key presses.
if (key_event.type() != ui::EventType::kKeyPressed) {
return true;
}
const auto button = mojom::Button::NewVkey(key_event.key_code());
switch (device_type) {
case DeviceType::kMouse:
input_device_settings_controller_->OnMouseButtonPressed(
key_event.source_device_id(), *button);
break;
case DeviceType::kGraphicsTablet:
input_device_settings_controller_->OnGraphicsTabletButtonPressed(
key_event.source_device_id(), *button);
break;
}
return true;
}
bool PeripheralCustomizationEventRewriter::RewriteEventFromButton(
const ui::Event& event,
const mojom::Button& button,
std::vector<std::unique_ptr<ui::Event>>& rewritten_events) {
std::optional<RemappingActionResult> remapping_action_result =
GetRemappingAction(event.source_device_id(), button);
if (!remapping_action_result) {
return false;
}
auto remapping_action = remapping_action_result->remapping_action;
if (event.type() == ui::EventType::kKeyPressed ||
event.type() == ui::EventType::kMousePressed) {
metrics_manager_->RecordRemappingActionWhenButtonPressed(
*remapping_action, remapping_action_result->peripheral_kind);
}
auto id = event.source_device_id();
switch (remapping_action_result->peripheral_kind) {
case InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType::
kMouse:
PR_LOG(INFO, Feature::IDS) << GetMouseSettingsLog(
"Mouse button is pressed",
*(input_device_settings_controller_->GetMouse(id)));
break;
case InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType::
kGraphicsTablet:
PR_LOG(INFO, Feature::IDS) << GetGraphicsTabletSettingsLog(
"Graphics tablet button is pressed",
*(input_device_settings_controller_->GetGraphicsTablet(id)));
break;
case InputDeviceSettingsMetricsManager::PeripheralCustomizationMetricsType::
kGraphicsTabletPen:
PR_LOG(INFO, Feature::IDS) << GetGraphicsTabletSettingsLog(
"Graphics tablet pen button is pressed",
*(input_device_settings_controller_->GetGraphicsTablet(id)));
break;
}
if (remapping_action->is_accelerator_action()) {
if (event.type() == ui::EventType::kKeyPressed ||
event.type() == ui::EventType::kMousePressed ||
event.type() == ui::EventType::kMousewheel) {
// Every accelerator supported by peripheral customization is not impacted
// by the accelerator passed. Therefore, passing an empty accelerator will
// cause no issues.
Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
remapping_action->get_accelerator_action(), /*accelerator=*/{});
}
return true;
}
// Get flags to release in rewriting key events.
int flags_to_release = 0;
auto iter =
device_button_to_flags_.find({event.source_device_id(), button.Clone()});
if (iter != device_button_to_flags_.end()) {
flags_to_release = iter->second;
}
if (remapping_action->is_key_event()) {
const auto& key_event = remapping_action->get_key_event();
auto entry = FindKeyCodeEntry(key_event->vkey);
// If no entry can be found, use the stored key_event struct.
if (!entry) {
rewritten_events =
RewriteEventToKeyEvents(event, *key_event, flags_to_release);
} else {
rewritten_events = RewriteEventToKeyEvents(
event,
mojom::KeyEvent(
entry->resulting_key_code, static_cast<int>(entry->dom_code),
static_cast<int>(entry->dom_key), key_event->modifiers, ""),
flags_to_release);
}
}
if (remapping_action->is_static_shortcut_action()) {
const auto static_action = remapping_action->get_static_shortcut_action();
if (static_action == mojom::StaticShortcutAction::kDisable) {
// Return true to discard the event.
return true;
}
if (kStaticActionToMouseButtonFlag.contains(static_action)) {
rewritten_events = RewriteEventToMouseButtonEvents(event, static_action);
} else {
rewritten_events = RewriteEventToKeyEvents(
event,
GetStaticShortcutAction(
remapping_action->get_static_shortcut_action()),
flags_to_release);
}
}
return false;
}
ui::EventDispatchDetails PeripheralCustomizationEventRewriter::RewriteKeyEvent(
const ui::KeyEvent& key_event,
const Continuation continuation) {
auto device_type_to_observe =
GetDeviceTypeToObserve(key_event.source_device_id());
if (device_type_to_observe) {
if (NotifyKeyEventObserving(key_event, *device_type_to_observe)) {
return DiscardEvent(continuation);
}
}
// Clone event and remove the already remapped modifiers and use this as the
// "source" key event for the rest of the rewriting.
std::unique_ptr<ui::Event> original_event_with_modifiers_removed =
CloneEvent(key_event);
RemoveRemappedModifiers(*original_event_with_modifiers_removed);
std::vector<std::unique_ptr<ui::Event>> rewritten_events;
mojom::ButtonPtr button = mojom::Button::NewVkey(key_event.key_code());
bool updated_button_map = false;
if (RewriteEventFromButton(*original_event_with_modifiers_removed, *button,
rewritten_events)) {
return DiscardEvent(continuation);
}
const bool event_rewritten = !rewritten_events.empty();
// Discard all "button" type events usually from graphics tablets.
if (!event_rewritten && key_event.key_code() >= ui::VKEY_BUTTON_0 &&
key_event.key_code() <= ui::VKEY_BUTTON_Z) {
return DiscardEvent(continuation);
}
// Add an event to our list to rewrite based on other pressed buttons.
if (rewritten_events.empty()) {
rewritten_events.push_back(
std::move(original_event_with_modifiers_removed));
}
// If the button was released, the pressed button map must be updated before
// applying remapped modifiers.
const ui::Event& last_rewritten_event = *rewritten_events.back();
if (event_rewritten &&
(last_rewritten_event.type() == ui::EventType::kMouseReleased ||
last_rewritten_event.type() == ui::EventType::kKeyReleased)) {
updated_button_map = true;
UpdatePressedButtonMap(std::move(button), key_event, rewritten_events);
}
// Remove flags from modifiers that are released on other devices.
if (!event_rewritten) {
UpdatePressedButtonMapFlags(key_event);
}
for (const auto& rewritten_event : rewritten_events) {
ApplyRemappedModifiers(*rewritten_event);
}
if (event_rewritten && !updated_button_map) {
UpdatePressedButtonMap(std::move(button), key_event, rewritten_events);
}
ui::EventDispatchDetails details;
for (const auto& rewritten_event : rewritten_events) {
details = SendEvent(continuation, rewritten_event.get());
}
return details;
}
void PeripheralCustomizationEventRewriter::UpdatePressedButtonMap(
mojom::ButtonPtr button,
const ui::Event& original_event,
const std::vector<std::unique_ptr<ui::Event>>& rewritten_events) {
// Scroll wheel events cannot affect other events modifiers since they do a
// full press/release sequence with the one event.
if (original_event.type() == ui::EventType::kMousewheel) {
return;
}
DeviceIdButton device_id_button_key =
DeviceIdButton{original_event.source_device_id(), std::move(button)};
// If the button is released, the entry must be removed from the map.
if (original_event.type() == ui::EventType::kMouseReleased ||
original_event.type() == ui::EventType::kKeyReleased) {
device_button_to_flags_.erase(std::move(device_id_button_key));
// Release all modifier flags on other currently pressed buttons.
for (const auto& rewritten_event : rewritten_events) {
if (rewritten_event->IsKeyEvent()) {
UpdatePressedButtonMapFlags(*rewritten_event->AsKeyEvent());
}
}
return;
}
// For each rewritten event, combine the flags that need to be applied to
// correctly handle the newly pressed event. This matters when pressing a
// modifier or a key with a combo of modifiers or when holding a rewritten
// mouse button.
ui::EventFlags event_flags = 0;
for (const auto& rewritten_event : rewritten_events) {
if (!rewritten_event) {
continue;
}
if (rewritten_event->IsKeyEvent()) {
const auto& key_event = *rewritten_event->AsKeyEvent();
event_flags |= ConvertKeyCodeToFlags(key_event.key_code());
continue;
}
if (rewritten_event->IsMouseEvent()) {
const auto& mouse_event = *rewritten_event->AsMouseEvent();
event_flags |= mouse_event.changed_button_flags();
continue;
}
}
if (!event_flags) {
return;
}
// Add the entry to the map with the flags that must be applied to other
// events.
device_button_to_flags_.insert_or_assign(std::move(device_id_button_key),
event_flags);
}
void PeripheralCustomizationEventRewriter::UpdatePressedButtonMapFlags(
const ui::KeyEvent& key_event) {
if (key_event.type() == ui::EventType::kKeyPressed) {
return;
}
// Remap the released key based on modifier remappings.
auto* settings = input_device_settings_controller_->GetKeyboardSettings(
key_event.source_device_id());
auto modifier_key = ConvertDomCodeToModifierKey(key_event.code());
int key_event_characteristic_flag =
ConvertKeyCodeToFlags(key_event.key_code());
// Modifiers only need to be remapped now if the rewriter fix is disabled.
if (!features::IsKeyboardRewriterFixEnabled() && settings && modifier_key) {
auto iter = settings->modifier_remappings.find(*modifier_key);
if (iter != settings->modifier_remappings.end()) {
key_event_characteristic_flag = ConvertModifierKeyToFlags(iter->second);
}
}
// Remove the key event characteristic flag as the key has already been
// released and should no longer apply the flag to other pressed events.
for (auto& [_, flag] : device_button_to_flags_) {
flag &= ~key_event_characteristic_flag;
}
}
ui::EventDispatchDetails
PeripheralCustomizationEventRewriter::RewriteMouseWheelEvent(
const ui::MouseWheelEvent& mouse_wheel_event,
const Continuation continuation) {
auto device_type_to_observe =
GetDeviceTypeToObserve(mouse_wheel_event.source_device_id());
if (device_type_to_observe) {
if (NotifyMouseWheelEventObserving(mouse_wheel_event,
*device_type_to_observe)) {
return DiscardEvent(continuation);
}
// Otherwise, the flags must be cleared for the remappable buttons so they
// do not affect the application while the mouse is meant to be observed.
std::unique_ptr<ui::Event> rewritten_event = CloneEvent(mouse_wheel_event);
const int remappable_flags =
GetRemappableMouseEventFlags(*device_type_to_observe);
rewritten_event->SetFlags(rewritten_event->flags() & ~remappable_flags);
if (rewritten_event->IsMouseEvent()) {
auto& rewritten_mouse_event = *rewritten_event->AsMouseEvent();
rewritten_mouse_event.set_changed_button_flags(
rewritten_mouse_event.changed_button_flags() & ~remappable_flags);
}
return SendEvent(continuation, rewritten_event.get());
}
// Clone event and remove the already remapped modifiers and use this as the
// "source" key event for the rest of the rewriting.
std::unique_ptr<ui::Event> original_event_with_modifiers_removed =
CloneEvent(mouse_wheel_event);
RemoveRemappedModifiers(*original_event_with_modifiers_removed);
std::vector<std::unique_ptr<ui::Event>> rewritten_events;
mojom::ButtonPtr button = GetButtonFromMouseWheelEvent(mouse_wheel_event);
bool updated_button_map = false;
if (!button.is_null()) {
if (RewriteEventFromButton(*original_event_with_modifiers_removed, *button,
rewritten_events)) {
return DiscardEvent(continuation);
}
}
const bool event_rewritten = !rewritten_events.empty();
// Add an event to our list to rewrite based on other pressed buttons.
if (!event_rewritten) {
rewritten_events.push_back(
std::move(original_event_with_modifiers_removed));
}
// If the button was released, the pressed button map must be updated before
// applying remapped modifiers.
const ui::Event& last_rewritten_event = *rewritten_events.back();
if (event_rewritten &&
(last_rewritten_event.type() == ui::EventType::kMouseReleased ||
last_rewritten_event.type() == ui::EventType::kKeyReleased)) {
updated_button_map = true;
UpdatePressedButtonMap(std::move(button), mouse_wheel_event,
rewritten_events);
}
for (const auto& rewritten_event : rewritten_events) {
ApplyRemappedModifiers(*rewritten_event);
}
if (event_rewritten && !updated_button_map) {
UpdatePressedButtonMap(std::move(button), mouse_wheel_event,
rewritten_events);
}
ui::EventDispatchDetails details;
for (const auto& rewritten_event : rewritten_events) {
details = SendEvent(continuation, rewritten_event.get());
}
return details;
}
ui::EventDispatchDetails
PeripheralCustomizationEventRewriter::RewriteMouseEvent(
const ui::MouseEvent& mouse_event,
const Continuation continuation) {
auto device_type_to_observe =
GetDeviceTypeToObserve(mouse_event.source_device_id());
if (device_type_to_observe) {
if (NotifyMouseEventObserving(mouse_event, *device_type_to_observe)) {
return DiscardEvent(continuation);
}
// Otherwise, the flags must be cleared for the remappable buttons so they
// do not affect the application while the mouse is meant to be observed.
std::unique_ptr<ui::Event> rewritten_event = CloneEvent(mouse_event);
const int remappable_flags =
GetRemappableMouseEventFlags(*device_type_to_observe);
rewritten_event->SetFlags(rewritten_event->flags() & ~remappable_flags);
if (rewritten_event->IsMouseEvent()) {
auto& rewritten_mouse_event = *rewritten_event->AsMouseEvent();
rewritten_mouse_event.set_changed_button_flags(
rewritten_mouse_event.changed_button_flags() & ~remappable_flags);
}
return SendEvent(continuation, rewritten_event.get());
}
// Clone event and remove the already remapped modifiers and use this as the
// "source" key event for the rest of the rewriting.
std::unique_ptr<ui::Event> original_event_with_modifiers_removed =
CloneEvent(mouse_event);
RemoveRemappedModifiers(*original_event_with_modifiers_removed);
std::vector<std::unique_ptr<ui::Event>> rewritten_events;
mojom::ButtonPtr button;
bool updated_button_map = false;
if (IsMouseButtonEvent(mouse_event) && mouse_event.changed_button_flags()) {
button = GetButtonFromMouseEvent(mouse_event);
if (RewriteEventFromButton(*original_event_with_modifiers_removed, *button,
rewritten_events)) {
return DiscardEvent(continuation);
}
}
const bool event_rewritten = !rewritten_events.empty();
// Add an event to our list to rewrite based on other pressed buttons.
if (!event_rewritten) {
rewritten_events.push_back(
std::move(original_event_with_modifiers_removed));
}
// If the button was released, the pressed button map must be updated before
// applying remapped modifiers.
const ui::Event& last_rewritten_event = *rewritten_events.back();
if (event_rewritten &&
(last_rewritten_event.type() == ui::EventType::kMouseReleased ||
last_rewritten_event.type() == ui::EventType::kKeyReleased)) {
updated_button_map = true;
UpdatePressedButtonMap(std::move(button), mouse_event, rewritten_events);
}
for (const auto& rewritten_event : rewritten_events) {
ApplyRemappedModifiers(*rewritten_event);
}
if (event_rewritten && !updated_button_map) {
UpdatePressedButtonMap(std::move(button), mouse_event, rewritten_events);
}
ui::EventDispatchDetails details;
for (const auto& rewritten_event : rewritten_events) {
details = SendEvent(continuation, rewritten_event.get());
}
return details;
}
ui::EventDispatchDetails PeripheralCustomizationEventRewriter::RewriteEvent(
const ui::Event& event,
const Continuation continuation) {
DCHECK(features::IsPeripheralCustomizationEnabled() ||
::features::IsShortcutCustomizationEnabled());
if (event.IsMouseWheelEvent()) {
return RewriteMouseWheelEvent(*event.AsMouseWheelEvent(), continuation);
}
if (event.IsMouseEvent()) {
return RewriteMouseEvent(*event.AsMouseEvent(), continuation);
}
if (event.IsKeyEvent()) {
return RewriteKeyEvent(*event.AsKeyEvent(), continuation);
}
return SendEvent(continuation, &event);
}
std::optional<PeripheralCustomizationEventRewriter::RemappingActionResult>
PeripheralCustomizationEventRewriter::GetRemappingAction(
int device_id,
const mojom::Button& button) {
const auto* mouse_settings =
input_device_settings_controller_->GetMouseSettings(device_id);
if (mouse_settings) {
return GetRemappingActionFromMouseSettings(button, *mouse_settings);
}
const auto* graphics_tablet_settings =
input_device_settings_controller_->GetGraphicsTabletSettings(device_id);
if (graphics_tablet_settings) {
return GetRemappingActionFromGraphicsTabletSettings(
button, *graphics_tablet_settings);
}
return std::nullopt;
}
void PeripheralCustomizationEventRewriter::RemoveRemappedModifiers(
ui::Event& event) {
int modifier_flags = 0;
if (const auto* mouse_settings =
input_device_settings_controller_->GetMouseSettings(
event.source_device_id());
mouse_settings) {
modifier_flags = GetRemappedModifiersFromMouseSettings(*mouse_settings);
} else if (const auto* graphics_tablet_settings =
input_device_settings_controller_->GetGraphicsTabletSettings(
event.source_device_id());
graphics_tablet_settings) {
modifier_flags = GetRemappedModifiersFromGraphicsTabletSettings(
*graphics_tablet_settings);
}
// TODO(dpad): This logic isn't quite correct. If a second devices is holding
// "Ctrl" and the original device has a button that is "Ctrl" that is
// remapped, this will behave incorrectly as it will remove "Ctrl". Instead,
// this needs to track what keys are being pressed by the device that have
// modifiers attached to them. For now, this is close enough to being correct.
if (modifier_flags) {
event.SetFlags(event.flags() & ~modifier_flags);
}
}
void PeripheralCustomizationEventRewriter::ApplyRemappedModifiers(
ui::Event& event) {
int flags = 0;
for (const auto& [_, flag] : device_button_to_flags_) {
flags |= flag;
}
if (flags) {
event.SetFlags(event.flags() | flags);
}
}
std::unique_ptr<ui::Event> PeripheralCustomizationEventRewriter::CloneEvent(
const ui::Event& event) {
std::unique_ptr<ui::Event> cloned_event = event.Clone();
// SetNativeEvent must be called explicitly as native events are not copied
// on ChromeOS by default. This is because `PlatformEvent` is a pointer by
// default, so its lifetime can not be guaranteed in general. In this case,
// the lifetime of `rewritten_event` is guaranteed to be less than the
// original `mouse_event`.
SetNativeEvent(*cloned_event, event.native_event());
return cloned_event;
}
} // namespace ash