blob: 7c8663bb32193044be54acdf69a3b32b57b7b023 [file] [log] [blame]
David Padlipsky5eaaade2023-07-21 22:39:381// Copyright 2023 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ash/events/peripheral_customization_event_rewriter.h"
6
David Padlipskyb7896da2023-10-25 19:45:467#include <linux/input.h>
8
David Padlipskycc7c2de2023-08-23 17:16:549#include <memory>
10
David Padlipskyf8730952023-09-01 19:38:2711#include "ash/accelerators/accelerator_controller_impl.h"
David Padlipsky5eaaade2023-07-21 22:39:3812#include "ash/constants/ash_features.h"
David Padlipsky3b55bb12023-09-05 22:24:5713#include "ash/public/mojom/input_device_settings.mojom-forward.h"
David Padlipskyb5a52cd12023-08-08 21:45:1914#include "ash/public/mojom/input_device_settings.mojom-shared.h"
15#include "ash/public/mojom/input_device_settings.mojom.h"
David Padlipskyf8730952023-09-01 19:38:2716#include "ash/shell.h"
David Padlipskyf9728f272023-09-06 16:50:3617#include "ash/system/input_device_settings/input_device_settings_controller_impl.h"
David Padlipsky5eaaade2023-07-21 22:39:3818#include "base/check.h"
David Padlipskyd664e672023-10-25 21:32:1519#include "base/containers/fixed_flat_map.h"
David Padlipskyb5a52cd12023-08-08 21:45:1920#include "base/notreached.h"
David Padlipskycc7c2de2023-08-23 17:16:5421#include "base/ranges/algorithm.h"
Jimmyb36a8772023-11-10 19:41:3922#include "ui/base/ui_base_features.h"
David Padlipskyd664e672023-10-25 21:32:1523#include "ui/display/screen.h"
David Padlipskyb5a52cd12023-08-08 21:45:1924#include "ui/events/event.h"
25#include "ui/events/event_constants.h"
26#include "ui/events/event_dispatcher.h"
David Padlipskyd664e672023-10-25 21:32:1527#include "ui/events/event_utils.h"
David Padlipskycc7c2de2023-08-23 17:16:5428#include "ui/events/keycodes/dom/dom_code.h"
29#include "ui/events/keycodes/dom/dom_key.h"
30#include "ui/events/keycodes/keyboard_codes_posix.h"
David Padlipskyb7896da2023-10-25 19:45:4631#include "ui/events/ozone/evdev/mouse_button_property.h"
David Padlipskyb5a52cd12023-08-08 21:45:1932#include "ui/events/types/event_type.h"
David Padlipskyd664e672023-10-25 21:32:1533#include "ui/gfx/geometry/point_f.h"
David Padlipsky5eaaade2023-07-21 22:39:3834
35namespace ash {
36
David Padlipskyb5a52cd12023-08-08 21:45:1937namespace {
38
39constexpr int kMouseRemappableFlags = ui::EF_BACK_MOUSE_BUTTON |
40 ui::EF_FORWARD_MOUSE_BUTTON |
41 ui::EF_MIDDLE_MOUSE_BUTTON;
42
43constexpr int kGraphicsTabletRemappableFlags =
44 ui::EF_RIGHT_MOUSE_BUTTON | ui::EF_BACK_MOUSE_BUTTON |
45 ui::EF_FORWARD_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON;
46
David Padlipskyd664e672023-10-25 21:32:1547constexpr auto kStaticActionToMouseButtonFlag =
48 base::MakeFixedFlatMap<mojom::StaticShortcutAction, ui::EventFlags>({
49 {mojom::StaticShortcutAction::kLeftClick, ui::EF_LEFT_MOUSE_BUTTON},
50 {mojom::StaticShortcutAction::kRightClick, ui::EF_RIGHT_MOUSE_BUTTON},
51 {mojom::StaticShortcutAction::kMiddleClick, ui::EF_MIDDLE_MOUSE_BUTTON},
52 });
53
YuhanYang92cbe1592023-12-05 02:13:2954constexpr char* ToMetricsString(
55 PeripheralCustomizationEventRewriter::PeripheralCustomizationMetricsType
56 peripheral_kind) {
57 switch (peripheral_kind) {
58 case PeripheralCustomizationEventRewriter::
59 PeripheralCustomizationMetricsType::kMouse:
60 return (char*)"Mouse";
61 case PeripheralCustomizationEventRewriter::
62 PeripheralCustomizationMetricsType::kGraphicsTablet:
63 return (char*)"GraphicsTablet";
64 case PeripheralCustomizationEventRewriter::
65 PeripheralCustomizationMetricsType::kGraphicsTabletPen:
66 return (char*)"GraphicsTabletPen";
67 }
68}
69
Danny Wangd388f6b2023-09-29 00:06:5070mojom::KeyEvent GetStaticShortcutAction(mojom::StaticShortcutAction action) {
Danny Wang6bdb5582023-09-18 21:27:2971 mojom::KeyEvent key_event;
72 switch (action) {
Danny Wang75a9a86f2023-09-29 21:00:3073 case mojom::StaticShortcutAction::kDisable:
David Padlipskyd664e672023-10-25 21:32:1574 case mojom::StaticShortcutAction::kLeftClick:
75 case mojom::StaticShortcutAction::kRightClick:
76 case mojom::StaticShortcutAction::kMiddleClick:
Danny Wang75a9a86f2023-09-29 21:00:3077 NOTREACHED_NORETURN();
Danny Wangd388f6b2023-09-29 00:06:5078 case mojom::StaticShortcutAction::kCopy:
Danny Wang6bdb5582023-09-18 21:27:2979 key_event = mojom::KeyEvent(
80 ui::VKEY_C, static_cast<int>(ui::DomCode::US_C),
81 static_cast<int>(ui::DomKey::Constant<'c'>::Character),
David Padlipskyfc60da892023-10-27 00:14:3182 ui::EF_CONTROL_DOWN, /*key_display=*/"");
Danny Wang6bdb5582023-09-18 21:27:2983 break;
Danny Wangd388f6b2023-09-29 00:06:5084 case mojom::StaticShortcutAction::kPaste:
Danny Wang6bdb5582023-09-18 21:27:2985 key_event = mojom::KeyEvent(
86 ui::VKEY_V, static_cast<int>(ui::DomCode::US_V),
87 static_cast<int>(ui::DomKey::Constant<'v'>::Character),
David Padlipskyfc60da892023-10-27 00:14:3188 ui::EF_CONTROL_DOWN, /*key_display=*/"");
Danny Wang6bdb5582023-09-18 21:27:2989 break;
Danny Wang0dfd3b02023-10-19 01:36:0290 case mojom::StaticShortcutAction::kUndo:
91 key_event = mojom::KeyEvent(
92 ui::VKEY_Z, static_cast<int>(ui::DomCode::US_Z),
93 static_cast<int>(ui::DomKey::Constant<'z'>::Character),
David Padlipskyfc60da892023-10-27 00:14:3194 ui::EF_CONTROL_DOWN, /*key_display=*/"");
Danny Wang0dfd3b02023-10-19 01:36:0295 break;
96 case mojom::StaticShortcutAction::kRedo:
97 key_event = mojom::KeyEvent(
98 ui::VKEY_Z, static_cast<int>(ui::DomCode::US_Z),
99 static_cast<int>(ui::DomKey::Constant<'z'>::Character),
David Padlipskyfc60da892023-10-27 00:14:31100 ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN, /*key_display=*/"");
Danny Wang0dfd3b02023-10-19 01:36:02101 break;
102 case mojom::StaticShortcutAction::kZoomIn:
103 key_event = mojom::KeyEvent(
104 ui::VKEY_OEM_PLUS, static_cast<int>(ui::DomCode::EQUAL),
105 static_cast<int>(ui::DomKey::Constant<'='>::Character),
David Padlipskyfc60da892023-10-27 00:14:31106 ui::EF_CONTROL_DOWN, /*key_display=*/"");
Danny Wang0dfd3b02023-10-19 01:36:02107 break;
108 case mojom::StaticShortcutAction::kZoomOut:
109 key_event = mojom::KeyEvent(
110 ui::VKEY_OEM_MINUS, static_cast<int>(ui::DomCode::MINUS),
111 static_cast<int>(ui::DomKey::Constant<'-'>::Character),
David Padlipskyfc60da892023-10-27 00:14:31112 ui::EF_CONTROL_DOWN, /*key_display=*/"");
Danny Wang0dfd3b02023-10-19 01:36:02113 break;
114 case mojom::StaticShortcutAction::kPreviousPage:
David Padlipskyfc60da892023-10-27 00:14:31115 key_event = mojom::KeyEvent(ui::VKEY_BROWSER_BACK,
116 static_cast<int>(ui::DomCode::BROWSER_BACK),
117 static_cast<int>(ui::DomKey::BROWSER_BACK),
118 ui::EF_NONE, /*key_display=*/"");
Danny Wang0dfd3b02023-10-19 01:36:02119 break;
120 case mojom::StaticShortcutAction::kNextPage:
David Padlipskyfc60da892023-10-27 00:14:31121 key_event =
122 mojom::KeyEvent(ui::VKEY_BROWSER_FORWARD,
123 static_cast<int>(ui::DomCode::BROWSER_FORWARD),
124 static_cast<int>(ui::DomKey::BROWSER_FORWARD),
125 ui::EF_NONE, /*key_display=*/"");
Danny Wang0dfd3b02023-10-19 01:36:02126 break;
Danny Wang6bdb5582023-09-18 21:27:29127 }
128 return key_event;
129}
130
David Padlipsky3394af92023-10-27 21:30:50131int ConvertKeyCodeToFlags(ui::KeyboardCode key_code) {
132 switch (key_code) {
133 case ui::VKEY_LWIN:
134 case ui::VKEY_RWIN:
135 return ui::EF_COMMAND_DOWN;
136 case ui::VKEY_CONTROL:
137 return ui::EF_CONTROL_DOWN;
138 case ui::VKEY_SHIFT:
139 case ui::VKEY_LSHIFT:
140 case ui::VKEY_RSHIFT:
141 return ui::EF_SHIFT_DOWN;
142 case ui::VKEY_MENU:
143 case ui::VKEY_RMENU:
144 return ui::EF_ALT_DOWN;
145 default:
146 return ui::EF_NONE;
147 }
148}
149
Danny Wang6bdb5582023-09-18 21:27:29150std::unique_ptr<ui::Event> RewriteEventToKeyEvent(
151 const ui::Event& event,
152 const mojom::KeyEvent& key_event) {
153 const ui::EventType event_type = (event.type() == ui::ET_MOUSE_PRESSED ||
154 event.type() == ui::ET_KEY_PRESSED)
155 ? ui::ET_KEY_PRESSED
156 : ui::ET_KEY_RELEASED;
David Padlipsky3394af92023-10-27 21:30:50157 int flags_to_apply = key_event.modifiers;
158 // Do not apply modifier flags when the key is a modifier and it is a release
159 // event. Modifier keys do not apply their flag on release.
160 if (event_type == ui::ET_KEY_RELEASED &&
161 key_event.modifiers ==
162 static_cast<uint32_t>(ConvertKeyCodeToFlags(key_event.vkey))) {
163 flags_to_apply = ui::EF_NONE;
164 }
Danny Wang6bdb5582023-09-18 21:27:29165 auto rewritten_event = std::make_unique<ui::KeyEvent>(
166 event_type, key_event.vkey, static_cast<ui::DomCode>(key_event.dom_code),
David Padlipsky3394af92023-10-27 21:30:50167 flags_to_apply | event.flags(),
Danny Wang6bdb5582023-09-18 21:27:29168 static_cast<ui::DomKey>(key_event.dom_key), event.time_stamp());
169 rewritten_event->set_source_device_id(event.source_device_id());
170 return rewritten_event;
171}
172
David Padlipskyd664e672023-10-25 21:32:15173std::unique_ptr<ui::Event> RewriteEventToMouseButtonEvent(
174 const ui::Event& event,
175 mojom::StaticShortcutAction action) {
176 auto* flag_iter = kStaticActionToMouseButtonFlag.find(action);
177 CHECK(flag_iter != kStaticActionToMouseButtonFlag.end());
178 const int characteristic_flag = flag_iter->second;
179
180 auto* screen = display::Screen::GetScreen();
181 CHECK(screen);
David Padlipsky3394af92023-10-27 21:30:50182 auto display = screen->GetDisplayNearestPoint(screen->GetCursorScreenPoint());
183 const gfx::PointF location =
184 gfx::ScalePoint(gfx::PointF(screen->GetCursorScreenPoint() -
185 display.bounds().origin().OffsetFromOrigin()),
186 display.device_scale_factor());
David Padlipskyd664e672023-10-25 21:32:15187
188 const ui::EventType type = (event.type() == ui::ET_MOUSE_PRESSED ||
189 event.type() == ui::ET_KEY_PRESSED)
190 ? ui::ET_MOUSE_PRESSED
191 : ui::ET_MOUSE_RELEASED;
192 auto rewritten_event = std::make_unique<ui::MouseEvent>(
193 type, location, location, event.time_stamp(),
194 event.flags() | characteristic_flag, characteristic_flag);
195 rewritten_event->set_source_device_id(event.source_device_id());
196 return rewritten_event;
197}
198
David Padlipskyb5a52cd12023-08-08 21:45:19199bool IsMouseButtonEvent(const ui::MouseEvent& mouse_event) {
200 return mouse_event.type() == ui::ET_MOUSE_PRESSED ||
201 mouse_event.type() == ui::ET_MOUSE_RELEASED;
202}
203
204bool IsMouseRemappableButton(int flags) {
205 return (flags & kMouseRemappableFlags) != 0;
206}
207
208bool IsGraphicsTabletRemappableButton(int flags) {
209 return (flags & kGraphicsTabletRemappableFlags) != 0;
210}
211
212int GetRemappableMouseEventFlags(
213 PeripheralCustomizationEventRewriter::DeviceType device_type) {
214 switch (device_type) {
215 case PeripheralCustomizationEventRewriter::DeviceType::kMouse:
216 return kMouseRemappableFlags;
217 case PeripheralCustomizationEventRewriter::DeviceType::kGraphicsTablet:
218 return kGraphicsTabletRemappableFlags;
219 }
220}
221
David Padlipskyb7896da2023-10-25 19:45:46222mojom::ButtonPtr GetButtonFromMouseEvent(const ui::MouseEvent& mouse_event) {
223 switch (mouse_event.changed_button_flags()) {
David Padlipskyb5a52cd12023-08-08 21:45:19224 case ui::EF_LEFT_MOUSE_BUTTON:
225 return mojom::Button::NewCustomizableButton(
226 mojom::CustomizableButton::kLeft);
227 case ui::EF_RIGHT_MOUSE_BUTTON:
228 return mojom::Button::NewCustomizableButton(
229 mojom::CustomizableButton::kRight);
230 case ui::EF_MIDDLE_MOUSE_BUTTON:
231 return mojom::Button::NewCustomizableButton(
232 mojom::CustomizableButton::kMiddle);
233 case ui::EF_FORWARD_MOUSE_BUTTON:
David Padlipskyb7896da2023-10-25 19:45:46234 case ui::EF_BACK_MOUSE_BUTTON:
235 break;
236 }
237
238 CHECK(mouse_event.changed_button_flags() == ui::EF_FORWARD_MOUSE_BUTTON ||
239 mouse_event.changed_button_flags() == ui::EF_BACK_MOUSE_BUTTON);
240 auto linux_key_code = ui::GetForwardBackMouseButtonProperty(mouse_event);
241 if (!linux_key_code) {
242 return (mouse_event.changed_button_flags() == ui::EF_FORWARD_MOUSE_BUTTON)
243 ? mojom::Button::NewCustomizableButton(
244 mojom::CustomizableButton::kForward)
245 : mojom::Button::NewCustomizableButton(
246 mojom::CustomizableButton::kBack);
247 }
248
249 switch (*linux_key_code) {
250 case BTN_FORWARD:
David Padlipskyb5a52cd12023-08-08 21:45:19251 return mojom::Button::NewCustomizableButton(
252 mojom::CustomizableButton::kForward);
David Padlipskyb7896da2023-10-25 19:45:46253 case BTN_BACK:
David Padlipskyb5a52cd12023-08-08 21:45:19254 return mojom::Button::NewCustomizableButton(
255 mojom::CustomizableButton::kBack);
David Padlipskyb7896da2023-10-25 19:45:46256 case BTN_SIDE:
257 return mojom::Button::NewCustomizableButton(
258 mojom::CustomizableButton::kSide);
259 case BTN_EXTRA:
260 return mojom::Button::NewCustomizableButton(
261 mojom::CustomizableButton::kExtra);
David Padlipskyb5a52cd12023-08-08 21:45:19262 }
David Padlipskyb7896da2023-10-25 19:45:46263
David Padlipskyb5a52cd12023-08-08 21:45:19264 NOTREACHED_NORETURN();
265}
266
David Padlipskycc7c2de2023-08-23 17:16:54267int ConvertButtonToFlags(const mojom::Button& button) {
268 if (button.is_customizable_button()) {
269 switch (button.get_customizable_button()) {
270 case mojom::CustomizableButton::kLeft:
271 return ui::EF_LEFT_MOUSE_BUTTON;
272 case mojom::CustomizableButton::kRight:
273 return ui::EF_RIGHT_MOUSE_BUTTON;
274 case mojom::CustomizableButton::kMiddle:
275 return ui::EF_MIDDLE_MOUSE_BUTTON;
276 case mojom::CustomizableButton::kForward:
David Padlipskycc7c2de2023-08-23 17:16:54277 case mojom::CustomizableButton::kExtra:
278 return ui::EF_FORWARD_MOUSE_BUTTON;
David Padlipskyb7896da2023-10-25 19:45:46279 case mojom::CustomizableButton::kBack:
David Padlipskycc7c2de2023-08-23 17:16:54280 case mojom::CustomizableButton::kSide:
281 return ui::EF_BACK_MOUSE_BUTTON;
282 }
283 }
284
285 if (button.is_vkey()) {
David Padlipsky3b55bb12023-09-05 22:24:57286 return ConvertKeyCodeToFlags(button.get_vkey());
David Padlipskycc7c2de2023-08-23 17:16:54287 }
288
289 return ui::EF_NONE;
290}
291
YuhanYang92cbe1592023-12-05 02:13:29292std::optional<PeripheralCustomizationEventRewriter::RemappingActionResult>
293GetRemappingActionFromMouseSettings(const mojom::Button& button,
294 const mojom::MouseSettings& settings) {
David Padlipskyf9728f272023-09-06 16:50:36295 const auto button_remapping_iter = base::ranges::find(
296 settings.button_remappings, button,
297 [](const mojom::ButtonRemappingPtr& entry) { return *entry->button; });
298 if (button_remapping_iter != settings.button_remappings.end()) {
YuhanYang92cbe1592023-12-05 02:13:29299 auto result = PeripheralCustomizationEventRewriter::RemappingActionResult(
300 *((*button_remapping_iter)->remapping_action),
301 PeripheralCustomizationEventRewriter::
302 PeripheralCustomizationMetricsType::kMouse);
303 return std::move(result);
David Padlipskyf9728f272023-09-06 16:50:36304 }
305
YuhanYang92cbe1592023-12-05 02:13:29306 return absl::nullopt;
David Padlipskyf9728f272023-09-06 16:50:36307}
308
YuhanYang92cbe1592023-12-05 02:13:29309std::optional<PeripheralCustomizationEventRewriter::RemappingActionResult>
310GetRemappingActionFromGraphicsTabletSettings(
David Padlipskyf9728f272023-09-06 16:50:36311 const mojom::Button& button,
312 const mojom::GraphicsTabletSettings& settings) {
313 const auto pen_button_remapping_iter = base::ranges::find(
314 settings.pen_button_remappings, button,
315 [](const mojom::ButtonRemappingPtr& entry) { return *entry->button; });
316 if (pen_button_remapping_iter != settings.pen_button_remappings.end()) {
YuhanYang92cbe1592023-12-05 02:13:29317 auto pen_action =
318 PeripheralCustomizationEventRewriter::RemappingActionResult(
319 *(*pen_button_remapping_iter)->remapping_action,
320 PeripheralCustomizationEventRewriter::
321 PeripheralCustomizationMetricsType::kGraphicsTabletPen);
322 return std::move(pen_action);
David Padlipskyf9728f272023-09-06 16:50:36323 }
324
325 const auto tablet_button_remapping_iter = base::ranges::find(
326 settings.tablet_button_remappings, button,
327 [](const mojom::ButtonRemappingPtr& entry) { return *entry->button; });
328 if (tablet_button_remapping_iter != settings.tablet_button_remappings.end()) {
YuhanYang92cbe1592023-12-05 02:13:29329 auto tablet_action =
330 PeripheralCustomizationEventRewriter::RemappingActionResult(
331 *(*tablet_button_remapping_iter)->remapping_action,
332 PeripheralCustomizationEventRewriter::
333 PeripheralCustomizationMetricsType::kGraphicsTablet);
334 return std::move(tablet_action);
David Padlipskyf9728f272023-09-06 16:50:36335 }
336
YuhanYang92cbe1592023-12-05 02:13:29337 return absl::nullopt;
David Padlipskyf9728f272023-09-06 16:50:36338}
339
340int GetRemappedModifiersFromMouseSettings(
341 const mojom::MouseSettings& settings) {
342 int modifiers = 0;
343 for (const auto& button_remapping : settings.button_remappings) {
David Padlipskyd664e672023-10-25 21:32:15344 if (button_remapping->remapping_action) {
345 modifiers |= ConvertButtonToFlags(*button_remapping->button);
346 }
David Padlipskyf9728f272023-09-06 16:50:36347 }
348 return modifiers;
349}
350
351int GetRemappedModifiersFromGraphicsTabletSettings(
352 const mojom::GraphicsTabletSettings& settings) {
353 int modifiers = 0;
354 for (const auto& button_remapping : settings.pen_button_remappings) {
355 modifiers |= ConvertButtonToFlags(*button_remapping->button);
356 }
357 for (const auto& button_remapping : settings.tablet_button_remappings) {
358 modifiers |= ConvertButtonToFlags(*button_remapping->button);
359 }
360 return modifiers;
361}
362
David Padlipskyb5a52cd12023-08-08 21:45:19363} // namespace
364
David Padlipsky3b55bb12023-09-05 22:24:57365// Compares the `DeviceIdButton` struct based on first the device id, and then
366// the button stored within the `DeviceIdButton` struct.
367bool operator<(
368 const PeripheralCustomizationEventRewriter::DeviceIdButton& left,
369 const PeripheralCustomizationEventRewriter::DeviceIdButton& right) {
370 if (right.device_id != left.device_id) {
371 return left.device_id < right.device_id;
372 }
373
374 // If both are VKeys, compare them.
375 if (left.button->is_vkey() && right.button->is_vkey()) {
376 return left.button->get_vkey() < right.button->get_vkey();
377 }
378
379 // If both are customizable buttons, compare them.
380 if (left.button->is_customizable_button() &&
381 right.button->is_customizable_button()) {
382 return left.button->get_customizable_button() <
383 right.button->get_customizable_button();
384 }
385
386 // Otherwise, return true if the lhs is a VKey as they mismatch and VKeys
387 // should be considered less than customizable buttons.
388 return left.button->is_vkey();
389}
390
391PeripheralCustomizationEventRewriter::DeviceIdButton::DeviceIdButton(
392 int device_id,
393 mojom::ButtonPtr button)
394 : device_id(device_id), button(std::move(button)) {}
395
396PeripheralCustomizationEventRewriter::DeviceIdButton::DeviceIdButton(
397 DeviceIdButton&& device_id_button)
398 : device_id(device_id_button.device_id),
399 button(std::move(device_id_button.button)) {}
400
401PeripheralCustomizationEventRewriter::DeviceIdButton::~DeviceIdButton() =
402 default;
403
404PeripheralCustomizationEventRewriter::DeviceIdButton&
405PeripheralCustomizationEventRewriter::DeviceIdButton::operator=(
406 PeripheralCustomizationEventRewriter::DeviceIdButton&& device_id_button) =
407 default;
408
YuhanYang92cbe1592023-12-05 02:13:29409PeripheralCustomizationEventRewriter::RemappingActionResult::
410 RemappingActionResult(mojom::RemappingAction& remapping_action,
411 PeripheralCustomizationMetricsType peripheral_kind)
412 : remapping_action(remapping_action), peripheral_kind(peripheral_kind) {}
413
414PeripheralCustomizationEventRewriter::RemappingActionResult::
415 RemappingActionResult(RemappingActionResult&& result)
416 : remapping_action(std::move(result.remapping_action)),
417 peripheral_kind(result.peripheral_kind) {}
418
419PeripheralCustomizationEventRewriter::RemappingActionResult::
420 ~RemappingActionResult() = default;
421
David Padlipskyf9728f272023-09-06 16:50:36422PeripheralCustomizationEventRewriter::PeripheralCustomizationEventRewriter(
423 InputDeviceSettingsController* input_device_settings_controller)
YuhanYang92cbe1592023-12-05 02:13:29424 : input_device_settings_controller_(input_device_settings_controller) {
425 metrics_manager_ = std::make_unique<InputDeviceSettingsMetricsManager>();
426}
427
David Padlipsky5eaaade2023-07-21 22:39:38428PeripheralCustomizationEventRewriter::~PeripheralCustomizationEventRewriter() =
429 default;
430
Arthur Sonzognia98e4432023-11-28 18:15:01431std::optional<PeripheralCustomizationEventRewriter::DeviceType>
David Padlipskyeedcbfe2023-08-08 23:58:36432PeripheralCustomizationEventRewriter::GetDeviceTypeToObserve(int device_id) {
433 if (mice_to_observe_.contains(device_id)) {
434 return DeviceType::kMouse;
435 }
436 if (graphics_tablets_to_observe_.contains(device_id)) {
437 return DeviceType::kGraphicsTablet;
438 }
Arthur Sonzognia98e4432023-11-28 18:15:01439 return std::nullopt;
David Padlipskyeedcbfe2023-08-08 23:58:36440}
441
Danny Wang50d45182023-10-05 17:55:45442void PeripheralCustomizationEventRewriter::StartObservingMouse(
443 int device_id,
Danny Wang6bc788e2023-12-08 23:08:02444 mojom::CustomizationRestriction customization_restriction) {
445 mice_to_observe_.insert_or_assign(device_id, customization_restriction);
David Padlipskyb5a52cd12023-08-08 21:45:19446}
447
448void PeripheralCustomizationEventRewriter::StartObservingGraphicsTablet(
449 int device_id) {
450 graphics_tablets_to_observe_.insert(device_id);
451}
452
453void PeripheralCustomizationEventRewriter::StopObserving() {
454 graphics_tablets_to_observe_.clear();
455 mice_to_observe_.clear();
David Padlipskyb5a52cd12023-08-08 21:45:19456}
457
458bool PeripheralCustomizationEventRewriter::NotifyMouseEventObserving(
459 const ui::MouseEvent& mouse_event,
460 DeviceType device_type) {
461 if (!IsMouseButtonEvent(mouse_event)) {
462 return false;
463 }
464
465 // Make sure the button is remappable for the current `device_type`.
466 switch (device_type) {
467 case DeviceType::kMouse:
468 if (!IsMouseRemappableButton(mouse_event.changed_button_flags())) {
469 return false;
470 }
471 break;
472 case DeviceType::kGraphicsTablet:
473 if (!IsGraphicsTabletRemappableButton(
474 mouse_event.changed_button_flags())) {
475 return false;
476 }
477 break;
478 }
479
480 if (mouse_event.type() != ui::ET_MOUSE_PRESSED) {
481 return true;
482 }
483
David Padlipskyb7896da2023-10-25 19:45:46484 const auto button = GetButtonFromMouseEvent(mouse_event);
David Padlipskyc16dd522023-09-08 21:18:22485 switch (device_type) {
486 case DeviceType::kMouse:
487 input_device_settings_controller_->OnMouseButtonPressed(
488 mouse_event.source_device_id(), *button);
489 break;
490 case DeviceType::kGraphicsTablet:
491 input_device_settings_controller_->OnGraphicsTabletButtonPressed(
492 mouse_event.source_device_id(), *button);
493 break;
David Padlipskyb5a52cd12023-08-08 21:45:19494 }
495
496 return true;
497}
498
David Padlipskyeedcbfe2023-08-08 23:58:36499bool PeripheralCustomizationEventRewriter::NotifyKeyEventObserving(
500 const ui::KeyEvent& key_event,
501 DeviceType device_type) {
Danny Wang6bc788e2023-12-08 23:08:02502 // Only mice that have kAllowCustomizations restriction should be allowed
Danny Wang50d45182023-10-05 17:55:45503 // to observe key events.
Danny Wang6bc788e2023-12-08 23:08:02504 if (device_type == DeviceType::kMouse) {
505 const auto iter = mice_to_observe_.find(key_event.source_device_id());
506 if (iter == mice_to_observe().end() ||
507 iter->second != mojom::CustomizationRestriction::kAllowCustomizations) {
508 return false;
509 }
Danny Wang50d45182023-10-05 17:55:45510 }
511
David Padlipskyeedcbfe2023-08-08 23:58:36512 // Observers should only be notified on key presses.
513 if (key_event.type() != ui::ET_KEY_PRESSED) {
514 return true;
515 }
516
517 const auto button = mojom::Button::NewVkey(key_event.key_code());
David Padlipskyc16dd522023-09-08 21:18:22518 switch (device_type) {
519 case DeviceType::kMouse:
520 input_device_settings_controller_->OnMouseButtonPressed(
521 key_event.source_device_id(), *button);
522 break;
523 case DeviceType::kGraphicsTablet:
524 input_device_settings_controller_->OnGraphicsTabletButtonPressed(
525 key_event.source_device_id(), *button);
526 break;
David Padlipskyeedcbfe2023-08-08 23:58:36527 }
528
529 return true;
530}
531
David Padlipskycc7c2de2023-08-23 17:16:54532bool PeripheralCustomizationEventRewriter::RewriteEventFromButton(
533 const ui::Event& event,
534 const mojom::Button& button,
535 std::unique_ptr<ui::Event>& rewritten_event) {
YuhanYang92cbe1592023-12-05 02:13:29536 absl::optional<PeripheralCustomizationEventRewriter::RemappingActionResult>
537 remapping_action_result =
538 GetRemappingAction(event.source_device_id(), button);
539 if (!remapping_action_result) {
David Padlipskycc7c2de2023-08-23 17:16:54540 return false;
541 }
YuhanYang92cbe1592023-12-05 02:13:29542 auto remapping_action = remapping_action_result->remapping_action;
543
544 metrics_manager_->RecordRemappingActionWhenButtonPressed(
545 *remapping_action,
546 ToMetricsString(remapping_action_result->peripheral_kind));
David Padlipskycc7c2de2023-08-23 17:16:54547
Danny Wangd388f6b2023-09-29 00:06:50548 if (remapping_action->is_accelerator_action()) {
David Padlipskyf8730952023-09-01 19:38:27549 if (event.type() == ui::ET_KEY_PRESSED ||
550 event.type() == ui::ET_MOUSE_PRESSED) {
551 // Every accelerator supported by peripheral customization is not impacted
552 // by the accelerator passed. Therefore, passing an empty accelerator will
553 // cause no issues.
554 Shell::Get()->accelerator_controller()->PerformActionIfEnabled(
Danny Wangd388f6b2023-09-29 00:06:50555 remapping_action->get_accelerator_action(), /*accelerator=*/{});
David Padlipskyf8730952023-09-01 19:38:27556 }
557
David Padlipskycc7c2de2023-08-23 17:16:54558 return true;
559 }
560
561 if (remapping_action->is_key_event()) {
562 const auto& key_event = remapping_action->get_key_event();
Danny Wang6bdb5582023-09-18 21:27:29563 rewritten_event = RewriteEventToKeyEvent(event, *key_event);
564 }
565
Danny Wangd388f6b2023-09-29 00:06:50566 if (remapping_action->is_static_shortcut_action()) {
David Padlipskyd664e672023-10-25 21:32:15567 const auto static_action = remapping_action->get_static_shortcut_action();
568 if (static_action == mojom::StaticShortcutAction::kDisable) {
Danny Wang75a9a86f2023-09-29 21:00:30569 // Return true to discard the event.
570 return true;
571 }
David Padlipskyd664e672023-10-25 21:32:15572
573 if (kStaticActionToMouseButtonFlag.contains(static_action)) {
574 rewritten_event = RewriteEventToMouseButtonEvent(event, static_action);
575 } else {
576 rewritten_event = RewriteEventToKeyEvent(
577 event, GetStaticShortcutAction(
578 remapping_action->get_static_shortcut_action()));
579 }
David Padlipskycc7c2de2023-08-23 17:16:54580 }
581
582 return false;
583}
584
David Padlipskyeedcbfe2023-08-08 23:58:36585ui::EventDispatchDetails PeripheralCustomizationEventRewriter::RewriteKeyEvent(
586 const ui::KeyEvent& key_event,
587 const Continuation continuation) {
588 auto device_type_to_observe =
589 GetDeviceTypeToObserve(key_event.source_device_id());
590 if (device_type_to_observe) {
591 if (NotifyKeyEventObserving(key_event, *device_type_to_observe)) {
592 return DiscardEvent(continuation);
593 }
594 }
595
David Padlipskycc7c2de2023-08-23 17:16:54596 std::unique_ptr<ui::Event> rewritten_event;
David Padlipsky3b55bb12023-09-05 22:24:57597 mojom::ButtonPtr button = mojom::Button::NewVkey(key_event.key_code());
David Padlipsky3394af92023-10-27 21:30:50598 bool updated_button_map = false;
David Padlipsky3b55bb12023-09-05 22:24:57599 if (RewriteEventFromButton(key_event, *button, rewritten_event)) {
David Padlipskycc7c2de2023-08-23 17:16:54600 return DiscardEvent(continuation);
601 }
David Padlipsky3394af92023-10-27 21:30:50602
603 // Update pressed button map now if either there was no rewrite or if its not
604 // a mouse release event.
605 if (!rewritten_event || rewritten_event->type() != ui::ET_MOUSE_RELEASED) {
606 updated_button_map = true;
607 UpdatePressedButtonMap(std::move(button), key_event, rewritten_event);
608 }
David Padlipskycc7c2de2023-08-23 17:16:54609
610 if (!rewritten_event) {
611 rewritten_event = std::make_unique<ui::KeyEvent>(key_event);
612 }
613
614 RemoveRemappedModifiers(*rewritten_event);
David Padlipsky3b55bb12023-09-05 22:24:57615 ApplyRemappedModifiers(*rewritten_event);
David Padlipsky3394af92023-10-27 21:30:50616
617 if (!updated_button_map) {
618 UpdatePressedButtonMap(std::move(button), key_event, rewritten_event);
619 }
620
David Padlipskycc7c2de2023-08-23 17:16:54621 return SendEvent(continuation, rewritten_event.get());
David Padlipskyeedcbfe2023-08-08 23:58:36622}
623
David Padlipsky3b55bb12023-09-05 22:24:57624void PeripheralCustomizationEventRewriter::UpdatePressedButtonMap(
625 mojom::ButtonPtr button,
626 const ui::Event& original_event,
627 const std::unique_ptr<ui::Event>& rewritten_event) {
628 DeviceIdButton device_id_button_key =
629 DeviceIdButton{original_event.source_device_id(), std::move(button)};
David Padlipsky3394af92023-10-27 21:30:50630
631 // If the button is released, the entry must be removed from the map.
632 if (original_event.type() == ui::ET_MOUSE_RELEASED ||
633 original_event.type() == ui::ET_KEY_RELEASED) {
634 device_button_to_flags_.erase(std::move(device_id_button_key));
635 return;
636 }
637
David Padlipskyd664e672023-10-25 21:32:15638 const int key_event_flags =
David Padlipsky3b55bb12023-09-05 22:24:57639 (rewritten_event && rewritten_event->IsKeyEvent())
640 ? ConvertKeyCodeToFlags(rewritten_event->AsKeyEvent()->key_code())
641 : 0;
David Padlipskyd664e672023-10-25 21:32:15642 const int mouse_event_flags =
643 (rewritten_event && rewritten_event->IsMouseEvent())
644 ? rewritten_event->AsMouseEvent()->changed_button_flags()
645 : 0;
David Padlipsky3b55bb12023-09-05 22:24:57646
David Padlipsky3394af92023-10-27 21:30:50647 const int combined_flags = key_event_flags | mouse_event_flags;
648 if (!combined_flags) {
David Padlipsky3b55bb12023-09-05 22:24:57649 return;
650 }
651
652 // Add the entry to the map with the flags that must be applied to other
653 // events.
654 device_button_to_flags_.insert_or_assign(std::move(device_id_button_key),
David Padlipsky3394af92023-10-27 21:30:50655 combined_flags);
David Padlipsky3b55bb12023-09-05 22:24:57656}
657
David Padlipskyb5a52cd12023-08-08 21:45:19658ui::EventDispatchDetails
659PeripheralCustomizationEventRewriter::RewriteMouseEvent(
660 const ui::MouseEvent& mouse_event,
661 const Continuation continuation) {
David Padlipskyeedcbfe2023-08-08 23:58:36662 auto device_type_to_observe =
663 GetDeviceTypeToObserve(mouse_event.source_device_id());
664 if (device_type_to_observe) {
665 if (NotifyMouseEventObserving(mouse_event, *device_type_to_observe)) {
David Padlipskyb5a52cd12023-08-08 21:45:19666 return DiscardEvent(continuation);
667 }
668
669 // Otherwise, the flags must be cleared for the remappable buttons so they
670 // do not affect the application while the mouse is meant to be observed.
David Padlipskydcd57e12023-10-25 23:00:18671 std::unique_ptr<ui::Event> rewritten_event = CloneEvent(mouse_event);
David Padlipskyeedcbfe2023-08-08 23:58:36672 const int remappable_flags =
673 GetRemappableMouseEventFlags(*device_type_to_observe);
David Padlipskydcd57e12023-10-25 23:00:18674 rewritten_event->set_flags(rewritten_event->flags() & ~remappable_flags);
675 if (rewritten_event->IsMouseEvent()) {
676 auto& rewritten_mouse_event = *rewritten_event->AsMouseEvent();
677 rewritten_mouse_event.set_changed_button_flags(
678 rewritten_mouse_event.changed_button_flags() & ~remappable_flags);
679 }
680 return SendEvent(continuation, rewritten_event.get());
David Padlipskyb5a52cd12023-08-08 21:45:19681 }
682
David Padlipskycc7c2de2023-08-23 17:16:54683 std::unique_ptr<ui::Event> rewritten_event;
David Padlipsky3394af92023-10-27 21:30:50684 mojom::ButtonPtr button;
685 bool updated_button_map = false;
David Padlipsky3b55bb12023-09-05 22:24:57686 if (IsMouseButtonEvent(mouse_event) && mouse_event.changed_button_flags()) {
David Padlipsky3394af92023-10-27 21:30:50687 button = GetButtonFromMouseEvent(mouse_event);
David Padlipsky3b55bb12023-09-05 22:24:57688 if (RewriteEventFromButton(mouse_event, *button, rewritten_event)) {
David Padlipskycc7c2de2023-08-23 17:16:54689 return DiscardEvent(continuation);
690 }
David Padlipsky3394af92023-10-27 21:30:50691 // Update pressed button map now if either there was no rewrite or if its
692 // not a mouse release event.
693 if (!rewritten_event || rewritten_event->type() != ui::ET_MOUSE_RELEASED) {
694 updated_button_map = true;
695 UpdatePressedButtonMap(std::move(button), mouse_event, rewritten_event);
696 }
David Padlipskycc7c2de2023-08-23 17:16:54697 }
David Padlipsky3394af92023-10-27 21:30:50698
David Padlipskycc7c2de2023-08-23 17:16:54699 if (!rewritten_event) {
David Padlipskydcd57e12023-10-25 23:00:18700 rewritten_event = CloneEvent(mouse_event);
David Padlipskycc7c2de2023-08-23 17:16:54701 }
702
703 RemoveRemappedModifiers(*rewritten_event);
David Padlipsky3b55bb12023-09-05 22:24:57704 ApplyRemappedModifiers(*rewritten_event);
David Padlipsky3394af92023-10-27 21:30:50705
706 if (!updated_button_map) {
707 UpdatePressedButtonMap(std::move(button), mouse_event, rewritten_event);
708 }
709
David Padlipskycc7c2de2023-08-23 17:16:54710 return SendEvent(continuation, rewritten_event.get());
David Padlipskyb5a52cd12023-08-08 21:45:19711}
712
David Padlipsky5eaaade2023-07-21 22:39:38713ui::EventDispatchDetails PeripheralCustomizationEventRewriter::RewriteEvent(
714 const ui::Event& event,
715 const Continuation continuation) {
Jimmyb36a8772023-11-10 19:41:39716 DCHECK(features::IsPeripheralCustomizationEnabled() ||
717 ::features::IsShortcutCustomizationEnabled());
David Padlipskyb5a52cd12023-08-08 21:45:19718
719 if (event.IsMouseEvent()) {
720 return RewriteMouseEvent(*event.AsMouseEvent(), continuation);
721 }
722
David Padlipskyeedcbfe2023-08-08 23:58:36723 if (event.IsKeyEvent()) {
724 return RewriteKeyEvent(*event.AsKeyEvent(), continuation);
725 }
726
David Padlipsky5eaaade2023-07-21 22:39:38727 return SendEvent(continuation, &event);
728}
729
YuhanYang92cbe1592023-12-05 02:13:29730std::optional<PeripheralCustomizationEventRewriter::RemappingActionResult>
David Padlipskycc7c2de2023-08-23 17:16:54731PeripheralCustomizationEventRewriter::GetRemappingAction(
732 int device_id,
733 const mojom::Button& button) {
David Padlipskyf9728f272023-09-06 16:50:36734 const auto* mouse_settings =
735 input_device_settings_controller_->GetMouseSettings(device_id);
736 if (mouse_settings) {
737 return GetRemappingActionFromMouseSettings(button, *mouse_settings);
David Padlipskycc7c2de2023-08-23 17:16:54738 }
739
David Padlipskyf9728f272023-09-06 16:50:36740 const auto* graphics_tablet_settings =
741 input_device_settings_controller_->GetGraphicsTabletSettings(device_id);
742 if (graphics_tablet_settings) {
743 return GetRemappingActionFromGraphicsTabletSettings(
744 button, *graphics_tablet_settings);
David Padlipskycc7c2de2023-08-23 17:16:54745 }
746
YuhanYang92cbe1592023-12-05 02:13:29747 return absl::nullopt;
David Padlipskycc7c2de2023-08-23 17:16:54748}
749
750void PeripheralCustomizationEventRewriter::RemoveRemappedModifiers(
751 ui::Event& event) {
David Padlipskyf9728f272023-09-06 16:50:36752 int modifier_flags = 0;
753 if (const auto* mouse_settings =
754 input_device_settings_controller_->GetMouseSettings(
755 event.source_device_id());
756 mouse_settings) {
757 modifier_flags = GetRemappedModifiersFromMouseSettings(*mouse_settings);
758 } else if (const auto* graphics_tablet_settings =
759 input_device_settings_controller_->GetGraphicsTabletSettings(
760 event.source_device_id());
761 graphics_tablet_settings) {
762 modifier_flags = GetRemappedModifiersFromGraphicsTabletSettings(
763 *graphics_tablet_settings);
David Padlipskycc7c2de2023-08-23 17:16:54764 }
765
766 // TODO(dpad): This logic isn't quite correct. If a second devices is holding
767 // "Ctrl" and the original device has a button that is "Ctrl" that is
768 // remapped, this will behave incorrectly as it will remove "Ctrl". Instead,
769 // this needs to track what keys are being pressed by the device that have
770 // modifiers attached to them. For now, this is close enough to being correct.
David Padlipskyf9728f272023-09-06 16:50:36771 event.set_flags(event.flags() & ~modifier_flags);
David Padlipskycc7c2de2023-08-23 17:16:54772}
773
David Padlipsky3b55bb12023-09-05 22:24:57774void PeripheralCustomizationEventRewriter::ApplyRemappedModifiers(
775 ui::Event& event) {
776 int flags = 0;
777 for (const auto& [_, flag] : device_button_to_flags_) {
778 flags |= flag;
779 }
780 event.set_flags(event.flags() | flags);
781}
782
David Padlipskydcd57e12023-10-25 23:00:18783std::unique_ptr<ui::Event> PeripheralCustomizationEventRewriter::CloneEvent(
784 const ui::Event& event) {
785 std::unique_ptr<ui::Event> cloned_event = event.Clone();
786 // SetNativeEvent must be called explicitly as native events are not copied
787 // on ChromeOS by default. This is because `PlatformEvent` is a pointer by
788 // default, so its lifetime can not be guaranteed in general. In this case,
789 // the lifetime of `rewritten_event` is guaranteed to be less than the
790 // original `mouse_event`.
791 SetNativeEvent(*cloned_event, event.native_event());
792 return cloned_event;
793}
794
David Padlipsky5eaaade2023-07-21 22:39:38795} // namespace ash