| // 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 <iterator> |
| #include <memory> |
| |
| #include "ash/accelerators/accelerator_controller_impl.h" |
| #include "ash/constants/ash_features.h" |
| #include "ash/public/cpp/accelerators_util.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 "base/check.h" |
| #include "base/check_op.h" |
| #include "base/containers/fixed_flat_map.h" |
| #include "base/containers/span.h" |
| #include "base/notreached.h" |
| #include "base/ranges/algorithm.h" |
| #include "ui/base/ui_base_features.h" |
| #include "ui/display/screen.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}, |
| }); |
| |
| constexpr std::string_view ToMetricsString( |
| PeripheralCustomizationEventRewriter::PeripheralCustomizationMetricsType |
| peripheral_kind) { |
| switch (peripheral_kind) { |
| case PeripheralCustomizationEventRewriter:: |
| PeripheralCustomizationMetricsType::kMouse: |
| return "Mouse"; |
| case PeripheralCustomizationEventRewriter:: |
| PeripheralCustomizationMetricsType::kGraphicsTablet: |
| return "GraphicsTablet"; |
| case PeripheralCustomizationEventRewriter:: |
| PeripheralCustomizationMetricsType::kGraphicsTabletPen: |
| return "GraphicsTabletPen"; |
| } |
| } |
| |
| 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_NORETURN(); |
| case mojom::StaticShortcutAction::kCopy: |
| key_event = mojom::KeyEvent( |
| ui::VKEY_C, static_cast<int>(ui::DomCode::US_C), |
| static_cast<int>(ui::DomKey::Constant<'c'>::Character), |
| 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::Constant<'v'>::Character), |
| 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::Constant<'z'>::Character), |
| 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::Constant<'z'>::Character), |
| 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::Constant<'='>::Character), |
| 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::Constant<'-'>::Character), |
| 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; |
| } |
| } |
| |
| 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: |
| 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::ET_KEY_PRESSED : ui::ET_KEY_RELEASED; |
| auto rewritten_modifier_event = std::make_unique<ui::KeyEvent>( |
| pressed_or_released_type, iter->key_code, iter->dom_code, |
| modifiers_pressed | modifiers_already_pressed, 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, |
| bool key_press) { |
| const ui::EventType event_type = |
| key_press ? ui::ET_KEY_PRESSED : ui::ET_KEY_RELEASED; |
| 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. |
| const uint32_t other_modifiers_to_apply = |
| key_event.modifiers & ~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::ET_KEY_RELEASED && |
| key_event.modifiers == modifier_key_flag) { |
| applied_modifier_key_flag = ui::EF_NONE; |
| } |
| |
| auto rewritten_event = std::make_unique<ui::KeyEvent>( |
| event_type, key_event.vkey, static_cast<ui::DomCode>(key_event.dom_code), |
| applied_modifier_key_flag | other_modifiers_to_apply | event.flags(), |
| static_cast<ui::DomKey>(key_event.dom_key), event.time_stamp()); |
| rewritten_event->set_source_device_id(event.source_device_id()); |
| |
| return GenerateFullKeyEventSequence( |
| event, other_modifiers_to_apply, event.flags(), |
| /*pressed=*/event_type == ui::ET_KEY_PRESSED, std::move(rewritten_event)); |
| } |
| |
| std::vector<std::unique_ptr<ui::Event>> RewriteEventToKeyEvents( |
| const ui::Event& event, |
| const mojom::KeyEvent& key_event) { |
| // 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::ET_MOUSEWHEEL; |
| |
| const bool key_press = should_press_and_release || |
| event.type() == ui::ET_MOUSE_PRESSED || |
| event.type() == ui::ET_KEY_PRESSED; |
| std::vector<std::unique_ptr<ui::Event>> rewritten_events = |
| RewriteEventToKeyEvents(event, key_event, key_press); |
| |
| if (should_press_and_release) { |
| std::vector<std::unique_ptr<ui::Event>> release_rewritten_events = |
| RewriteEventToKeyEvents(event, key_event, /*key_press=*/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; |
| } |
| |
| 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::ET_MOUSEWHEEL; |
| |
| 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; |
| |
| auto* screen = display::Screen::GetScreen(); |
| CHECK(screen); |
| auto display = screen->GetDisplayNearestPoint(screen->GetCursorScreenPoint()); |
| const gfx::PointF location = |
| gfx::ScalePoint(gfx::PointF(screen->GetCursorScreenPoint() - |
| display.bounds().origin().OffsetFromOrigin()), |
| display.device_scale_factor()); |
| |
| const ui::EventType type = |
| (should_press_and_release || event.type() == ui::ET_MOUSE_PRESSED || |
| event.type() == ui::ET_KEY_PRESSED) |
| ? ui::ET_MOUSE_PRESSED |
| : ui::ET_MOUSE_RELEASED; |
| 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::ET_MOUSE_RELEASED, 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::ET_MOUSE_PRESSED || |
| mouse_event.type() == ui::ET_MOUSE_RELEASED; |
| } |
| |
| 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_NORETURN(); |
| } |
| |
| // 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<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, |
| PeripheralCustomizationEventRewriter:: |
| 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, |
| PeripheralCustomizationEventRewriter:: |
| 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, |
| PeripheralCustomizationEventRewriter:: |
| 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 alphabet letter. |
| bool IsAlphaKeyboardCode(ui::KeyboardCode key_code) { |
| return key_code >= ui::VKEY_A && key_code <= ui::VKEY_Z; |
| } |
| |
| // Verify if the keyboard code is a number. |
| bool IsNumberKeyboardCode(ui::KeyboardCode key_code) { |
| return key_code >= ui::VKEY_0 && key_code <= ui::VKEY_9; |
| } |
| |
| } // 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, |
| 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::ET_MOUSE_PRESSED) { |
| 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 letters key event. |
| // 3. If restriction is kAllowAlphabetOrNumberKeyEventRewrites, mice are |
| // allowed to observe alphabet letters 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 IsAlphaKeyboardCode(key_event.key_code()); |
| case mojom::CustomizationRestriction:: |
| kAllowAlphabetOrNumberKeyEventRewrites: |
| return IsAlphaKeyboardCode(key_event.key_code()) || |
| IsNumberKeyboardCode(key_event.key_code()); |
| case mojom::CustomizationRestriction::kAllowTabEventRewrites: |
| return key_event.key_code() == ui::VKEY_TAB; |
| 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)) { |
| return false; |
| } |
| |
| // Observers should only be notified on key presses. |
| if (key_event.type() != ui::ET_KEY_PRESSED) { |
| 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) { |
| absl::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::ET_KEY_PRESSED || |
| event.type() == ui::ET_MOUSE_PRESSED) { |
| metrics_manager_->RecordRemappingActionWhenButtonPressed( |
| *remapping_action, |
| ToMetricsString(remapping_action_result->peripheral_kind).data()); |
| } |
| |
| if (remapping_action->is_accelerator_action()) { |
| if (event.type() == ui::ET_KEY_PRESSED || |
| event.type() == ui::ET_MOUSE_PRESSED || |
| event.type() == ui::ET_MOUSEWHEEL) { |
| // 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; |
| } |
| |
| 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); |
| } 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, "")); |
| } |
| } |
| |
| 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())); |
| } |
| } |
| |
| 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::ET_MOUSE_RELEASED || |
| last_rewritten_event.type() == ui::ET_KEY_RELEASED)) { |
| updated_button_map = true; |
| UpdatePressedButtonMap(std::move(button), key_event, rewritten_events); |
| } |
| |
| 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::ET_MOUSEWHEEL) { |
| 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::ET_MOUSE_RELEASED || |
| original_event.type() == ui::ET_KEY_RELEASED) { |
| device_button_to_flags_.erase(std::move(device_id_button_key)); |
| 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); |
| } |
| |
| 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::ET_MOUSE_RELEASED || |
| last_rewritten_event.type() == ui::ET_KEY_RELEASED)) { |
| 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::ET_MOUSE_RELEASED || |
| last_rewritten_event.type() == ui::ET_KEY_RELEASED)) { |
| 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 absl::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. |
| 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; |
| } |
| 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 |