1 // Copyright 2014 The Chromium Authors. All rights reserved.
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 "ui/chromeos/events/event_rewriter_chromeos.h"
6
7 #include <fcntl.h>
8 #include <stddef.h>
9
10 #include "base/feature_list.h"
11 #include "base/files/file_path.h"
12 #include "base/files/scoped_file.h"
13 #include "base/logging.h"
14 #include "base/metrics/user_metrics.h"
15 #include "base/stl_util.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "base/strings/string_split.h"
18 #include "base/strings/string_util.h"
19 #include "base/system/sys_info.h"
20 #include "chromeos/constants/chromeos_features.h"
21 #include "chromeos/constants/chromeos_switches.h"
22 #include "device/udev_linux/scoped_udev.h"
23 #include "ui/base/ime/chromeos/ime_keyboard.h"
24 #include "ui/base/ime/chromeos/input_method_manager.h"
25 #include "ui/base/ui_base_features.h"
26 #include "ui/chromeos/events/modifier_key.h"
27 #include "ui/chromeos/events/pref_names.h"
28 #include "ui/events/devices/device_data_manager.h"
29 #include "ui/events/event_utils.h"
30 #include "ui/events/keycodes/dom/dom_code.h"
31 #include "ui/events/keycodes/dom/keycode_converter.h"
32 #include "ui/events/keycodes/keyboard_code_conversion.h"
33 #include "ui/events/ozone/evdev/event_device_info.h"
34
35 namespace ui {
36
37 namespace {
38
39 // Hotrod controller vendor/product ids.
40 const int kHotrodRemoteVendorId = 0x0471;
41 const int kHotrodRemoteProductId = 0x21cc;
42
43 // Flag masks for remapping alt+click or search+click to right click.
44 constexpr int kAltLeftButton = (EF_ALT_DOWN | EF_LEFT_MOUSE_BUTTON);
45 constexpr int kSearchLeftButton = (EF_COMMAND_DOWN | EF_LEFT_MOUSE_BUTTON);
46
47 // Table of properties of remappable keys and/or remapping targets (not
48 // strictly limited to "modifiers").
49 //
50 // This is used in two distinct ways: for rewriting key up/down events,
51 // and for rewriting modifier EventFlags on any kind of event.
52 //
53 // For the first case, rewriting key up/down events, |RewriteModifierKeys()|
54 // determines the preference name |prefs::kLanguageRemap...KeyTo| for the
55 // incoming key and, using |GetRemappedKey()|, gets the user preference
56 // value |input_method::k...Key| for the incoming key, and finally finds that
57 // value in this table to obtain the |result| properties of the target key.
58 //
59 // For the second case, rewriting modifier EventFlags,
60 // |GetRemappedModifierMasks()| processes every table entry whose |flag|
61 // is set in the incoming event. Using the |pref_name| in the table entry,
62 // it likewise uses |GetRemappedKey()| to find the properties of the
63 // user preference target key, and replaces the flag accordingly.
64 const struct ModifierRemapping {
65 int flag;
66 chromeos::ModifierKey remap_to;
67 const char* pref_name;
68 EventRewriterChromeOS::MutableKeyState result;
69 } kModifierRemappings[] = {
70 {EF_CONTROL_DOWN,
71 chromeos::ModifierKey::kControlKey,
72 prefs::kLanguageRemapControlKeyTo,
73 {EF_CONTROL_DOWN, DomCode::CONTROL_LEFT, DomKey::CONTROL, VKEY_CONTROL}},
74 {// kModifierRemappingNeoMod3 references this entry by index.
75 EF_MOD3_DOWN | EF_ALTGR_DOWN,
76 chromeos::ModifierKey::kNumModifierKeys,
77 nullptr,
78 {EF_MOD3_DOWN | EF_ALTGR_DOWN, DomCode::CAPS_LOCK, DomKey::ALT_GRAPH,
79 VKEY_ALTGR}},
80 {EF_COMMAND_DOWN,
81 chromeos::ModifierKey::kSearchKey,
82 prefs::kLanguageRemapSearchKeyTo,
83 {EF_COMMAND_DOWN, DomCode::META_LEFT, DomKey::META, VKEY_LWIN}},
84 {EF_ALT_DOWN,
85 chromeos::ModifierKey::kAltKey,
86 prefs::kLanguageRemapAltKeyTo,
87 {EF_ALT_DOWN, DomCode::ALT_LEFT, DomKey::ALT, VKEY_MENU}},
88 {EF_NONE,
89 chromeos::ModifierKey::kVoidKey,
90 nullptr,
91 {EF_NONE, DomCode::NONE, DomKey::NONE, VKEY_UNKNOWN}},
92 {EF_MOD3_DOWN,
93 chromeos::ModifierKey::kCapsLockKey,
94 prefs::kLanguageRemapCapsLockKeyTo,
95 {EF_MOD3_DOWN, DomCode::CAPS_LOCK, DomKey::CAPS_LOCK, VKEY_CAPITAL}},
96 {EF_NONE,
97 chromeos::ModifierKey::kEscapeKey,
98 prefs::kLanguageRemapEscapeKeyTo,
99 {EF_NONE, DomCode::ESCAPE, DomKey::ESCAPE, VKEY_ESCAPE}},
100 {EF_NONE,
101 chromeos::ModifierKey::kBackspaceKey,
102 prefs::kLanguageRemapBackspaceKeyTo,
103 {EF_NONE, DomCode::BACKSPACE, DomKey::BACKSPACE, VKEY_BACK}},
104 {EF_NONE,
105 chromeos::ModifierKey::kAssistantKey,
106 prefs::kLanguageRemapAssistantKeyTo,
107 {EF_NONE, DomCode::LAUNCH_ASSISTANT, DomKey::LAUNCH_ASSISTANT,
108 VKEY_ASSISTANT}}};
109
110 const EventRewriterChromeOS::MutableKeyState kCustomTopRowLayoutFKeys[] = {
111 {EF_NONE, DomCode::F1, DomKey::F1, VKEY_F1},
112 {EF_NONE, DomCode::F2, DomKey::F2, VKEY_F2},
113 {EF_NONE, DomCode::F3, DomKey::F3, VKEY_F3},
114 {EF_NONE, DomCode::F4, DomKey::F4, VKEY_F4},
115 {EF_NONE, DomCode::F5, DomKey::F5, VKEY_F5},
116 {EF_NONE, DomCode::F6, DomKey::F6, VKEY_F6},
117 {EF_NONE, DomCode::F7, DomKey::F7, VKEY_F7},
118 {EF_NONE, DomCode::F8, DomKey::F8, VKEY_F8},
119 {EF_NONE, DomCode::F9, DomKey::F9, VKEY_F9},
120 {EF_NONE, DomCode::F10, DomKey::F10, VKEY_F10},
121 {EF_NONE, DomCode::F11, DomKey::F11, VKEY_F11},
122 {EF_NONE, DomCode::F12, DomKey::F12, VKEY_F12},
123 {EF_NONE, DomCode::F13, DomKey::F13, VKEY_F13},
124 {EF_NONE, DomCode::F14, DomKey::F14, VKEY_F14},
125 {EF_NONE, DomCode::F15, DomKey::F15, VKEY_F15},
126 };
127 const size_t kAllFKeysSize = base::size(kCustomTopRowLayoutFKeys);
128 constexpr KeyboardCode kMaxCustomTopRowLayoutFKeyCode = VKEY_F15;
129
IsCustomLayoutFunctionKey(KeyboardCode key_code)130 bool IsCustomLayoutFunctionKey(KeyboardCode key_code) {
131 return key_code >= VKEY_F1 && key_code <= kMaxCustomTopRowLayoutFKeyCode;
132 }
133
134 const ModifierRemapping* kModifierRemappingNeoMod3 = &kModifierRemappings[1];
135
136 // Gets a remapped key for |pref_name| key. For example, to find out which
137 // key Ctrl is currently remapped to, call the function with
138 // prefs::kLanguageRemapControlKeyTo.
139 // Note: For the Search key, call GetSearchRemappedKey().
GetRemappedKey(const std::string & pref_name,EventRewriterChromeOS::Delegate * delegate)140 const ModifierRemapping* GetRemappedKey(
141 const std::string& pref_name,
142 EventRewriterChromeOS::Delegate* delegate) {
143 if (!delegate)
144 return nullptr;
145
146 int value = -1;
147 if (!delegate->GetKeyboardRemappedPrefValue(pref_name, &value))
148 return nullptr;
149
150 for (auto& remapping : kModifierRemappings) {
151 if (value == static_cast<int>(remapping.remap_to))
152 return &remapping;
153 }
154
155 return nullptr;
156 }
157
158 // Gets a remapped key for the Search key based on the |keyboard_type| of the
159 // last event. Internal Search key, Command key on external Apple keyboards, and
160 // Meta key (either Search or Windows) on external non-Apple keyboards can all
161 // be remapped separately.
GetSearchRemappedKey(EventRewriterChromeOS::Delegate * delegate,EventRewriterChromeOS::DeviceType keyboard_type)162 const ModifierRemapping* GetSearchRemappedKey(
163 EventRewriterChromeOS::Delegate* delegate,
164 EventRewriterChromeOS::DeviceType keyboard_type) {
165 std::string pref_name;
166 switch (keyboard_type) {
167 case EventRewriterChromeOS::kDeviceExternalAppleKeyboard:
168 pref_name = prefs::kLanguageRemapExternalCommandKeyTo;
169 break;
170
171 case EventRewriterChromeOS::kDeviceExternalGenericKeyboard:
172 case EventRewriterChromeOS::kDeviceExternalUnknown:
173 pref_name = prefs::kLanguageRemapExternalMetaKeyTo;
174 break;
175
176 case EventRewriterChromeOS::kDeviceExternalChromeOsKeyboard:
177 case EventRewriterChromeOS::kDeviceInternalKeyboard:
178 case EventRewriterChromeOS::kDeviceHotrodRemote:
179 case EventRewriterChromeOS::kDeviceVirtualCoreKeyboard:
180 case EventRewriterChromeOS::kDeviceUnknown:
181 // Use the preference for internal Search key remapping.
182 pref_name = prefs::kLanguageRemapSearchKeyTo;
183 break;
184 }
185
186 return GetRemappedKey(pref_name, delegate);
187 }
188
IsISOLevel5ShiftUsedByCurrentInputMethod()189 bool IsISOLevel5ShiftUsedByCurrentInputMethod() {
190 // Since both German Neo2 XKB layout and Caps Lock depend on Mod3Mask,
191 // it's not possible to make both features work. For now, we don't remap
192 // Mod3Mask when Neo2 is in use.
193 // TODO(yusukes): Remove the restriction.
194 ::chromeos::input_method::InputMethodManager* manager =
195 ::chromeos::input_method::InputMethodManager::Get();
196 return manager->IsISOLevel5ShiftUsedByCurrentInputMethod();
197 }
198
199 struct KeyboardRemapping {
200 // MatchKeyboardRemapping() succeeds if the tested has all of the specified
201 // flags (and possibly other flags), and either the key_code matches or the
202 // condition's key_code is VKEY_UNKNOWN.
203 struct Condition {
204 int flags;
205 KeyboardCode key_code;
206 } condition;
207 // ApplyRemapping(), which is the primary user of this structure,
208 // conditionally sets the output fields from the |result| here.
209 // - |dom_code| is set if |result.dom_code| is not NONE.
210 // - |dom_key| and |character| are set if |result.dom_key| is not NONE.
211 // -|key_code| is set if |result.key_code| is not VKEY_UNKNOWN.
212 // - |flags| are always set from |result.flags|, but this can be |EF_NONE|.
213 EventRewriterChromeOS::MutableKeyState result;
214 };
215
216 // If |strict| is true, the flags must match exactly the same. In other words,
217 // the event will be rewritten only if the exactly specified modifier is
218 // pressed. If false, it can match even if other modifiers are pressed.
MatchKeyboardRemapping(const EventRewriterChromeOS::MutableKeyState & suspect,const KeyboardRemapping::Condition & test,bool strict=false)219 bool MatchKeyboardRemapping(
220 const EventRewriterChromeOS::MutableKeyState& suspect,
221 const KeyboardRemapping::Condition& test,
222 bool strict = false) {
223 // Reset non modifier key event related flags for strict mode.
224 constexpr int kKeyEventModifiersMask = EF_SHIFT_DOWN | EF_CONTROL_DOWN |
225 EF_ALT_DOWN | EF_COMMAND_DOWN |
226 EF_ALTGR_DOWN | EF_MOD3_DOWN;
227
228 const int suspect_flags_for_strict = suspect.flags & kKeyEventModifiersMask;
229 const bool flag_matched = strict
230 ? suspect_flags_for_strict == test.flags
231 : ((suspect.flags & test.flags) == test.flags);
232 return flag_matched && ((test.key_code == VKEY_UNKNOWN) ||
233 (test.key_code == suspect.key_code));
234 }
235
ApplyRemapping(const EventRewriterChromeOS::MutableKeyState & changes,EventRewriterChromeOS::MutableKeyState * state)236 void ApplyRemapping(const EventRewriterChromeOS::MutableKeyState& changes,
237 EventRewriterChromeOS::MutableKeyState* state) {
238 state->flags |= changes.flags;
239 if (changes.code != DomCode::NONE)
240 state->code = changes.code;
241 if (changes.key != DomKey::NONE)
242 state->key = changes.key;
243 if (changes.key_code != VKEY_UNKNOWN)
244 state->key_code = changes.key_code;
245 }
246
247 // Given a set of KeyboardRemapping structs, finds a matching struct
248 // if possible, and updates the remapped event values. Returns true if a
249 // remapping was found and remapped values were updated.
250 // See MatchKeyboardRemapping() for |strict|.
RewriteWithKeyboardRemappings(const KeyboardRemapping * mappings,size_t num_mappings,const EventRewriterChromeOS::MutableKeyState & input_state,EventRewriterChromeOS::MutableKeyState * remapped_state,bool strict=false)251 bool RewriteWithKeyboardRemappings(
252 const KeyboardRemapping* mappings,
253 size_t num_mappings,
254 const EventRewriterChromeOS::MutableKeyState& input_state,
255 EventRewriterChromeOS::MutableKeyState* remapped_state,
256 bool strict = false) {
257 for (size_t i = 0; i < num_mappings; ++i) {
258 const KeyboardRemapping& map = mappings[i];
259 if (MatchKeyboardRemapping(input_state, map.condition, strict)) {
260 remapped_state->flags = (input_state.flags & ~map.condition.flags);
261 ApplyRemapping(map.result, remapped_state);
262 return true;
263 }
264 }
265 return false;
266 }
267
SetMeaningForLayout(EventType type,EventRewriterChromeOS::MutableKeyState * state)268 void SetMeaningForLayout(EventType type,
269 EventRewriterChromeOS::MutableKeyState* state) {
270 // Currently layout is applied by creating a temporary key event with the
271 // current physical state, and extracting the layout results.
272 KeyEvent key(type, state->key_code, state->code, state->flags);
273 state->key = key.GetDomKey();
274 }
275
RelocateModifier(DomCode code,DomKeyLocation location)276 DomCode RelocateModifier(DomCode code, DomKeyLocation location) {
277 bool right = (location == DomKeyLocation::RIGHT);
278 switch (code) {
279 case DomCode::CONTROL_LEFT:
280 case DomCode::CONTROL_RIGHT:
281 return right ? DomCode::CONTROL_RIGHT : DomCode::CONTROL_LEFT;
282 case DomCode::SHIFT_LEFT:
283 case DomCode::SHIFT_RIGHT:
284 return right ? DomCode::SHIFT_RIGHT : DomCode::SHIFT_LEFT;
285 case DomCode::ALT_LEFT:
286 case DomCode::ALT_RIGHT:
287 return right ? DomCode::ALT_RIGHT : DomCode::ALT_LEFT;
288 case DomCode::META_LEFT:
289 case DomCode::META_RIGHT:
290 return right ? DomCode::META_RIGHT : DomCode::META_LEFT;
291 default:
292 break;
293 }
294 return code;
295 }
296
297 // Returns true if |mouse_event| was generated from a touchpad device.
IsFromTouchpadDevice(const MouseEvent & mouse_event)298 bool IsFromTouchpadDevice(const MouseEvent& mouse_event) {
299 for (const InputDevice& touchpad :
300 DeviceDataManager::GetInstance()->GetTouchpadDevices()) {
301 if (touchpad.id == mouse_event.source_device_id())
302 return true;
303 }
304
305 return false;
306 }
307
308 // Returns true if |value| is replaced with the specific device property value
309 // without getting an error.
GetDeviceProperty(const base::FilePath & device_path,const char * key,std::string * value)310 bool GetDeviceProperty(const base::FilePath& device_path,
311 const char* key,
312 std::string* value) {
313 device::ScopedUdevPtr udev(device::udev_new());
314 if (!udev.get())
315 return false;
316
317 device::ScopedUdevDevicePtr device(device::udev_device_new_from_syspath(
318 udev.get(), device_path.value().c_str()));
319 if (!device.get())
320 return false;
321
322 *value = device::UdevDeviceGetPropertyValue(device.get(), key);
323 return true;
324 }
325
326 // Returns true if |value| is replaced with the specific device attribute value
327 // without getting an error. |device_path| should be obtained from the
328 // |InputDevice.sys_path| field.
GetDeviceAttributeRecursive(const base::FilePath & device_path,const char * key,std::string * value)329 bool GetDeviceAttributeRecursive(const base::FilePath& device_path,
330 const char* key,
331 std::string* value) {
332 device::ScopedUdevPtr udev(device::udev_new());
333 if (!udev.get())
334 return false;
335
336 device::ScopedUdevDevicePtr device(device::udev_device_new_from_syspath(
337 udev.get(), device_path.value().c_str()));
338 if (!device.get())
339 return false;
340
341 *value = device::UdevDeviceRecursiveGetSysattrValue(device.get(), key);
342 return true;
343 }
344
345 constexpr char kLayoutProperty[] = "CROS_KEYBOARD_TOP_ROW_LAYOUT";
346 constexpr char kCustomTopRowLayoutAttribute[] = "function_row_physmap";
347 constexpr char kCustomTopRowLayoutProperty[] = "FUNCTION_ROW_PHYSMAP";
348
GetTopRowLayoutProperty(const InputDevice & keyboard_device,std::string * out_prop)349 bool GetTopRowLayoutProperty(const InputDevice& keyboard_device,
350 std::string* out_prop) {
351 return GetDeviceProperty(keyboard_device.sys_path, kLayoutProperty, out_prop);
352 }
353
354 // Parses keyboard to row layout string. Returns true if data is valid.
ParseKeyboardTopRowLayout(const std::string & layout_string,EventRewriterChromeOS::KeyboardTopRowLayout * out_layout)355 bool ParseKeyboardTopRowLayout(
356 const std::string& layout_string,
357 EventRewriterChromeOS::KeyboardTopRowLayout* out_layout) {
358 if (layout_string.empty()) {
359 *out_layout = EventRewriterChromeOS::kKbdTopRowLayoutDefault;
360 return true;
361 }
362
363 int layout_id;
364 if (!base::StringToInt(layout_string, &layout_id)) {
365 LOG(WARNING) << "Failed to parse layout " << kLayoutProperty << " value '"
366 << layout_string << "'";
367 return false;
368 }
369 if (layout_id < EventRewriterChromeOS::kKbdTopRowLayoutMin ||
370 layout_id > EventRewriterChromeOS::kKbdTopRowLayoutMax) {
371 LOG(WARNING) << "Invalid " << kLayoutProperty << " '" << layout_string
372 << "'";
373 return false;
374 }
375 *out_layout =
376 static_cast<EventRewriterChromeOS::KeyboardTopRowLayout>(layout_id);
377 return true;
378 }
379
380 // Parses the custom top row layout string. The string contains a space
381 // separated list of scan codes in hex. eg "aa ab ac" for F1, F2, F3, etc.
382 // Returns true if the string can be parsed.
ParseCustomTopRowLayoutMap(const std::string & layout,base::flat_map<uint32_t,EventRewriterChromeOS::MutableKeyState> * out_scan_code_map)383 bool ParseCustomTopRowLayoutMap(
384 const std::string& layout,
385 base::flat_map<uint32_t, EventRewriterChromeOS::MutableKeyState>*
386 out_scan_code_map) {
387 const std::vector<std::string> scan_code_strings = base::SplitString(
388 layout, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
389 if (scan_code_strings.size() == 0 ||
390 scan_code_strings.size() > kAllFKeysSize) {
391 return false;
392 }
393
394 base::flat_map<uint32_t, EventRewriterChromeOS::MutableKeyState>
395 scan_code_map;
396 for (size_t i = 0; i < scan_code_strings.size(); i++) {
397 uint32_t scan_code = 0;
398 if (!base::HexStringToUInt(scan_code_strings[i], &scan_code)) {
399 return false;
400 }
401
402 scan_code_map[scan_code] = kCustomTopRowLayoutFKeys[i];
403 }
404
405 *out_scan_code_map = std::move(scan_code_map);
406 return true;
407 }
408
GetCustomTopRowLayoutAttribute(const InputDevice & keyboard_device,std::string * out_prop)409 bool GetCustomTopRowLayoutAttribute(const InputDevice& keyboard_device,
410 std::string* out_prop) {
411 bool result = GetDeviceAttributeRecursive(
412 keyboard_device.sys_path, kCustomTopRowLayoutAttribute, out_prop);
413
414 if (result && out_prop->size() > 0) {
415 VLOG(1) << "Identified custom top row keyboard layout: sys_path="
416 << keyboard_device.sys_path << " layout=" << *out_prop;
417 return true;
418 }
419
420 return false;
421 }
422
GetCustomTopRowLayout(const InputDevice & keyboard_device,std::string * out_prop)423 bool GetCustomTopRowLayout(const InputDevice& keyboard_device,
424 std::string* out_prop) {
425 if (GetCustomTopRowLayoutAttribute(keyboard_device, out_prop))
426 return true;
427 return GetDeviceProperty(keyboard_device.sys_path,
428 kCustomTopRowLayoutProperty, out_prop);
429 }
430
HasCustomTopRowLayout(const InputDevice & keyboard_device)431 bool HasCustomTopRowLayout(const InputDevice& keyboard_device) {
432 std::string layout;
433 base::flat_map<uint32_t, EventRewriterChromeOS::MutableKeyState> top_row_map;
434 return GetCustomTopRowLayout(keyboard_device, &layout) &&
435 ParseCustomTopRowLayoutMap(layout, &top_row_map);
436 }
437
438 // Returns whether |key_code| appears as one of the key codes that might be
439 // remapped by table mappings.
IsKeyCodeInMappings(KeyboardCode key_code,const KeyboardRemapping * mappings,size_t num_mappings)440 bool IsKeyCodeInMappings(KeyboardCode key_code,
441 const KeyboardRemapping* mappings,
442 size_t num_mappings) {
443 for (size_t i = 0; i < num_mappings; ++i) {
444 const KeyboardRemapping& map = mappings[i];
445 if (key_code == map.condition.key_code) {
446 return true;
447 }
448 }
449 return false;
450 }
451
452 // Returns true if all bits in |flag_mask| are set in |flags|.
AreFlagsSet(int flags,int flag_mask)453 bool AreFlagsSet(int flags, int flag_mask) {
454 return (flags & flag_mask) == flag_mask;
455 }
456
457 // Determines the type of |keyboard_device| we are dealing with.
458 // |has_chromeos_top_row| argument indicates that the keyboard's top
459 // row has "action" keys (such as back, refresh, etc.) instead of the
460 // standard F1-F12 keys.
IdentifyKeyboardType(const InputDevice & keyboard_device,bool has_chromeos_top_row)461 EventRewriterChromeOS::DeviceType IdentifyKeyboardType(
462 const InputDevice& keyboard_device,
463 bool has_chromeos_top_row) {
464 if (keyboard_device.vendor_id == kHotrodRemoteVendorId &&
465 keyboard_device.product_id == kHotrodRemoteProductId) {
466 VLOG(1) << "Hotrod remote '" << keyboard_device.name
467 << "' connected: id=" << keyboard_device.id;
468 return EventRewriterChromeOS::kDeviceHotrodRemote;
469 }
470
471 if (base::LowerCaseEqualsASCII(keyboard_device.name,
472 "virtual core keyboard")) {
473 VLOG(1) << "Xorg virtual '" << keyboard_device.name
474 << "' connected: id=" << keyboard_device.id;
475 return EventRewriterChromeOS::kDeviceVirtualCoreKeyboard;
476 }
477
478 if (keyboard_device.type == INPUT_DEVICE_INTERNAL) {
479 VLOG(1) << "Internal keyboard '" << keyboard_device.name
480 << "' connected: id=" << keyboard_device.id;
481 return EventRewriterChromeOS::kDeviceInternalKeyboard;
482 }
483
484 // This is an external device.
485 if (has_chromeos_top_row) {
486 // If the device was tagged as having Chrome OS top row layout it must be a
487 // Chrome OS keyboard.
488 VLOG(1) << "External Chrome OS keyboard '" << keyboard_device.name
489 << "' connected: id=" << keyboard_device.id;
490 return EventRewriterChromeOS::kDeviceExternalChromeOsKeyboard;
491 }
492
493 const std::vector<std::string> tokens =
494 base::SplitString(keyboard_device.name, " .", base::KEEP_WHITESPACE,
495 base::SPLIT_WANT_NONEMPTY);
496
497 // Parse |device_name| to help classify it.
498 bool found_apple = false;
499 bool found_keyboard = false;
500 for (size_t i = 0; i < tokens.size(); ++i) {
501 if (!found_apple && base::LowerCaseEqualsASCII(tokens[i], "apple"))
502 found_apple = true;
503 if (!found_keyboard && base::LowerCaseEqualsASCII(tokens[i], "keyboard"))
504 found_keyboard = true;
505 }
506 if (found_apple) {
507 // If the |device_name| contains the two words, "apple" and "keyboard",
508 // treat it as an Apple keyboard.
509 if (found_keyboard) {
510 VLOG(1) << "Apple keyboard '" << keyboard_device.name
511 << "' connected: id=" << keyboard_device.id;
512 return EventRewriterChromeOS::kDeviceExternalAppleKeyboard;
513 } else {
514 VLOG(1) << "Apple device '" << keyboard_device.name
515 << "' connected: id=" << keyboard_device.id;
516 return EventRewriterChromeOS::kDeviceExternalUnknown;
517 }
518 } else if (found_keyboard) {
519 VLOG(1) << "External keyboard '" << keyboard_device.name
520 << "' connected: id=" << keyboard_device.id;
521 return EventRewriterChromeOS::kDeviceExternalGenericKeyboard;
522 } else {
523 VLOG(1) << "External device '" << keyboard_device.name
524 << "' connected: id=" << keyboard_device.id;
525 return EventRewriterChromeOS::kDeviceExternalUnknown;
526 }
527 }
528
IdentifyKeyboard(const InputDevice & keyboard_device,EventRewriterChromeOS::DeviceType * out_type,EventRewriterChromeOS::KeyboardTopRowLayout * out_layout)529 bool IdentifyKeyboard(const InputDevice& keyboard_device,
530 EventRewriterChromeOS::DeviceType* out_type,
531 EventRewriterChromeOS::KeyboardTopRowLayout* out_layout) {
532 std::string layout_string;
533 EventRewriterChromeOS::KeyboardTopRowLayout layout;
534 const bool has_custom_top_row = HasCustomTopRowLayout(keyboard_device);
535 if (has_custom_top_row) {
536 layout = EventRewriterChromeOS::kKbdTopRowLayoutCustom;
537 } else if (!GetTopRowLayoutProperty(keyboard_device, &layout_string) ||
538 !ParseKeyboardTopRowLayout(layout_string, &layout)) {
539 *out_type = EventRewriterChromeOS::kDeviceUnknown;
540 *out_layout = EventRewriterChromeOS::kKbdTopRowLayoutDefault;
541 return false;
542 }
543
544 *out_type = IdentifyKeyboardType(
545 keyboard_device, has_custom_top_row || !layout_string.empty());
546 *out_layout = layout;
547 return true;
548 }
549
550 } // namespace
551
552 ///////////////////////////////////////////////////////////////////////////////
553
MutableKeyState()554 EventRewriterChromeOS::MutableKeyState::MutableKeyState()
555 : MutableKeyState(0, DomCode::NONE, 0, KeyboardCode::VKEY_NONAME) {}
556
MutableKeyState(const KeyEvent * key_event)557 EventRewriterChromeOS::MutableKeyState::MutableKeyState(
558 const KeyEvent* key_event)
559 : MutableKeyState(key_event->flags(),
560 key_event->code(),
561 key_event->GetDomKey(),
562 key_event->key_code()) {}
563
MutableKeyState(int input_flags,DomCode input_code,DomKey::Base input_key,KeyboardCode input_key_code)564 EventRewriterChromeOS::MutableKeyState::MutableKeyState(
565 int input_flags,
566 DomCode input_code,
567 DomKey::Base input_key,
568 KeyboardCode input_key_code)
569 : flags(input_flags),
570 code(input_code),
571 key(input_key),
572 key_code(input_key_code) {}
573
574 ///////////////////////////////////////////////////////////////////////////////
575
EventRewriterChromeOS(Delegate * delegate,EventRewriter * sticky_keys_controller,bool privacy_screen_supported)576 EventRewriterChromeOS::EventRewriterChromeOS(
577 Delegate* delegate,
578 EventRewriter* sticky_keys_controller,
579 bool privacy_screen_supported)
580 : last_keyboard_device_id_(ED_UNKNOWN_DEVICE),
581 ime_keyboard_for_testing_(nullptr),
582 delegate_(delegate),
583 sticky_keys_controller_(sticky_keys_controller),
584 privacy_screen_supported_(privacy_screen_supported),
585 pressed_modifier_latches_(EF_NONE),
586 latched_modifier_latches_(EF_NONE),
587 used_modifier_latches_(EF_NONE) {}
588
~EventRewriterChromeOS()589 EventRewriterChromeOS::~EventRewriterChromeOS() {}
590
KeyboardDeviceAddedForTesting(int device_id)591 void EventRewriterChromeOS::KeyboardDeviceAddedForTesting(int device_id) {
592 KeyboardDeviceAdded(device_id);
593 }
594
ResetStateForTesting()595 void EventRewriterChromeOS::ResetStateForTesting() {
596 pressed_key_states_.clear();
597
598 pressed_modifier_latches_ = latched_modifier_latches_ =
599 used_modifier_latches_ = EF_NONE;
600 }
601
RewriteMouseButtonEventForTesting(const MouseEvent & event,const Continuation continuation)602 void EventRewriterChromeOS::RewriteMouseButtonEventForTesting(
603 const MouseEvent& event,
604 const Continuation continuation) {
605 RewriteMouseButtonEvent(event, continuation);
606 }
607
RewriteEvent(const Event & event,const Continuation continuation)608 EventDispatchDetails EventRewriterChromeOS::RewriteEvent(
609 const Event& event,
610 const Continuation continuation) {
611 if ((event.type() == ET_KEY_PRESSED) || (event.type() == ET_KEY_RELEASED)) {
612 std::unique_ptr<Event> rewritten_event;
613 EventRewriteStatus status =
614 RewriteKeyEvent(*((&event)->AsKeyEvent()), &rewritten_event);
615 return RewriteKeyEventInContext(*((&event)->AsKeyEvent()),
616 std::move(rewritten_event), status,
617 continuation);
618 }
619 if ((event.type() == ET_MOUSE_PRESSED) ||
620 (event.type() == ET_MOUSE_RELEASED)) {
621 return RewriteMouseButtonEvent(static_cast<const MouseEvent&>(event),
622 continuation);
623 }
624 if (event.type() == ET_MOUSEWHEEL) {
625 return RewriteMouseWheelEvent(static_cast<const MouseWheelEvent&>(event),
626 continuation);
627 }
628 if ((event.type() == ET_TOUCH_PRESSED) ||
629 (event.type() == ET_TOUCH_RELEASED)) {
630 return RewriteTouchEvent(static_cast<const TouchEvent&>(event),
631 continuation);
632 }
633 if (event.IsScrollEvent()) {
634 return RewriteScrollEvent(static_cast<const ScrollEvent&>(event),
635 continuation);
636 }
637
638 return SendEvent(continuation, &event);
639 }
640
BuildRewrittenKeyEvent(const KeyEvent & key_event,const MutableKeyState & state,std::unique_ptr<Event> * rewritten_event)641 void EventRewriterChromeOS::BuildRewrittenKeyEvent(
642 const KeyEvent& key_event,
643 const MutableKeyState& state,
644 std::unique_ptr<Event>* rewritten_event) {
645 auto key_event_ptr = std::make_unique<KeyEvent>(
646 key_event.type(), state.key_code, state.code, state.flags, state.key,
647 key_event.time_stamp());
648 key_event_ptr->set_scan_code(key_event.scan_code());
649 *rewritten_event = std::move(key_event_ptr);
650 }
651
652 // static
GetDeviceType(const InputDevice & keyboard_device)653 EventRewriterChromeOS::DeviceType EventRewriterChromeOS::GetDeviceType(
654 const InputDevice& keyboard_device) {
655 DeviceType type;
656 KeyboardTopRowLayout layout;
657 if (IdentifyKeyboard(keyboard_device, &type, &layout))
658 return type;
659
660 return EventRewriterChromeOS::kDeviceUnknown;
661 }
662
663 // static
664 EventRewriterChromeOS::KeyboardTopRowLayout
GetKeyboardTopRowLayout(const InputDevice & keyboard_device)665 EventRewriterChromeOS::GetKeyboardTopRowLayout(
666 const InputDevice& keyboard_device) {
667 DeviceType type;
668 KeyboardTopRowLayout layout;
669 if (IdentifyKeyboard(keyboard_device, &type, &layout))
670 return layout;
671
672 return kKbdTopRowLayoutDefault;
673 }
674
675 // static
HasAssistantKeyOnKeyboard(const InputDevice & keyboard_device,bool * has_assistant_key)676 bool EventRewriterChromeOS::HasAssistantKeyOnKeyboard(
677 const InputDevice& keyboard_device,
678 bool* has_assistant_key) {
679 const char kDevNameProperty[] = "DEVNAME";
680 std::string dev_name;
681 if (!GetDeviceProperty(keyboard_device.sys_path, kDevNameProperty,
682 &dev_name) ||
683 dev_name.empty()) {
684 return false;
685 }
686
687 base::ScopedFD fd(open(dev_name.c_str(), O_RDONLY));
688 if (fd.get() < 0) {
689 LOG(ERROR) << "Cannot open " << dev_name.c_str() << " : " << errno;
690 return false;
691 }
692
693 EventDeviceInfo devinfo;
694 if (!devinfo.Initialize(fd.get(), keyboard_device.sys_path)) {
695 LOG(ERROR) << "Failed to get device information for "
696 << keyboard_device.sys_path.value();
697 return false;
698 }
699
700 *has_assistant_key = devinfo.HasKeyEvent(KEY_ASSISTANT);
701 return true;
702 }
703
RewriteModifierKeys(const KeyEvent & key_event,MutableKeyState * state)704 bool EventRewriterChromeOS::RewriteModifierKeys(const KeyEvent& key_event,
705 MutableKeyState* state) {
706 DCHECK(key_event.type() == ET_KEY_PRESSED ||
707 key_event.type() == ET_KEY_RELEASED);
708
709 if (!delegate_ || !delegate_->RewriteModifierKeys())
710 return false;
711
712 // Preserve a copy of the original before rewriting |state| based on
713 // user preferences, device configuration, and certain IME properties.
714 MutableKeyState incoming = *state;
715 state->flags = EF_NONE;
716 int characteristic_flag = EF_NONE;
717 bool exact_event = false;
718
719 // First, remap the key code.
720 const ModifierRemapping* remapped_key = nullptr;
721 // Remapping based on DomKey.
722 switch (incoming.key) {
723 case DomKey::ALT_GRAPH:
724 // The Neo2 codes modifiers such that CapsLock appears as VKEY_ALTGR,
725 // but AltGraph (right Alt) also appears as VKEY_ALTGR in Neo2,
726 // as it does in other layouts. Neo2's "Mod3" is represented in
727 // EventFlags by a combination of AltGr+Mod3, while its "Mod4" is
728 // AltGr alone.
729 if (IsISOLevel5ShiftUsedByCurrentInputMethod()) {
730 if (incoming.code == DomCode::CAPS_LOCK) {
731 characteristic_flag = EF_ALTGR_DOWN | EF_MOD3_DOWN;
732 remapped_key =
733 GetRemappedKey(prefs::kLanguageRemapCapsLockKeyTo, delegate_);
734 } else {
735 characteristic_flag = EF_ALTGR_DOWN;
736 remapped_key = GetSearchRemappedKey(delegate_, GetLastKeyboardType());
737 }
738 }
739 if (remapped_key && remapped_key->result.key_code == VKEY_CAPITAL)
740 remapped_key = kModifierRemappingNeoMod3;
741 break;
742 case DomKey::ALT_GRAPH_LATCH:
743 if (key_event.type() == ET_KEY_PRESSED) {
744 pressed_modifier_latches_ |= EF_ALTGR_DOWN;
745 } else {
746 pressed_modifier_latches_ &= ~EF_ALTGR_DOWN;
747 if (used_modifier_latches_ & EF_ALTGR_DOWN)
748 used_modifier_latches_ &= ~EF_ALTGR_DOWN;
749 else
750 latched_modifier_latches_ |= EF_ALTGR_DOWN;
751 }
752 // Rewrite to AltGraph. When this key is used like a regular modifier,
753 // the web-exposed result looks like a use of the regular modifier.
754 // When it's used as a latch, the web-exposed result is a vacuous
755 // modifier press-and-release, which should be harmless, but preserves
756 // the event for applications using the |code| (e.g. remoting).
757 state->key = DomKey::ALT_GRAPH;
758 state->key_code = VKEY_ALTGR;
759 exact_event = true;
760 break;
761 default:
762 break;
763 }
764
765 // Remapping based on DomCode.
766 switch (incoming.code) {
767 // On Chrome OS, Caps_Lock with Mod3Mask is sent when Caps Lock is pressed
768 // (with one exception: when IsISOLevel5ShiftUsedByCurrentInputMethod() is
769 // true, the key generates XK_ISO_Level3_Shift with Mod3Mask, not
770 // Caps_Lock).
771 case DomCode::CAPS_LOCK:
772 // This key is already remapped to Mod3 in remapping based on DomKey. Skip
773 // more remapping.
774 if (IsISOLevel5ShiftUsedByCurrentInputMethod() && remapped_key)
775 break;
776
777 characteristic_flag = EF_CAPS_LOCK_ON;
778 remapped_key =
779 GetRemappedKey(prefs::kLanguageRemapCapsLockKeyTo, delegate_);
780 break;
781 case DomCode::META_LEFT:
782 case DomCode::META_RIGHT:
783 characteristic_flag = EF_COMMAND_DOWN;
784 remapped_key = GetSearchRemappedKey(delegate_, GetLastKeyboardType());
785 // Default behavior is Super key, hence don't remap the event if the pref
786 // is unavailable.
787 break;
788 case DomCode::CONTROL_LEFT:
789 case DomCode::CONTROL_RIGHT:
790 characteristic_flag = EF_CONTROL_DOWN;
791 remapped_key =
792 GetRemappedKey(prefs::kLanguageRemapControlKeyTo, delegate_);
793 break;
794 case DomCode::ALT_LEFT:
795 case DomCode::ALT_RIGHT:
796 // ALT key
797 characteristic_flag = EF_ALT_DOWN;
798 remapped_key = GetRemappedKey(prefs::kLanguageRemapAltKeyTo, delegate_);
799 break;
800 case DomCode::ESCAPE:
801 remapped_key =
802 GetRemappedKey(prefs::kLanguageRemapEscapeKeyTo, delegate_);
803 break;
804 case DomCode::BACKSPACE:
805 remapped_key =
806 GetRemappedKey(prefs::kLanguageRemapBackspaceKeyTo, delegate_);
807 break;
808 case DomCode::LAUNCH_ASSISTANT:
809 remapped_key =
810 GetRemappedKey(prefs::kLanguageRemapAssistantKeyTo, delegate_);
811 break;
812 default:
813 break;
814 }
815
816 if (remapped_key) {
817 state->key_code = remapped_key->result.key_code;
818 state->code = remapped_key->result.code;
819 state->key = remapped_key->result.key;
820 incoming.flags |= characteristic_flag;
821 characteristic_flag = remapped_key->flag;
822 if (incoming.key_code == VKEY_CAPITAL) {
823 // Caps Lock is rewritten to another key event, remove EF_CAPS_LOCK_ON
824 // flag to prevent the keyboard's Caps Lock state being synced to the
825 // rewritten key event's flag in InputMethodChromeOS.
826 incoming.flags &= ~EF_CAPS_LOCK_ON;
827 }
828 if (remapped_key->remap_to == chromeos::ModifierKey::kCapsLockKey)
829 characteristic_flag |= EF_CAPS_LOCK_ON;
830 state->code = RelocateModifier(
831 state->code, KeycodeConverter::DomCodeToLocation(incoming.code));
832 }
833
834 // Next, remap modifier bits.
835 state->flags |= GetRemappedModifierMasks(key_event, incoming.flags);
836
837 // If the DomKey is not a modifier before remapping but is after, set the
838 // modifier latches for the later non-modifier key's modifier states.
839 bool non_modifier_to_modifier =
840 !KeycodeConverter::IsDomKeyForModifier(incoming.key) &&
841 KeycodeConverter::IsDomKeyForModifier(state->key);
842 if (key_event.type() == ET_KEY_PRESSED) {
843 state->flags |= characteristic_flag;
844 if (non_modifier_to_modifier)
845 pressed_modifier_latches_ |= characteristic_flag;
846 } else {
847 state->flags &= ~characteristic_flag;
848 if (non_modifier_to_modifier)
849 pressed_modifier_latches_ &= ~characteristic_flag;
850 }
851
852 if (key_event.type() == ET_KEY_PRESSED) {
853 if (!KeycodeConverter::IsDomKeyForModifier(state->key)) {
854 used_modifier_latches_ |= pressed_modifier_latches_;
855 latched_modifier_latches_ = EF_NONE;
856 }
857 }
858
859 // Implement the Caps Lock modifier here, rather than in the
860 // AcceleratorController, so that the event is visible to apps (see
861 // crbug.com/775743).
862 if (key_event.type() == ET_KEY_RELEASED && state->key_code == VKEY_CAPITAL) {
863 ::chromeos::input_method::ImeKeyboard* ime_keyboard =
864 ime_keyboard_for_testing_
865 ? ime_keyboard_for_testing_
866 : ::chromeos::input_method::InputMethodManager::Get()
867 ->GetImeKeyboard();
868 ime_keyboard->SetCapsLockEnabled(!ime_keyboard->CapsLockIsEnabled());
869 }
870 return exact_event;
871 }
872
DeviceKeyPressedOrReleased(int device_id)873 void EventRewriterChromeOS::DeviceKeyPressedOrReleased(int device_id) {
874 const auto iter = device_id_to_info_.find(device_id);
875 DeviceType type;
876 if (iter != device_id_to_info_.end())
877 type = iter->second.type;
878 else
879 type = KeyboardDeviceAdded(device_id);
880
881 // Ignore virtual Xorg keyboard (magic that generates key repeat
882 // events). Pretend that the previous real keyboard is the one that is still
883 // in use.
884 if (type == kDeviceVirtualCoreKeyboard)
885 return;
886
887 last_keyboard_device_id_ = device_id;
888 }
889
IsHotrodRemote() const890 bool EventRewriterChromeOS::IsHotrodRemote() const {
891 return IsLastKeyboardOfType(kDeviceHotrodRemote);
892 }
893
IsLastKeyboardOfType(DeviceType device_type) const894 bool EventRewriterChromeOS::IsLastKeyboardOfType(DeviceType device_type) const {
895 return GetLastKeyboardType() == device_type;
896 }
897
GetLastKeyboardType() const898 EventRewriterChromeOS::DeviceType EventRewriterChromeOS::GetLastKeyboardType()
899 const {
900 if (last_keyboard_device_id_ == ED_UNKNOWN_DEVICE)
901 return kDeviceUnknown;
902
903 const auto iter = device_id_to_info_.find(last_keyboard_device_id_);
904 if (iter == device_id_to_info_.end()) {
905 LOG(ERROR) << "Device ID " << last_keyboard_device_id_ << " is unknown.";
906 return kDeviceUnknown;
907 }
908
909 return iter->second.type;
910 }
911
GetRemappedModifierMasks(const Event & event,int original_flags) const912 int EventRewriterChromeOS::GetRemappedModifierMasks(const Event& event,
913 int original_flags) const {
914 int unmodified_flags = original_flags;
915 int rewritten_flags = pressed_modifier_latches_ | latched_modifier_latches_;
916 for (size_t i = 0; unmodified_flags && (i < base::size(kModifierRemappings));
917 ++i) {
918 const ModifierRemapping* remapped_key = nullptr;
919 if (!(unmodified_flags & kModifierRemappings[i].flag))
920 continue;
921 switch (kModifierRemappings[i].flag) {
922 case EF_COMMAND_DOWN:
923 remapped_key = GetSearchRemappedKey(delegate_, GetLastKeyboardType());
924 break;
925 case EF_MOD3_DOWN:
926 // If EF_MOD3_DOWN is used by the current input method, leave it alone;
927 // it is not remappable.
928 if (IsISOLevel5ShiftUsedByCurrentInputMethod())
929 continue;
930 // Otherwise, Mod3Mask is set on X events when the Caps Lock key
931 // is down, but, if Caps Lock is remapped, CapsLock is NOT set,
932 // because pressing the key does not invoke caps lock. So, the
933 // kModifierRemappings[] table uses EF_MOD3_DOWN for the Caps
934 // Lock remapping.
935 break;
936 case EF_MOD3_DOWN | EF_ALTGR_DOWN:
937 if ((original_flags & EF_ALTGR_DOWN) &&
938 IsISOLevel5ShiftUsedByCurrentInputMethod()) {
939 remapped_key = kModifierRemappingNeoMod3;
940 }
941 break;
942 default:
943 break;
944 }
945 if (!remapped_key && kModifierRemappings[i].pref_name) {
946 remapped_key =
947 GetRemappedKey(kModifierRemappings[i].pref_name, delegate_);
948 }
949 if (remapped_key) {
950 unmodified_flags &= ~kModifierRemappings[i].flag;
951 rewritten_flags |= remapped_key->flag;
952 }
953 }
954 return rewritten_flags | unmodified_flags;
955 }
956
ShouldRemapToRightClick(const MouseEvent & mouse_event,int flags,int * matched_mask) const957 bool EventRewriterChromeOS::ShouldRemapToRightClick(
958 const MouseEvent& mouse_event,
959 int flags,
960 int* matched_mask) const {
961 *matched_mask = 0;
962 if (base::FeatureList::IsEnabled(
963 ::chromeos::features::kUseSearchClickForRightClick)) {
964 if (AreFlagsSet(flags, kSearchLeftButton)) {
965 *matched_mask = kSearchLeftButton;
966 }
967 } else {
968 if (AreFlagsSet(flags, kAltLeftButton)) {
969 *matched_mask = kAltLeftButton;
970 }
971 }
972
973 return (*matched_mask != 0) &&
974 ((mouse_event.type() == ET_MOUSE_PRESSED) ||
975 pressed_device_ids_.count(mouse_event.source_device_id())) &&
976 IsFromTouchpadDevice(mouse_event);
977 }
978
RewriteKeyEvent(const KeyEvent & key_event,std::unique_ptr<Event> * rewritten_event)979 EventRewriteStatus EventRewriterChromeOS::RewriteKeyEvent(
980 const KeyEvent& key_event,
981 std::unique_ptr<Event>* rewritten_event) {
982 if (delegate_ && delegate_->IsExtensionCommandRegistered(key_event.key_code(),
983 key_event.flags()))
984 return EVENT_REWRITE_CONTINUE;
985 if (key_event.source_device_id() != ED_UNKNOWN_DEVICE)
986 DeviceKeyPressedOrReleased(key_event.source_device_id());
987
988 // Drop repeated keys from Hotrod remote.
989 if ((key_event.flags() & EF_IS_REPEAT) &&
990 (key_event.type() == ET_KEY_PRESSED) && IsHotrodRemote() &&
991 key_event.key_code() != VKEY_BACK) {
992 return EVENT_REWRITE_DISCARD;
993 }
994
995 MutableKeyState state = {key_event.flags(), key_event.code(),
996 key_event.GetDomKey(), key_event.key_code()};
997
998 // Do not rewrite an event sent by ui_controls::SendKeyPress(). See
999 // crbug.com/136465.
1000 if (!(key_event.flags() & EF_FINAL)) {
1001 if (RewriteModifierKeys(key_event, &state)) {
1002 // Early exit with completed event.
1003 BuildRewrittenKeyEvent(key_event, state, rewritten_event);
1004 return EVENT_REWRITE_REWRITTEN;
1005 }
1006 RewriteNumPadKeys(key_event, &state);
1007 }
1008
1009 EventRewriteStatus status = EVENT_REWRITE_CONTINUE;
1010 bool is_sticky_key_extension_command = false;
1011 if (sticky_keys_controller_) {
1012 KeyEvent tmp_event = key_event;
1013 tmp_event.set_key_code(state.key_code);
1014 tmp_event.set_flags(state.flags);
1015 std::unique_ptr<Event> output_event;
1016 status = sticky_keys_controller_->RewriteEvent(tmp_event, &output_event);
1017 if (status == EVENT_REWRITE_REWRITTEN ||
1018 status == EVENT_REWRITE_DISPATCH_ANOTHER)
1019 state.flags = output_event->flags();
1020 if (status == EVENT_REWRITE_DISCARD)
1021 return EVENT_REWRITE_DISCARD;
1022 is_sticky_key_extension_command =
1023 delegate_ &&
1024 delegate_->IsExtensionCommandRegistered(state.key_code, state.flags);
1025 }
1026
1027 // If flags have changed, this may change the interpretation of the key,
1028 // so reapply layout.
1029 if (state.flags != key_event.flags())
1030 SetMeaningForLayout(key_event.type(), &state);
1031
1032 // If sticky key rewrites the event, and it matches an extension command, do
1033 // not further rewrite the event since it won't match the extension command
1034 // thereafter.
1035 if (!is_sticky_key_extension_command && !(key_event.flags() & EF_FINAL)) {
1036 RewriteExtendedKeys(key_event, &state);
1037 RewriteFunctionKeys(key_event, &state);
1038 }
1039 if ((key_event.flags() == state.flags) &&
1040 (key_event.key_code() == state.key_code) &&
1041 (status == EVENT_REWRITE_CONTINUE)) {
1042 return EVENT_REWRITE_CONTINUE;
1043 }
1044 // Sticky keys may have returned a result other than |EVENT_REWRITE_CONTINUE|,
1045 // in which case we need to preserve that return status. Alternatively, we
1046 // might be here because key_event changed, in which case we need to
1047 // return |EVENT_REWRITE_REWRITTEN|.
1048 if (status == EVENT_REWRITE_CONTINUE)
1049 status = EVENT_REWRITE_REWRITTEN;
1050 BuildRewrittenKeyEvent(key_event, state, rewritten_event);
1051 return status;
1052 }
1053
1054 // TODO(yhanada): Clean up this method once StickyKeysController migrates to the
1055 // new API.
RewriteMouseButtonEvent(const MouseEvent & mouse_event,const Continuation continuation)1056 EventDispatchDetails EventRewriterChromeOS::RewriteMouseButtonEvent(
1057 const MouseEvent& mouse_event,
1058 const Continuation continuation) {
1059 int flags = RewriteLocatedEvent(mouse_event);
1060 EventRewriteStatus status = EVENT_REWRITE_CONTINUE;
1061 if (sticky_keys_controller_) {
1062 MouseEvent tmp_event = mouse_event;
1063 tmp_event.set_flags(flags);
1064 std::unique_ptr<Event> output_event;
1065 status = sticky_keys_controller_->RewriteEvent(tmp_event, &output_event);
1066 if (status == EVENT_REWRITE_REWRITTEN ||
1067 status == EVENT_REWRITE_DISPATCH_ANOTHER) {
1068 flags = output_event->flags();
1069 }
1070 }
1071 int changed_button = EF_NONE;
1072 if ((mouse_event.type() == ET_MOUSE_PRESSED) ||
1073 (mouse_event.type() == ET_MOUSE_RELEASED)) {
1074 changed_button = RewriteModifierClick(mouse_event, &flags);
1075 }
1076 if ((mouse_event.flags() == flags) && (status == EVENT_REWRITE_CONTINUE)) {
1077 return SendEvent(continuation, &mouse_event);
1078 }
1079
1080 std::unique_ptr<Event> rewritten_event = Event::Clone(mouse_event);
1081 rewritten_event->set_flags(flags);
1082 if (changed_button != EF_NONE) {
1083 static_cast<MouseEvent*>(rewritten_event.get())
1084 ->set_changed_button_flags(changed_button);
1085 }
1086
1087 EventDispatchDetails details =
1088 SendEventFinally(continuation, rewritten_event.get());
1089 if (status == EVENT_REWRITE_DISPATCH_ANOTHER &&
1090 !details.dispatcher_destroyed) {
1091 // Here, we know that another event is a modifier key release event from
1092 // StickyKeysController.
1093 return SendStickyKeysReleaseEvents(std::move(rewritten_event),
1094 continuation);
1095 }
1096 return details;
1097 }
1098
RewriteMouseWheelEvent(const MouseWheelEvent & wheel_event,const Continuation continuation)1099 EventDispatchDetails EventRewriterChromeOS::RewriteMouseWheelEvent(
1100 const MouseWheelEvent& wheel_event,
1101 const Continuation continuation) {
1102 if (!sticky_keys_controller_)
1103 return SendEvent(continuation, &wheel_event);
1104
1105 const int flags = RewriteLocatedEvent(wheel_event);
1106 MouseWheelEvent tmp_event = wheel_event;
1107 tmp_event.set_flags(flags);
1108 return sticky_keys_controller_->RewriteEvent(tmp_event, continuation);
1109 }
1110
RewriteTouchEvent(const TouchEvent & touch_event,const Continuation continuation)1111 EventDispatchDetails EventRewriterChromeOS::RewriteTouchEvent(
1112 const TouchEvent& touch_event,
1113 const Continuation continuation) {
1114 const int flags = RewriteLocatedEvent(touch_event);
1115 if (touch_event.flags() == flags)
1116 return SendEvent(continuation, &touch_event);
1117 TouchEvent rewritten_touch_event(touch_event);
1118 rewritten_touch_event.set_flags(flags);
1119 return SendEventFinally(continuation, &rewritten_touch_event);
1120 }
1121
RewriteScrollEvent(const ScrollEvent & scroll_event,const Continuation continuation)1122 EventDispatchDetails EventRewriterChromeOS::RewriteScrollEvent(
1123 const ScrollEvent& scroll_event,
1124 const Continuation continuation) {
1125 if (!sticky_keys_controller_)
1126 return SendEvent(continuation, &scroll_event);
1127 return sticky_keys_controller_->RewriteEvent(scroll_event, continuation);
1128 }
1129
RewriteNumPadKeys(const KeyEvent & key_event,MutableKeyState * state)1130 void EventRewriterChromeOS::RewriteNumPadKeys(const KeyEvent& key_event,
1131 MutableKeyState* state) {
1132 DCHECK(key_event.type() == ET_KEY_PRESSED ||
1133 key_event.type() == ET_KEY_RELEASED);
1134 static const struct NumPadRemapping {
1135 KeyboardCode input_key_code;
1136 MutableKeyState result;
1137 } kNumPadRemappings[] = {{VKEY_DELETE,
1138 {EF_NONE, DomCode::NONE,
1139 DomKey::Constant<'.'>::Character, VKEY_DECIMAL}},
1140 {VKEY_INSERT,
1141 {EF_NONE, DomCode::NONE,
1142 DomKey::Constant<'0'>::Character, VKEY_NUMPAD0}},
1143 {VKEY_END,
1144 {EF_NONE, DomCode::NONE,
1145 DomKey::Constant<'1'>::Character, VKEY_NUMPAD1}},
1146 {VKEY_DOWN,
1147 {EF_NONE, DomCode::NONE,
1148 DomKey::Constant<'2'>::Character, VKEY_NUMPAD2}},
1149 {VKEY_NEXT,
1150 {EF_NONE, DomCode::NONE,
1151 DomKey::Constant<'3'>::Character, VKEY_NUMPAD3}},
1152 {VKEY_LEFT,
1153 {EF_NONE, DomCode::NONE,
1154 DomKey::Constant<'4'>::Character, VKEY_NUMPAD4}},
1155 {VKEY_CLEAR,
1156 {EF_NONE, DomCode::NONE,
1157 DomKey::Constant<'5'>::Character, VKEY_NUMPAD5}},
1158 {VKEY_RIGHT,
1159 {EF_NONE, DomCode::NONE,
1160 DomKey::Constant<'6'>::Character, VKEY_NUMPAD6}},
1161 {VKEY_HOME,
1162 {EF_NONE, DomCode::NONE,
1163 DomKey::Constant<'7'>::Character, VKEY_NUMPAD7}},
1164 {VKEY_UP,
1165 {EF_NONE, DomCode::NONE,
1166 DomKey::Constant<'8'>::Character, VKEY_NUMPAD8}},
1167 {VKEY_PRIOR,
1168 {EF_NONE, DomCode::NONE,
1169 DomKey::Constant<'9'>::Character, VKEY_NUMPAD9}}};
1170 for (const auto& map : kNumPadRemappings) {
1171 if (state->key_code == map.input_key_code) {
1172 if (KeycodeConverter::DomCodeToLocation(state->code) ==
1173 DomKeyLocation::NUMPAD) {
1174 ApplyRemapping(map.result, state);
1175 }
1176 return;
1177 }
1178 }
1179 }
1180
RewriteExtendedKeys(const KeyEvent & key_event,MutableKeyState * state)1181 void EventRewriterChromeOS::RewriteExtendedKeys(const KeyEvent& key_event,
1182 MutableKeyState* state) {
1183 DCHECK(key_event.type() == ET_KEY_PRESSED ||
1184 key_event.type() == ET_KEY_RELEASED);
1185 MutableKeyState incoming = *state;
1186
1187 if ((incoming.flags & (EF_COMMAND_DOWN | EF_ALT_DOWN)) ==
1188 (EF_COMMAND_DOWN | EF_ALT_DOWN)) {
1189 // Allow Search to avoid rewriting extended keys.
1190 // For these, we only remove the EF_COMMAND_DOWN flag.
1191 static const KeyboardRemapping::Condition kAvoidRemappings[] = {
1192 {// Alt+Backspace
1193 EF_ALT_DOWN | EF_COMMAND_DOWN, VKEY_BACK},
1194 {// Control+Alt+Up
1195 EF_ALT_DOWN | EF_CONTROL_DOWN | EF_COMMAND_DOWN, VKEY_UP},
1196 {// Control+Alt+Down
1197 EF_ALT_DOWN | EF_CONTROL_DOWN | EF_COMMAND_DOWN, VKEY_DOWN}};
1198 for (const auto& condition : kAvoidRemappings) {
1199 if (MatchKeyboardRemapping(*state, condition)) {
1200 state->flags = incoming.flags & ~EF_COMMAND_DOWN;
1201 return;
1202 }
1203 }
1204 }
1205
1206 if (incoming.flags & EF_COMMAND_DOWN) {
1207 bool strict = ::features::IsNewShortcutMappingEnabled();
1208 bool skip_search_key_remapping =
1209 delegate_ && delegate_->IsSearchKeyAcceleratorReserved();
1210 if (strict) {
1211 // These two keys are used to select to Home/End.
1212 static const KeyboardRemapping kNewSearchRemappings[] = {
1213 {// Search+Shift+Left -> select to home.
1214 {EF_COMMAND_DOWN | EF_SHIFT_DOWN, VKEY_LEFT},
1215 {EF_SHIFT_DOWN, DomCode::HOME, DomKey::HOME, VKEY_HOME}},
1216 {// Search+Shift+Right -> select to end.
1217 {EF_COMMAND_DOWN | EF_SHIFT_DOWN, VKEY_RIGHT},
1218 {EF_SHIFT_DOWN, DomCode::END, DomKey::END, VKEY_END}},
1219 };
1220 if (!skip_search_key_remapping &&
1221 RewriteWithKeyboardRemappings(kNewSearchRemappings,
1222 base::size(kNewSearchRemappings),
1223 incoming, state, /*strict=*/true)) {
1224 return;
1225 }
1226 }
1227 static const KeyboardRemapping kSearchRemappings[] = {
1228 {// Search+BackSpace -> Delete
1229 {EF_COMMAND_DOWN, VKEY_BACK},
1230 {EF_NONE, DomCode::DEL, DomKey::DEL, VKEY_DELETE}},
1231 {// Search+Left -> Home
1232 {EF_COMMAND_DOWN, VKEY_LEFT},
1233 {EF_NONE, DomCode::HOME, DomKey::HOME, VKEY_HOME}},
1234 {// Search+Up -> Prior (aka PageUp)
1235 {EF_COMMAND_DOWN, VKEY_UP},
1236 {EF_NONE, DomCode::PAGE_UP, DomKey::PAGE_UP, VKEY_PRIOR}},
1237 {// Search+Right -> End
1238 {EF_COMMAND_DOWN, VKEY_RIGHT},
1239 {EF_NONE, DomCode::END, DomKey::END, VKEY_END}},
1240 {// Search+Down -> Next (aka PageDown)
1241 {EF_COMMAND_DOWN, VKEY_DOWN},
1242 {EF_NONE, DomCode::PAGE_DOWN, DomKey::PAGE_DOWN, VKEY_NEXT}},
1243 {// Search+Period -> Insert
1244 {EF_COMMAND_DOWN, VKEY_OEM_PERIOD},
1245 {EF_NONE, DomCode::INSERT, DomKey::INSERT, VKEY_INSERT}}};
1246 if (!skip_search_key_remapping &&
1247 RewriteWithKeyboardRemappings(kSearchRemappings,
1248 base::size(kSearchRemappings), incoming,
1249 state, strict)) {
1250 return;
1251 }
1252 }
1253
1254 if (incoming.flags & EF_ALT_DOWN) {
1255 static const KeyboardRemapping kNonSearchRemappings[] = {
1256 {// Alt+BackSpace -> Delete
1257 {EF_ALT_DOWN, VKEY_BACK},
1258 {EF_NONE, DomCode::DEL, DomKey::DEL, VKEY_DELETE}},
1259 {// Control+Alt+Up -> Home
1260 {EF_ALT_DOWN | EF_CONTROL_DOWN, VKEY_UP},
1261 {EF_NONE, DomCode::HOME, DomKey::HOME, VKEY_HOME}},
1262 {// Alt+Up -> Prior (aka PageUp)
1263 {EF_ALT_DOWN, VKEY_UP},
1264 {EF_NONE, DomCode::PAGE_UP, DomKey::PAGE_UP, VKEY_PRIOR}},
1265 {// Control+Alt+Down -> End
1266 {EF_ALT_DOWN | EF_CONTROL_DOWN, VKEY_DOWN},
1267 {EF_NONE, DomCode::END, DomKey::END, VKEY_END}},
1268 {// Alt+Down -> Next (aka PageDown)
1269 {EF_ALT_DOWN, VKEY_DOWN},
1270 {EF_NONE, DomCode::PAGE_DOWN, DomKey::PAGE_DOWN, VKEY_NEXT}}};
1271 if (RewriteWithKeyboardRemappings(kNonSearchRemappings,
1272 base::size(kNonSearchRemappings),
1273 incoming, state)) {
1274 return;
1275 }
1276 }
1277 }
1278
RewriteFunctionKeys(const KeyEvent & key_event,MutableKeyState * state)1279 void EventRewriterChromeOS::RewriteFunctionKeys(const KeyEvent& key_event,
1280 MutableKeyState* state) {
1281 CHECK(key_event.type() == ET_KEY_PRESSED ||
1282 key_event.type() == ET_KEY_RELEASED);
1283
1284 // Some action key codes are mapped to standard VKEY and DomCode values
1285 // during event to KeyEvent translation. However, in Chrome, different VKEY
1286 // combinations trigger those actions. This table maps event VKEYs to the
1287 // right action VKEYs.
1288 // TODO(dtor): Either add proper accelerators for VKEY_ZOOM or move
1289 // from VKEY_MEDIA_LAUNCH_APP2 to VKEY_ZOOM.
1290 static const KeyboardRemapping kActionToActionKeys[] = {
1291 // Zoom toggle is actually through VKEY_MEDIA_LAUNCH_APP2.
1292 {{EF_NONE, VKEY_ZOOM},
1293 {EF_NONE, DomCode::ZOOM_TOGGLE, DomKey::ZOOM_TOGGLE,
1294 VKEY_MEDIA_LAUNCH_APP2}},
1295 };
1296
1297 // Map certain action keys to the right VKey and modifier.
1298 RewriteWithKeyboardRemappings(kActionToActionKeys,
1299 base::size(kActionToActionKeys), *state, state);
1300
1301 // Some key codes have a Dom code but no VKEY value assigned. They're mapped
1302 // to VKEY values here.
1303 if (state->key_code == VKEY_UNKNOWN) {
1304 if (state->code == DomCode::SHOW_ALL_WINDOWS) {
1305 // Show all windows is through VKEY_MEDIA_LAUNCH_APP1.
1306 state->key_code = VKEY_MEDIA_LAUNCH_APP1;
1307 state->key = DomKey::F4;
1308 } else if (state->code == DomCode::DISPLAY_TOGGLE_INT_EXT) {
1309 // Display toggle is through control + VKEY_MEDIA_LAUNCH_APP2.
1310 state->flags |= EF_CONTROL_DOWN;
1311 state->key_code = VKEY_MEDIA_LAUNCH_APP2;
1312 state->key = DomKey::F12;
1313 }
1314 }
1315
1316 const auto iter = device_id_to_info_.find(key_event.source_device_id());
1317 KeyboardTopRowLayout layout = kKbdTopRowLayoutDefault;
1318 if (iter != device_id_to_info_.end()) {
1319 layout = iter->second.top_row_layout;
1320 }
1321
1322 const bool search_is_pressed = (state->flags & EF_COMMAND_DOWN) != 0;
1323 if (layout == kKbdTopRowLayoutCustom) {
1324 if (RewriteTopRowKeysForCustomLayout(key_event.source_device_id(),
1325 key_event, search_is_pressed, state)) {
1326 return;
1327 }
1328 } else if (layout == kKbdTopRowLayoutWilco ||
1329 layout == kKbdTopRowLayoutDrallion) {
1330 if (RewriteTopRowKeysForLayoutWilco(key_event, search_is_pressed, state,
1331 layout)) {
1332 return;
1333 }
1334 } else if ((state->key_code >= VKEY_F1) && (state->key_code <= VKEY_F12)) {
1335 // Search? Top Row Result
1336 // ------- -------- ------
1337 // No Fn Unchanged
1338 // No System Fn -> System
1339 // Yes Fn Fn -> System
1340 // Yes System Search+Fn -> Fn
1341 if (ForceTopRowAsFunctionKeys() == search_is_pressed) {
1342 // Rewrite the F1-F12 keys on a Chromebook keyboard to system keys.
1343 // This is the original Chrome OS layout.
1344 static const KeyboardRemapping kFkeysToSystemKeys1[] = {
1345 {{EF_NONE, VKEY_F1},
1346 {EF_NONE, DomCode::BROWSER_BACK, DomKey::BROWSER_BACK,
1347 VKEY_BROWSER_BACK}},
1348 {{EF_NONE, VKEY_F2},
1349 {EF_NONE, DomCode::BROWSER_FORWARD, DomKey::BROWSER_FORWARD,
1350 VKEY_BROWSER_FORWARD}},
1351 {{EF_NONE, VKEY_F3},
1352 {EF_NONE, DomCode::BROWSER_REFRESH, DomKey::BROWSER_REFRESH,
1353 VKEY_BROWSER_REFRESH}},
1354 {{EF_NONE, VKEY_F4},
1355 {EF_NONE, DomCode::ZOOM_TOGGLE, DomKey::ZOOM_TOGGLE,
1356 VKEY_MEDIA_LAUNCH_APP2}},
1357 {{EF_NONE, VKEY_F5},
1358 {EF_NONE, DomCode::SELECT_TASK, DomKey::LAUNCH_MY_COMPUTER,
1359 VKEY_MEDIA_LAUNCH_APP1}},
1360 {{EF_NONE, VKEY_F6},
1361 {EF_NONE, DomCode::BRIGHTNESS_DOWN, DomKey::BRIGHTNESS_DOWN,
1362 VKEY_BRIGHTNESS_DOWN}},
1363 {{EF_NONE, VKEY_F7},
1364 {EF_NONE, DomCode::BRIGHTNESS_UP, DomKey::BRIGHTNESS_UP,
1365 VKEY_BRIGHTNESS_UP}},
1366 {{EF_NONE, VKEY_F8},
1367 {EF_NONE, DomCode::VOLUME_MUTE, DomKey::AUDIO_VOLUME_MUTE,
1368 VKEY_VOLUME_MUTE}},
1369 {{EF_NONE, VKEY_F9},
1370 {EF_NONE, DomCode::VOLUME_DOWN, DomKey::AUDIO_VOLUME_DOWN,
1371 VKEY_VOLUME_DOWN}},
1372 {{EF_NONE, VKEY_F10},
1373 {EF_NONE, DomCode::VOLUME_UP, DomKey::AUDIO_VOLUME_UP,
1374 VKEY_VOLUME_UP}},
1375 };
1376 // The new layout with forward button removed and play/pause added.
1377 static const KeyboardRemapping kFkeysToSystemKeys2[] = {
1378 {{EF_NONE, VKEY_F1},
1379 {EF_NONE, DomCode::BROWSER_BACK, DomKey::BROWSER_BACK,
1380 VKEY_BROWSER_BACK}},
1381 {{EF_NONE, VKEY_F2},
1382 {EF_NONE, DomCode::BROWSER_REFRESH, DomKey::BROWSER_REFRESH,
1383 VKEY_BROWSER_REFRESH}},
1384 {{EF_NONE, VKEY_F3},
1385 {EF_NONE, DomCode::ZOOM_TOGGLE, DomKey::ZOOM_TOGGLE,
1386 VKEY_MEDIA_LAUNCH_APP2}},
1387 {{EF_NONE, VKEY_F4},
1388 {EF_NONE, DomCode::SELECT_TASK, DomKey::LAUNCH_MY_COMPUTER,
1389 VKEY_MEDIA_LAUNCH_APP1}},
1390 {{EF_NONE, VKEY_F5},
1391 {EF_NONE, DomCode::BRIGHTNESS_DOWN, DomKey::BRIGHTNESS_DOWN,
1392 VKEY_BRIGHTNESS_DOWN}},
1393 {{EF_NONE, VKEY_F6},
1394 {EF_NONE, DomCode::BRIGHTNESS_UP, DomKey::BRIGHTNESS_UP,
1395 VKEY_BRIGHTNESS_UP}},
1396 {{EF_NONE, VKEY_F7},
1397 {EF_NONE, DomCode::MEDIA_PLAY_PAUSE, DomKey::MEDIA_PLAY_PAUSE,
1398 VKEY_MEDIA_PLAY_PAUSE}},
1399 {{EF_NONE, VKEY_F8},
1400 {EF_NONE, DomCode::VOLUME_MUTE, DomKey::AUDIO_VOLUME_MUTE,
1401 VKEY_VOLUME_MUTE}},
1402 {{EF_NONE, VKEY_F9},
1403 {EF_NONE, DomCode::VOLUME_DOWN, DomKey::AUDIO_VOLUME_DOWN,
1404 VKEY_VOLUME_DOWN}},
1405 {{EF_NONE, VKEY_F10},
1406 {EF_NONE, DomCode::VOLUME_UP, DomKey::AUDIO_VOLUME_UP,
1407 VKEY_VOLUME_UP}},
1408 };
1409
1410 const KeyboardRemapping* mapping = nullptr;
1411 size_t mappingSize = 0u;
1412 switch (layout) {
1413 case kKbdTopRowLayout2:
1414 mapping = kFkeysToSystemKeys2;
1415 mappingSize = base::size(kFkeysToSystemKeys2);
1416 break;
1417 case kKbdTopRowLayout1:
1418 default:
1419 mapping = kFkeysToSystemKeys1;
1420 mappingSize = base::size(kFkeysToSystemKeys1);
1421 break;
1422 }
1423
1424 MutableKeyState incoming_without_command = *state;
1425 incoming_without_command.flags &= ~EF_COMMAND_DOWN;
1426 if (RewriteWithKeyboardRemappings(mapping, mappingSize,
1427 incoming_without_command, state)) {
1428 return;
1429 }
1430 } else if (search_is_pressed) {
1431 // Allow Search to avoid rewriting F1-F12.
1432 state->flags &= ~EF_COMMAND_DOWN;
1433 return;
1434 }
1435 }
1436
1437 if (state->flags & EF_COMMAND_DOWN) {
1438 const bool strict = ::features::IsNewShortcutMappingEnabled();
1439 struct SearchToFunctionMap {
1440 DomCode input_dom_code;
1441 MutableKeyState result;
1442 };
1443
1444 // We check the DOM3 |code| here instead of the VKEY, as these keys may
1445 // have different |KeyboardCode|s when modifiers are pressed, such as
1446 // shift.
1447 if (strict) {
1448 // Remap Search + 1/2 to F11/12.
1449 static const SearchToFunctionMap kNumberKeysToFkeys[] = {
1450 {DomCode::DIGIT1, {EF_NONE, DomCode::F11, DomKey::F12, VKEY_F11}},
1451 {DomCode::DIGIT2, {EF_NONE, DomCode::F12, DomKey::F12, VKEY_F12}},
1452 };
1453 for (const auto& map : kNumberKeysToFkeys) {
1454 if (state->code == map.input_dom_code) {
1455 state->flags &= ~EF_COMMAND_DOWN;
1456 ApplyRemapping(map.result, state);
1457 return;
1458 }
1459 }
1460 } else {
1461 // Remap Search + top row to F1~F12.
1462 static const SearchToFunctionMap kNumberKeysToFkeys[] = {
1463 {DomCode::DIGIT1, {EF_NONE, DomCode::F1, DomKey::F1, VKEY_F1}},
1464 {DomCode::DIGIT2, {EF_NONE, DomCode::F2, DomKey::F2, VKEY_F2}},
1465 {DomCode::DIGIT3, {EF_NONE, DomCode::F3, DomKey::F3, VKEY_F3}},
1466 {DomCode::DIGIT4, {EF_NONE, DomCode::F4, DomKey::F4, VKEY_F4}},
1467 {DomCode::DIGIT5, {EF_NONE, DomCode::F5, DomKey::F5, VKEY_F5}},
1468 {DomCode::DIGIT6, {EF_NONE, DomCode::F6, DomKey::F6, VKEY_F6}},
1469 {DomCode::DIGIT7, {EF_NONE, DomCode::F7, DomKey::F7, VKEY_F7}},
1470 {DomCode::DIGIT8, {EF_NONE, DomCode::F8, DomKey::F8, VKEY_F8}},
1471 {DomCode::DIGIT9, {EF_NONE, DomCode::F9, DomKey::F9, VKEY_F9}},
1472 {DomCode::DIGIT0, {EF_NONE, DomCode::F10, DomKey::F10, VKEY_F10}},
1473 {DomCode::MINUS, {EF_NONE, DomCode::F11, DomKey::F11, VKEY_F11}},
1474 {DomCode::EQUAL, {EF_NONE, DomCode::F12, DomKey::F12, VKEY_F12}}};
1475 for (const auto& map : kNumberKeysToFkeys) {
1476 if (state->code == map.input_dom_code) {
1477 state->flags &= ~EF_COMMAND_DOWN;
1478 ApplyRemapping(map.result, state);
1479 return;
1480 }
1481 }
1482 }
1483 }
1484 }
1485
RewriteLocatedEvent(const Event & event)1486 int EventRewriterChromeOS::RewriteLocatedEvent(const Event& event) {
1487 if (!delegate_)
1488 return event.flags();
1489 return GetRemappedModifierMasks(event, event.flags());
1490 }
1491
RewriteModifierClick(const MouseEvent & mouse_event,int * flags)1492 int EventRewriterChromeOS::RewriteModifierClick(const MouseEvent& mouse_event,
1493 int* flags) {
1494 // Note that this behavior is limited to mouse events coming from touchpad
1495 // devices. https://crbug.com/890648.
1496
1497 // Remap either Alt+Button1 or Search+Button1 to Button3 based on
1498 // flag/setting.
1499 int matched_mask;
1500 if (ShouldRemapToRightClick(mouse_event, *flags, &matched_mask)) {
1501 *flags &= ~matched_mask;
1502 *flags |= EF_RIGHT_MOUSE_BUTTON;
1503 if (mouse_event.type() == ET_MOUSE_PRESSED) {
1504 pressed_device_ids_.insert(mouse_event.source_device_id());
1505 if (matched_mask == kSearchLeftButton) {
1506 base::RecordAction(
1507 base::UserMetricsAction("SearchClickMappedToRightClick"));
1508 } else {
1509 DCHECK(matched_mask == kAltLeftButton);
1510 base::RecordAction(
1511 base::UserMetricsAction("AltClickMappedToRightClick"));
1512 }
1513 } else {
1514 pressed_device_ids_.erase(mouse_event.source_device_id());
1515 }
1516 return EF_RIGHT_MOUSE_BUTTON;
1517 }
1518 return EF_NONE;
1519 }
1520
RewriteKeyEventInContext(const KeyEvent & key_event,std::unique_ptr<Event> rewritten_event,EventRewriteStatus status,const Continuation continuation)1521 EventDispatchDetails EventRewriterChromeOS::RewriteKeyEventInContext(
1522 const KeyEvent& key_event,
1523 std::unique_ptr<Event> rewritten_event,
1524 EventRewriteStatus status,
1525 const Continuation continuation) {
1526 if (status == EventRewriteStatus::EVENT_REWRITE_DISCARD)
1527 return DiscardEvent(continuation);
1528
1529 MutableKeyState current_key_state;
1530 auto key_state_comparator =
1531 [¤t_key_state](
1532 const std::pair<MutableKeyState, MutableKeyState>& key_state) {
1533 return (current_key_state.code == key_state.first.code) &&
1534 (current_key_state.key == key_state.first.key) &&
1535 (current_key_state.key_code == key_state.first.key_code);
1536 };
1537
1538 const int mapped_flag = ModifierDomKeyToEventFlag(key_event.GetDomKey());
1539
1540 if (key_event.type() == ET_KEY_PRESSED) {
1541 current_key_state = MutableKeyState(
1542 rewritten_event ? static_cast<const KeyEvent*>(rewritten_event.get())
1543 : &key_event);
1544 MutableKeyState original_key_state(&key_event);
1545 auto iter = std::find_if(pressed_key_states_.begin(),
1546 pressed_key_states_.end(), key_state_comparator);
1547
1548 // When a key is pressed, store |current_key_state| if it is not stored
1549 // before.
1550 if (iter == pressed_key_states_.end()) {
1551 pressed_key_states_.push_back(
1552 std::make_pair(current_key_state, original_key_state));
1553 }
1554
1555 if (status == EventRewriteStatus::EVENT_REWRITE_CONTINUE)
1556 return SendEvent(continuation, &key_event);
1557
1558 EventDispatchDetails details =
1559 SendEventFinally(continuation, rewritten_event.get());
1560 if (status == EventRewriteStatus::EVENT_REWRITE_DISPATCH_ANOTHER &&
1561 !details.dispatcher_destroyed) {
1562 return SendStickyKeysReleaseEvents(std::move(rewritten_event),
1563 continuation);
1564 }
1565 return details;
1566 }
1567
1568 DCHECK_EQ(key_event.type(), ET_KEY_RELEASED);
1569
1570 if (mapped_flag != EF_NONE) {
1571 // The released key is a modifier
1572
1573 DomKey::Base current_key = key_event.GetDomKey();
1574 auto key_state_iter = pressed_key_states_.begin();
1575 int event_flags =
1576 rewritten_event ? rewritten_event->flags() : key_event.flags();
1577 rewritten_event.reset();
1578
1579 // Iterate the keys being pressed. Release the key events which satisfy one
1580 // of the following conditions:
1581 // (1) the key event's original key code (before key event rewriting if
1582 // any) is the same with the key to be released.
1583 // (2) the key event is rewritten and its original flags are influenced by
1584 // the key to be released.
1585 // Example: Press the Launcher button, Press the Up Arrow button, Release
1586 // the Launcher button. When Launcher is released: the key event whose key
1587 // code is Launcher should be released because it satisfies the condition 1;
1588 // the key event whose key code is PageUp should be released because it
1589 // satisfies the condition 2.
1590 EventDispatchDetails details;
1591 while (key_state_iter != pressed_key_states_.end() &&
1592 !details.dispatcher_destroyed) {
1593 const bool is_rewritten =
1594 (key_state_iter->first.key != key_state_iter->second.key);
1595 const bool flag_affected = key_state_iter->second.flags & mapped_flag;
1596 const bool should_release = key_state_iter->second.key == current_key ||
1597 (flag_affected && is_rewritten);
1598
1599 if (should_release) {
1600 // If the key should be released, create a key event for it.
1601 auto dispatched_event = std::make_unique<KeyEvent>(
1602 key_event.type(), key_state_iter->first.key_code,
1603 key_state_iter->first.code, event_flags, key_state_iter->first.key,
1604 key_event.time_stamp());
1605 dispatched_event->set_scan_code(key_event.scan_code());
1606 details = SendEventFinally(continuation, dispatched_event.get());
1607
1608 key_state_iter = pressed_key_states_.erase(key_state_iter);
1609 continue;
1610 }
1611 key_state_iter++;
1612 }
1613 return details;
1614 }
1615
1616 // The released key is not a modifier
1617
1618 current_key_state = MutableKeyState(
1619 rewritten_event ? static_cast<const KeyEvent*>(rewritten_event.get())
1620 : &key_event);
1621 auto iter = std::find_if(pressed_key_states_.begin(),
1622 pressed_key_states_.end(), key_state_comparator);
1623 if (iter != pressed_key_states_.end()) {
1624 pressed_key_states_.erase(iter);
1625
1626 if (status == EventRewriteStatus::EVENT_REWRITE_CONTINUE)
1627 return SendEvent(continuation, &key_event);
1628
1629 EventDispatchDetails details =
1630 SendEventFinally(continuation, rewritten_event.get());
1631 if (status == EventRewriteStatus::EVENT_REWRITE_DISPATCH_ANOTHER &&
1632 !details.dispatcher_destroyed) {
1633 return SendStickyKeysReleaseEvents(std::move(rewritten_event),
1634 continuation);
1635 }
1636 return details;
1637 }
1638
1639 // Event rewriting may create a meaningless key event.
1640 // For example: press the Up Arrow button, press the Launcher button,
1641 // release the Up Arrow. When the Up Arrow button is released, key event
1642 // rewriting happens. However, the rewritten event is not among
1643 // |pressed_key_states_|. So it should be blocked and the original event
1644 // should be propagated.
1645 return SendEvent(continuation, &key_event);
1646 }
1647
StoreCustomTopRowMapping(const InputDevice & keyboard_device)1648 bool EventRewriterChromeOS::StoreCustomTopRowMapping(
1649 const InputDevice& keyboard_device) {
1650 std::string layout;
1651 if (!GetCustomTopRowLayout(keyboard_device, &layout)) {
1652 LOG(WARNING) << "Could not read top row layout map for device "
1653 << keyboard_device.id;
1654 return false;
1655 }
1656
1657 base::flat_map<uint32_t, MutableKeyState> top_row_map;
1658 if (!ParseCustomTopRowLayoutMap(layout, &top_row_map)) {
1659 LOG(WARNING) << "Could not parse top row layout map: " << layout;
1660 return false;
1661 }
1662
1663 top_row_scan_code_map_[keyboard_device.id] = std::move(top_row_map);
1664 return true;
1665 }
1666
1667 // New CrOS keyboards differ from previous Chrome OS keyboards in a few
1668 // ways. Previous keyboards always sent F1-Fxx keys and allowed Chrome to
1669 // decide how to interpret them. New CrOS keyboards now always send action
1670 // keys (eg. Back, Refresh, Overview). So while the default previously was
1671 // to always expect to remap F-Key to action key, for these devices respect
1672 // what the keyboard sends unless the user overrides with either the Search
1673 // key or the "Top Row is always F-Key" setting.
1674 //
1675 // Additionally, these keyboards provide the mapping via sysfs so each
1676 // new keyboard does not need to be explicitly special cased in the future.
1677 //
1678 // Search Force function keys Key code Result
1679 // ------- ------------------- -------- ------
1680 // No No Function Unchanged
1681 // Yes No Function Unchanged
1682 // No Yes Function Unchanged
1683 // Yes Yes Function Unchanged
1684 // No No Action Unchanged
1685 // Yes No Action Action -> Fn
1686 // No Yes Action Action -> Fn
1687 // Yes Yes Action Unchanged
RewriteTopRowKeysForCustomLayout(int device_id,const KeyEvent & key_event,bool search_is_pressed,EventRewriterChromeOS::MutableKeyState * state)1688 bool EventRewriterChromeOS::RewriteTopRowKeysForCustomLayout(
1689 int device_id,
1690 const KeyEvent& key_event,
1691 bool search_is_pressed,
1692 EventRewriterChromeOS::MutableKeyState* state) {
1693 // Incoming function keys are never remapped.
1694 if (IsCustomLayoutFunctionKey(key_event.key_code())) {
1695 return true;
1696 }
1697
1698 const auto& scan_code_map_iter = top_row_scan_code_map_.find(device_id);
1699 if (scan_code_map_iter == top_row_scan_code_map_.end()) {
1700 LOG(WARNING) << "Found no top row key mapping for device " << device_id;
1701 return false;
1702 }
1703
1704 const base::flat_map<uint32_t, MutableKeyState>& scan_code_map =
1705 scan_code_map_iter->second;
1706 const auto& key_iter = scan_code_map.find(key_event.scan_code());
1707
1708 // If the scan code appears in the top row mapping it is an action key.
1709 const bool is_action_key = (key_iter != scan_code_map.end());
1710 if (is_action_key) {
1711 if (search_is_pressed != ForceTopRowAsFunctionKeys()) {
1712 ApplyRemapping(key_iter->second, state);
1713 }
1714
1715 // Clear command/search key if pressed. It's been consumed in the remapping
1716 // or wasn't pressed.
1717 state->flags &= ~EF_COMMAND_DOWN;
1718 return true;
1719 }
1720
1721 return false;
1722 }
1723
1724 // The keyboard layout for Wilco has a slightly different top-row layout, emits
1725 // both Fn and action keys from kernel and has key events with Dom codes and no
1726 // VKey value == VKEY_UNKNOWN. Depending on the state of the search key and
1727 // force-function-key preference, function keys have to be mapped to action keys
1728 // or vice versa.
1729 //
1730 // Search force function keys key code Result
1731 // ------- ------------------- -------- ------
1732 // No No Function Unchanged
1733 // Yes No Function Fn -> Action
1734 // No Yes Function Unchanged
1735 // Yes Yes Function Fn -> Action
1736 // No No Action Unchanged
1737 // Yes No Action Action -> Fn
1738 // No Yes Action Action -> Fn
1739 // Yes Yes Action Unchanged
RewriteTopRowKeysForLayoutWilco(const KeyEvent & key_event,bool search_is_pressed,MutableKeyState * state,KeyboardTopRowLayout layout)1740 bool EventRewriterChromeOS::RewriteTopRowKeysForLayoutWilco(
1741 const KeyEvent& key_event,
1742 bool search_is_pressed,
1743 MutableKeyState* state,
1744 KeyboardTopRowLayout layout) {
1745 // When the kernel issues an function key (Fn modifier help down) and the
1746 // search key is pressed, the function key needs to be mapped to its
1747 // corresponding action key. This table defines those function-to-action
1748 // mappings.
1749 static const KeyboardRemapping kFnkeysToActionKeys[] = {
1750 {{EF_NONE, VKEY_F1},
1751 {EF_NONE, DomCode::BROWSER_BACK, DomKey::BROWSER_BACK,
1752 VKEY_BROWSER_BACK}},
1753 {{EF_NONE, VKEY_F2},
1754 {EF_NONE, DomCode::BROWSER_REFRESH, DomKey::BROWSER_REFRESH,
1755 VKEY_BROWSER_REFRESH}},
1756 // Map F3 to VKEY_MEDIA_LAUNCH_APP2 + EF_NONE == toggle full screen:
1757 {{EF_NONE, VKEY_F3},
1758 {EF_NONE, DomCode::ZOOM_TOGGLE, DomKey::ZOOM_TOGGLE,
1759 VKEY_MEDIA_LAUNCH_APP2}},
1760 // Map F4 to VKEY_MEDIA_LAUNCH_APP1 + EF_NONE == overview:
1761 {{EF_NONE, VKEY_F4},
1762 {EF_NONE, DomCode::F4, DomKey::F4, VKEY_MEDIA_LAUNCH_APP1}},
1763 {{EF_NONE, VKEY_F5},
1764 {EF_NONE, DomCode::BRIGHTNESS_DOWN, DomKey::BRIGHTNESS_DOWN,
1765 VKEY_BRIGHTNESS_DOWN}},
1766 {{EF_NONE, VKEY_F6},
1767 {EF_NONE, DomCode::BRIGHTNESS_UP, DomKey::BRIGHTNESS_UP,
1768 VKEY_BRIGHTNESS_UP}},
1769 {{EF_NONE, VKEY_F7},
1770 {EF_NONE, DomCode::VOLUME_MUTE, DomKey::AUDIO_VOLUME_MUTE,
1771 VKEY_VOLUME_MUTE}},
1772 {{EF_NONE, VKEY_F8},
1773 {EF_NONE, DomCode::VOLUME_DOWN, DomKey::AUDIO_VOLUME_DOWN,
1774 VKEY_VOLUME_DOWN}},
1775 {{EF_NONE, VKEY_F9},
1776 {EF_NONE, DomCode::VOLUME_UP, DomKey::AUDIO_VOLUME_UP, VKEY_VOLUME_UP}},
1777 // Note: F10 and F11 are left as-is since no action is associated with
1778 // these keys.
1779 {{EF_NONE, VKEY_F10}, {EF_NONE, DomCode::F10, DomKey::F10, VKEY_F10}},
1780 {{EF_NONE, VKEY_F11}, {EF_NONE, DomCode::F11, DomKey::F11, VKEY_F11}},
1781 {{EF_NONE, VKEY_F12},
1782 // Map F12 to VKEY_MEDIA_LAUNCH_APP2 + EF_CONTROL_DOWN == toggle mirror
1783 // mode:
1784 {EF_CONTROL_DOWN, DomCode::F12, DomKey::F12, VKEY_MEDIA_LAUNCH_APP2}},
1785 };
1786
1787 // When the kernel issues an action key (default mode) and the search key is
1788 // pressed, the action key needs to be mapped back to its corresponding
1789 // action key. This table defines those action-to-function mappings. Note:
1790 // this table is essentially the dual of kFnToActionLeys above.
1791 static const KeyboardRemapping kActionToFnKeys[] = {
1792 {{EF_NONE, VKEY_BROWSER_BACK},
1793 {EF_NONE, DomCode::F1, DomKey::F1, VKEY_F1}},
1794 {{EF_NONE, VKEY_BROWSER_REFRESH},
1795 {EF_NONE, DomCode::F2, DomKey::F2, VKEY_F2}},
1796 {{EF_NONE, VKEY_MEDIA_LAUNCH_APP1},
1797 {EF_NONE, DomCode::F4, DomKey::F4, VKEY_F4}},
1798 {{EF_NONE, VKEY_BRIGHTNESS_DOWN},
1799 {EF_NONE, DomCode::F5, DomKey::F5, VKEY_F5}},
1800 {{EF_NONE, VKEY_BRIGHTNESS_UP},
1801 {EF_NONE, DomCode::F6, DomKey::F6, VKEY_F6}},
1802 {{EF_NONE, VKEY_VOLUME_MUTE},
1803 {EF_NONE, DomCode::F7, DomKey::F7, VKEY_F7}},
1804 {{EF_NONE, VKEY_VOLUME_DOWN},
1805 {EF_NONE, DomCode::F8, DomKey::F8, VKEY_F8}},
1806 {{EF_NONE, VKEY_VOLUME_UP}, {EF_NONE, DomCode::F9, DomKey::F9, VKEY_F9}},
1807 // Do not change the order of the next two entries. The remapping of
1808 // VKEY_MEDIA_LAUNCH_APP2 with Control held down must appear before
1809 // VKEY_MEDIA_LAUNCH_APP2 by itself to be considered.
1810 {{EF_CONTROL_DOWN, VKEY_MEDIA_LAUNCH_APP2},
1811 {EF_NONE, DomCode::F12, DomKey::F12, VKEY_F12}},
1812 {{EF_NONE, VKEY_MEDIA_LAUNCH_APP2},
1813 {EF_NONE, DomCode::F3, DomKey::F3, VKEY_F3}},
1814 // VKEY_PRIVACY_SCREEN_TOGGLE shares a key with F12 on Drallion.
1815 {{EF_NONE, VKEY_PRIVACY_SCREEN_TOGGLE},
1816 {EF_NONE, DomCode::F12, DomKey::F12, VKEY_F12}},
1817 };
1818
1819 MutableKeyState incoming_without_command = *state;
1820 incoming_without_command.flags &= ~EF_COMMAND_DOWN;
1821
1822 if ((state->key_code >= VKEY_F1) && (state->key_code <= VKEY_F12)) {
1823 // Incoming key code is a Fn key. Check if it needs to be mapped back to its
1824 // corresponding action key.
1825 if (search_is_pressed) {
1826 // On some Drallion devices, F12 shares a key with privacy screen toggle.
1827 // Account for this before rewriting for Wilco 1.0 layout.
1828 if (layout == kKbdTopRowLayoutDrallion && state->key_code == VKEY_F12) {
1829 if (privacy_screen_supported_) {
1830 state->key_code = VKEY_PRIVACY_SCREEN_TOGGLE;
1831 state->code = DomCode::PRIVACY_SCREEN_TOGGLE;
1832 }
1833 // Clear command flag before returning
1834 state->flags = (state->flags & ~EF_COMMAND_DOWN);
1835 return true;
1836 }
1837 return RewriteWithKeyboardRemappings(kFnkeysToActionKeys,
1838 base::size(kFnkeysToActionKeys),
1839 incoming_without_command, state);
1840 }
1841 return true;
1842 } else if (IsKeyCodeInMappings(state->key_code, kActionToFnKeys,
1843 base::size(kActionToFnKeys))) {
1844 // Incoming key code is an action key. Check if it needs to be mapped back
1845 // to its corresponding function key.
1846 if (search_is_pressed != ForceTopRowAsFunctionKeys()) {
1847 // On Drallion, mirror mode toggle is on its own key so don't remap it.
1848 if (layout == kKbdTopRowLayoutDrallion &&
1849 MatchKeyboardRemapping(*state,
1850 {EF_CONTROL_DOWN, VKEY_MEDIA_LAUNCH_APP2})) {
1851 // Clear command flag before returning
1852 state->flags = (state->flags & ~EF_COMMAND_DOWN);
1853 return true;
1854 }
1855 return RewriteWithKeyboardRemappings(kActionToFnKeys,
1856 base::size(kActionToFnKeys),
1857 incoming_without_command, state);
1858 }
1859 // Remap Privacy Screen Toggle to F12 on Drallion devices that do not have
1860 // privacy screens.
1861 if (layout == kKbdTopRowLayoutDrallion && !privacy_screen_supported_ &&
1862 MatchKeyboardRemapping(*state, {EF_NONE, VKEY_PRIVACY_SCREEN_TOGGLE})) {
1863 state->key_code = VKEY_F12;
1864 state->code = DomCode::F12;
1865 state->key = DomKey::F12;
1866 }
1867 // At this point we know search_is_pressed == ForceTopRowAsFunctionKeys().
1868 // If they're both true, they cancel each other. Thus we can clear the
1869 // search-key modifier flag.
1870 state->flags &= ~EF_COMMAND_DOWN;
1871
1872 return true;
1873 }
1874
1875 return false;
1876 }
1877
ForceTopRowAsFunctionKeys() const1878 bool EventRewriterChromeOS::ForceTopRowAsFunctionKeys() const {
1879 return delegate_ && delegate_->TopRowKeysAreFunctionKeys();
1880 }
1881
KeyboardDeviceAdded(int device_id)1882 EventRewriterChromeOS::DeviceType EventRewriterChromeOS::KeyboardDeviceAdded(
1883 int device_id) {
1884 if (!DeviceDataManager::HasInstance())
1885 return kDeviceUnknown;
1886 const std::vector<InputDevice>& keyboard_devices =
1887 DeviceDataManager::GetInstance()->GetKeyboardDevices();
1888 for (const auto& keyboard : keyboard_devices) {
1889 if (keyboard.id != device_id)
1890 continue;
1891
1892 DeviceType type;
1893 KeyboardTopRowLayout layout;
1894 // Don't store a device info when an error occurred while reading from
1895 // udev. This gives a chance to reattempt reading from udev on
1896 // subsequent key events, rather than being stuck in a bad state until
1897 // next reboot. crbug.com/783166.
1898 if (!IdentifyKeyboard(keyboard, &type, &layout)) {
1899 return type;
1900 }
1901
1902 // For custom layouts, parse and save the top row mapping.
1903 if (layout == EventRewriterChromeOS::kKbdTopRowLayoutCustom) {
1904 if (!StoreCustomTopRowMapping(keyboard)) {
1905 return type;
1906 }
1907 }
1908
1909 // Always overwrite the existing device_id since the X server may
1910 // reuse a device id for an unattached device.
1911 device_id_to_info_[keyboard.id] = {type, layout};
1912 return type;
1913 }
1914 return kDeviceUnknown;
1915 }
1916
SendStickyKeysReleaseEvents(std::unique_ptr<Event> rewritten_event,const Continuation continuation)1917 EventDispatchDetails EventRewriterChromeOS::SendStickyKeysReleaseEvents(
1918 std::unique_ptr<Event> rewritten_event,
1919 const Continuation continuation) {
1920 EventDispatchDetails details;
1921 std::unique_ptr<Event> last_sent_event = std::move(rewritten_event);
1922 while (sticky_keys_controller_ && !details.dispatcher_destroyed) {
1923 std::unique_ptr<Event> new_event;
1924 EventRewriteStatus status = sticky_keys_controller_->NextDispatchEvent(
1925 *last_sent_event, &new_event);
1926 details = SendEventFinally(continuation, new_event.get());
1927 last_sent_event = std::move(new_event);
1928 if (status != EventRewriteStatus::EVENT_REWRITE_DISPATCH_ANOTHER)
1929 return details;
1930 }
1931 return details;
1932 }
1933
1934 } // namespace ui
1935