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.
5 #include "chrome/browser/chromeos/events/event_rewriter.h"
9 #include "ash/wm/window_state.h"
10 #include "ash/wm/window_util.h"
11 #include "base/command_line.h"
12 #include "base/logging.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/strings/string_util.h"
15 #include "base/sys_info.h"
16 #include "chrome/browser/chromeos/login/login_display_host_impl.h"
17 #include "chrome/browser/chromeos/login/user_manager.h"
18 #include "chrome/browser/profiles/profile_manager.h"
19 #include "chrome/common/pref_names.h"
20 #include "chromeos/chromeos_switches.h"
21 #include "chromeos/ime/ime_keyboard.h"
22 #include "chromeos/ime/input_method_manager.h"
23 #include "ui/events/event.h"
24 #include "ui/events/event_utils.h"
25 #include "ui/events/keycodes/keyboard_code_conversion.h"
26 #include "ui/events/platform/platform_event_source.h"
27 #include "ui/wm/core/window_util.h"
30 #include <X11/extensions/XInput2.h>
32 // Get rid of macros from Xlib.h that conflicts with other parts of the code.
36 #include "chrome/browser/chromeos/events/xinput_hierarchy_changed_event_listener.h"
37 #include "ui/base/x/x11_util.h"
38 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
45 const int kBadDeviceId = -1;
47 // Table of key properties of remappable keys and/or remapping targets.
48 // This is searched in two distinct ways:
49 // - |remap_to| is an |input_method::ModifierKey|, which is the form
50 // held in user preferences. |GetRemappedKey()| maps this to the
51 // corresponding |key_code| and characterstic event |flag|.
52 // - |flag| is a |ui::EventFlags|. |GetRemappedModifierMasks()| maps this
53 // to the corresponding user preference |pref_name| for that flag's
54 // key, so that it can then be remapped as above.
55 // In addition |kModifierRemappingCtrl| is a direct reference to the
56 // Control key entry in the table, used in handling Chromebook Diamond
57 // and Apple Command keys.
58 const struct ModifierRemapping {
61 ui::KeyboardCode key_code;
62 const char* pref_name;
63 } kModifierRemappings[] = {
64 {input_method::kSearchKey, ui::EF_COMMAND_DOWN, ui::VKEY_LWIN,
65 prefs::kLanguageRemapSearchKeyTo},
66 {input_method::kControlKey, ui::EF_CONTROL_DOWN, ui::VKEY_CONTROL,
67 prefs::kLanguageRemapControlKeyTo},
68 {input_method::kAltKey, ui::EF_ALT_DOWN, ui::VKEY_MENU,
69 prefs::kLanguageRemapAltKeyTo},
70 {input_method::kVoidKey, 0, ui::VKEY_UNKNOWN, NULL},
71 {input_method::kCapsLockKey, ui::EF_CAPS_LOCK_DOWN, ui::VKEY_CAPITAL,
72 prefs::kLanguageRemapCapsLockKeyTo},
73 {input_method::kEscapeKey, 0, ui::VKEY_ESCAPE, NULL},
74 {0, 0, ui::VKEY_F15, prefs::kLanguageRemapDiamondKeyTo},
77 const ModifierRemapping* kModifierRemappingCtrl = &kModifierRemappings[1];
79 // Gets a remapped key for |pref_name| key. For example, to find out which
80 // key Search is currently remapped to, call the function with
81 // prefs::kLanguageRemapSearchKeyTo.
82 const ModifierRemapping* GetRemappedKey(const std::string& pref_name,
83 const PrefService& pref_service) {
84 if (!pref_service.FindPreference(pref_name.c_str()))
85 return NULL; // The |pref_name| hasn't been registered. On login screen?
86 const int value = pref_service.GetInteger(pref_name.c_str());
87 for (size_t i = 0; i < arraysize(kModifierRemappings); ++i) {
88 if (value == kModifierRemappings[i].remap_to)
89 return &kModifierRemappings[i];
94 bool HasDiamondKey() {
95 return CommandLine::ForCurrentProcess()->HasSwitch(
96 chromeos::switches::kHasChromeOSDiamondKey);
99 bool IsISOLevel5ShiftUsedByCurrentInputMethod() {
100 // Since both German Neo2 XKB layout and Caps Lock depend on Mod3Mask,
101 // it's not possible to make both features work. For now, we don't remap
102 // Mod3Mask when Neo2 is in use.
103 // TODO(yusukes): Remove the restriction.
104 input_method::InputMethodManager* manager =
105 input_method::InputMethodManager::Get();
106 return manager->IsISOLevel5ShiftUsedByCurrentInputMethod();
109 EventRewriter::DeviceType GetDeviceType(const std::string& device_name) {
110 std::vector<std::string> tokens;
111 Tokenize(device_name, " .", &tokens);
113 // If the |device_name| contains the two words, "apple" and "keyboard", treat
114 // it as an Apple keyboard.
115 bool found_apple = false;
116 bool found_keyboard = false;
117 for (size_t i = 0; i < tokens.size(); ++i) {
118 if (!found_apple && LowerCaseEqualsASCII(tokens[i], "apple"))
120 if (!found_keyboard && LowerCaseEqualsASCII(tokens[i], "keyboard"))
121 found_keyboard = true;
122 if (found_apple && found_keyboard)
123 return EventRewriter::kDeviceAppleKeyboard;
126 return EventRewriter::kDeviceUnknown;
130 void UpdateX11EventMask(int ui_flags, unsigned int* x_flags) {
135 {ui::EF_CONTROL_DOWN, ControlMask},
136 {ui::EF_SHIFT_DOWN, ShiftMask},
137 {ui::EF_ALT_DOWN, Mod1Mask},
138 {ui::EF_CAPS_LOCK_DOWN, LockMask},
139 {ui::EF_ALTGR_DOWN, Mod5Mask},
140 {ui::EF_MOD3_DOWN, Mod3Mask},
141 {ui::EF_NUMPAD_KEY, Mod2Mask},
142 {ui::EF_LEFT_MOUSE_BUTTON, Button1Mask},
143 {ui::EF_MIDDLE_MOUSE_BUTTON, Button2Mask},
144 {ui::EF_RIGHT_MOUSE_BUTTON, Button3Mask},
146 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(flags); ++i) {
147 if (ui_flags & flags[i].ui)
148 *x_flags |= flags[i].x;
150 *x_flags &= ~flags[i].x;
157 EventRewriter::EventRewriter()
158 : last_device_id_(kBadDeviceId),
159 ime_keyboard_for_testing_(NULL),
160 pref_service_for_testing_(NULL) {
162 ui::PlatformEventSource::GetInstance()->AddPlatformEventObserver(this);
163 if (base::SysInfo::IsRunningOnChromeOS()) {
164 XInputHierarchyChangedEventListener::GetInstance()->AddObserver(this);
169 EventRewriter::~EventRewriter() {
171 ui::PlatformEventSource::GetInstance()->RemovePlatformEventObserver(this);
172 if (base::SysInfo::IsRunningOnChromeOS()) {
173 XInputHierarchyChangedEventListener::GetInstance()->RemoveObserver(this);
178 EventRewriter::DeviceType EventRewriter::DeviceAddedForTesting(
180 const std::string& device_name) {
181 return DeviceAddedInternal(device_id, device_name);
184 void EventRewriter::RewriteLocatedEventForTesting(const ui::Event& event,
186 MutableKeyState state = {*flags, ui::VKEY_UNKNOWN};
187 RewriteLocatedEvent(event, &state);
188 *flags = state.flags;
191 ui::EventRewriteStatus EventRewriter::RewriteEvent(
192 const ui::Event& event,
193 scoped_ptr<ui::Event>* rewritten_event) {
195 // Do not rewrite an event sent by ui_controls::SendKeyPress(). See
197 XEvent* xev = event.native_event();
198 if (xev && xev->xany.send_event)
199 return ui::EVENT_REWRITE_CONTINUE;
201 switch (event.type()) {
202 case ui::ET_KEY_PRESSED:
203 case ui::ET_KEY_RELEASED: {
204 const ui::KeyEvent& key_event = static_cast<const ui::KeyEvent&>(event);
205 MutableKeyState state = {key_event.flags(), key_event.key_code()};
206 RewriteModifierKeys(key_event, &state);
207 RewriteNumPadKeys(key_event, &state);
208 RewriteExtendedKeys(key_event, &state);
209 RewriteFunctionKeys(key_event, &state);
210 if ((key_event.flags() != state.flags) ||
211 (key_event.key_code() != state.key_code)) {
212 ui::KeyEvent* rewritten_key_event = new ui::KeyEvent(key_event);
213 rewritten_event->reset(rewritten_key_event);
214 rewritten_key_event->set_flags(state.flags);
215 rewritten_key_event->set_key_code(state.key_code);
216 rewritten_key_event->set_character(
217 ui::GetCharacterFromKeyCode(state.key_code, state.flags));
218 rewritten_key_event->NormalizeFlags();
220 xev = rewritten_key_event->native_event();
222 XKeyEvent* xkey = &(xev->xkey);
223 UpdateX11EventMask(state.flags, &xkey->state);
224 xkey->keycode = XKeysymToKeycode(
226 ui::XKeysymForWindowsKeyCode(state.key_code,
227 state.flags & ui::EF_SHIFT_DOWN));
230 return ui::EVENT_REWRITE_REWRITTEN;
232 return ui::EVENT_REWRITE_CONTINUE;
234 case ui::ET_MOUSE_PRESSED:
235 case ui::ET_MOUSE_RELEASED:
236 case ui::ET_TOUCH_PRESSED:
237 case ui::ET_TOUCH_RELEASED: {
238 MutableKeyState state = {event.flags(), ui::VKEY_UNKNOWN};
239 RewriteLocatedEvent(event, &state);
240 if (event.flags() != state.flags) {
241 if (event.IsMouseEvent()) {
242 rewritten_event->reset(
243 new ui::MouseEvent(static_cast<const ui::MouseEvent&>(event)));
245 rewritten_event->reset(
246 new ui::TouchEvent(static_cast<const ui::TouchEvent&>(event)));
248 rewritten_event->get()->set_flags(state.flags);
249 return ui::EVENT_REWRITE_REWRITTEN;
251 return ui::EVENT_REWRITE_CONTINUE;
254 return ui::EVENT_REWRITE_CONTINUE;
259 ui::EventRewriteStatus EventRewriter::NextDispatchEvent(
260 const ui::Event& last_event,
261 scoped_ptr<ui::Event>* new_event) {
263 return ui::EVENT_REWRITE_CONTINUE;
267 void EventRewriter::DeviceKeyPressedOrReleased(int device_id) {
268 if (!device_id_to_type_.count(device_id)) {
269 // |device_id| is unknown. This means the device was connected before
270 // booting the OS. Query the name of the device and add it to the map.
271 DeviceAdded(device_id);
273 last_device_id_ = device_id;
277 const PrefService* EventRewriter::GetPrefService() const {
278 if (pref_service_for_testing_)
279 return pref_service_for_testing_;
280 Profile* profile = ProfileManager::GetActiveUserProfile();
281 return profile ? profile->GetPrefs() : NULL;
284 bool EventRewriter::IsAppleKeyboard() const {
285 if (last_device_id_ == kBadDeviceId)
288 // Check which device generated |event|.
289 std::map<int, DeviceType>::const_iterator iter =
290 device_id_to_type_.find(last_device_id_);
291 if (iter == device_id_to_type_.end()) {
292 LOG(ERROR) << "Device ID " << last_device_id_ << " is unknown.";
296 const DeviceType type = iter->second;
297 return type == kDeviceAppleKeyboard;
300 bool EventRewriter::TopRowKeysAreFunctionKeys(const ui::KeyEvent& event) const {
301 const PrefService* prefs = GetPrefService();
302 if (prefs && prefs->FindPreference(prefs::kLanguageSendFunctionKeys) &&
303 prefs->GetBoolean(prefs::kLanguageSendFunctionKeys))
306 ash::wm::WindowState* state = ash::wm::GetActiveWindowState();
307 return state ? state->top_row_keys_are_function_keys() : false;
310 int EventRewriter::GetRemappedModifierMasks(const PrefService& pref_service,
311 const ui::Event& event,
312 int original_flags) const {
313 int unmodified_flags = original_flags;
314 int rewritten_flags = 0;
315 for (size_t i = 0; unmodified_flags && (i < arraysize(kModifierRemappings));
317 const ModifierRemapping* remapped_key = 0;
318 if (!(unmodified_flags & kModifierRemappings[i].flag))
320 switch (kModifierRemappings[i].flag) {
321 case ui::EF_COMMAND_DOWN:
322 // Rewrite Command key presses on an Apple keyboard to Control.
323 if (IsAppleKeyboard()) {
324 DCHECK_EQ(ui::EF_CONTROL_DOWN, kModifierRemappingCtrl->flag);
325 remapped_key = kModifierRemappingCtrl;
328 case ui::EF_CAPS_LOCK_DOWN:
329 // If CapsLock is used by the current input method, don't allow the
330 // CapsLock pref to remap it, or the keyboard behavior will be broken.
331 if (IsISOLevel5ShiftUsedByCurrentInputMethod())
337 if (!remapped_key && kModifierRemappings[i].pref_name) {
339 GetRemappedKey(kModifierRemappings[i].pref_name, pref_service);
342 unmodified_flags &= ~kModifierRemappings[i].flag;
343 rewritten_flags |= remapped_key->flag;
346 return rewritten_flags | unmodified_flags;
349 bool EventRewriter::RewriteWithKeyboardRemappingsByKeyCode(
350 const KeyboardRemapping* remappings,
351 size_t num_remappings,
352 const MutableKeyState& input,
353 MutableKeyState* remapped_state) {
354 for (size_t i = 0; i < num_remappings; ++i) {
355 const KeyboardRemapping& map = remappings[i];
356 if (input.key_code != map.input_key_code)
358 if ((input.flags & map.input_flags) != map.input_flags)
360 remapped_state->key_code = map.output_key_code;
361 remapped_state->flags = (input.flags & ~map.input_flags) | map.output_flags;
367 void EventRewriter::RewriteModifierKeys(const ui::KeyEvent& key_event,
368 MutableKeyState* state) {
369 DCHECK(key_event.type() == ui::ET_KEY_PRESSED ||
370 key_event.type() == ui::ET_KEY_RELEASED);
372 // Do nothing if we have just logged in as guest but have not restarted chrome
373 // process yet (so we are still on the login screen). In this situations we
374 // have no user profile so can not do anything useful.
375 // Note that currently, unlike other accounts, when user logs in as guest, we
376 // restart chrome process. In future this is to be changed.
377 // TODO(glotov): remove the following condition when we do not restart chrome
378 // when user logs in as guest.
379 // TODO(kpschoedel): check whether this is still necessary.
380 if (UserManager::Get()->IsLoggedInAsGuest() &&
381 LoginDisplayHostImpl::default_host())
384 const PrefService* pref_service = GetPrefService();
388 MutableKeyState incoming = *state;
389 state->flags = ui::EF_NONE;
390 int characteristic_flag = ui::EF_NONE;
392 // First, remap the key code.
393 const ModifierRemapping* remapped_key = NULL;
394 switch (incoming.key_code) {
395 // On Chrome OS, F15 (XF86XK_Launch6) with NumLock (Mod2Mask) is sent
396 // when Diamond key is pressed.
398 // When diamond key is not available, the configuration UI for Diamond
399 // key is not shown. Therefore, ignore the kLanguageRemapDiamondKeyTo
403 GetRemappedKey(prefs::kLanguageRemapDiamondKeyTo, *pref_service);
404 // Default behavior is Ctrl key.
406 DCHECK_EQ(ui::VKEY_CONTROL, kModifierRemappingCtrl->key_code);
407 remapped_key = kModifierRemappingCtrl;
408 characteristic_flag = ui::EF_CONTROL_DOWN;
411 // On Chrome OS, XF86XK_Launch7 (F16) with Mod3Mask is sent when Caps Lock
412 // is pressed (with one exception: when
413 // IsISOLevel5ShiftUsedByCurrentInputMethod() is true, the key generates
414 // XK_ISO_Level3_Shift with Mod3Mask, not XF86XK_Launch7).
416 characteristic_flag = ui::EF_CAPS_LOCK_DOWN;
418 GetRemappedKey(prefs::kLanguageRemapCapsLockKeyTo, *pref_service);
422 characteristic_flag = ui::EF_COMMAND_DOWN;
423 // Rewrite Command-L/R key presses on an Apple keyboard to Control.
424 if (IsAppleKeyboard()) {
425 DCHECK_EQ(ui::VKEY_CONTROL, kModifierRemappingCtrl->key_code);
426 remapped_key = kModifierRemappingCtrl;
429 GetRemappedKey(prefs::kLanguageRemapSearchKeyTo, *pref_service);
431 // Default behavior is Super key, hence don't remap the event if the pref
434 case ui::VKEY_CONTROL:
435 characteristic_flag = ui::EF_CONTROL_DOWN;
437 GetRemappedKey(prefs::kLanguageRemapControlKeyTo, *pref_service);
441 characteristic_flag = ui::EF_ALT_DOWN;
443 GetRemappedKey(prefs::kLanguageRemapAltKeyTo, *pref_service);
450 state->key_code = remapped_key->key_code;
451 incoming.flags |= characteristic_flag;
452 characteristic_flag = remapped_key->flag;
455 // Next, remap modifier bits.
457 GetRemappedModifierMasks(*pref_service, key_event, incoming.flags);
458 if (key_event.type() == ui::ET_KEY_PRESSED)
459 state->flags |= characteristic_flag;
461 state->flags &= ~characteristic_flag;
463 // Toggle Caps Lock if the remapped key is ui::VKEY_CAPITAL, but do nothing if
464 // the original key is ui::VKEY_CAPITAL (i.e. a Caps Lock key on an external
465 // keyboard is pressed) since X can handle that case.
466 if (key_event.type() == ui::ET_KEY_PRESSED &&
467 incoming.key_code != ui::VKEY_CAPITAL &&
468 state->key_code == ui::VKEY_CAPITAL) {
469 chromeos::input_method::ImeKeyboard* ime_keyboard =
470 ime_keyboard_for_testing_
471 ? ime_keyboard_for_testing_
472 : chromeos::input_method::InputMethodManager::Get()
474 ime_keyboard->SetCapsLockEnabled(!ime_keyboard->CapsLockIsEnabled());
478 void EventRewriter::RewriteNumPadKeys(const ui::KeyEvent& key_event,
479 MutableKeyState* state) {
480 DCHECK(key_event.type() == ui::ET_KEY_PRESSED ||
481 key_event.type() == ui::ET_KEY_RELEASED);
482 if (!(state->flags & ui::EF_NUMPAD_KEY))
484 MutableKeyState incoming = *state;
486 static const KeyboardRemapping kNumPadRemappings[] = {
487 {ui::VKEY_INSERT, ui::EF_NUMPAD_KEY, ui::VKEY_NUMPAD0, ui::EF_NUMPAD_KEY},
488 {ui::VKEY_DELETE, ui::EF_NUMPAD_KEY, ui::VKEY_DECIMAL, ui::EF_NUMPAD_KEY},
489 {ui::VKEY_END, ui::EF_NUMPAD_KEY, ui::VKEY_NUMPAD1, ui::EF_NUMPAD_KEY},
490 {ui::VKEY_DOWN, ui::EF_NUMPAD_KEY, ui::VKEY_NUMPAD2, ui::EF_NUMPAD_KEY},
491 {ui::VKEY_NEXT, ui::EF_NUMPAD_KEY, ui::VKEY_NUMPAD3, ui::EF_NUMPAD_KEY},
492 {ui::VKEY_LEFT, ui::EF_NUMPAD_KEY, ui::VKEY_NUMPAD4, ui::EF_NUMPAD_KEY},
493 {ui::VKEY_CLEAR, ui::EF_NUMPAD_KEY, ui::VKEY_NUMPAD5, ui::EF_NUMPAD_KEY},
494 {ui::VKEY_RIGHT, ui::EF_NUMPAD_KEY, ui::VKEY_NUMPAD6, ui::EF_NUMPAD_KEY},
495 {ui::VKEY_HOME, ui::EF_NUMPAD_KEY, ui::VKEY_NUMPAD7, ui::EF_NUMPAD_KEY},
496 {ui::VKEY_UP, ui::EF_NUMPAD_KEY, ui::VKEY_NUMPAD8, ui::EF_NUMPAD_KEY},
497 {ui::VKEY_PRIOR, ui::EF_NUMPAD_KEY, ui::VKEY_NUMPAD9, ui::EF_NUMPAD_KEY},
500 RewriteWithKeyboardRemappingsByKeyCode(
501 kNumPadRemappings, arraysize(kNumPadRemappings), incoming, state);
504 void EventRewriter::RewriteExtendedKeys(const ui::KeyEvent& key_event,
505 MutableKeyState* state) {
506 DCHECK(key_event.type() == ui::ET_KEY_PRESSED ||
507 key_event.type() == ui::ET_KEY_RELEASED);
509 MutableKeyState incoming = *state;
510 bool rewritten = false;
512 if ((incoming.flags & (ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN)) ==
513 (ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN)) {
514 // Allow Search to avoid rewriting extended keys.
515 static const KeyboardRemapping kAvoidRemappings[] = {
517 ui::VKEY_BACK, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, ui::VKEY_BACK,
522 ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN,
523 ui::VKEY_UP, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN,
526 ui::VKEY_UP, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, ui::VKEY_UP,
529 { // Control+Alt+Down
531 ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN,
532 ui::VKEY_DOWN, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN,
535 ui::VKEY_DOWN, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, ui::VKEY_DOWN,
539 rewritten = RewriteWithKeyboardRemappingsByKeyCode(
540 kAvoidRemappings, arraysize(kAvoidRemappings), incoming, state);
543 if (!rewritten && (incoming.flags & ui::EF_COMMAND_DOWN)) {
544 static const KeyboardRemapping kSearchRemappings[] = {
545 { // Search+BackSpace -> Delete
546 ui::VKEY_BACK, ui::EF_COMMAND_DOWN, ui::VKEY_DELETE, 0},
547 { // Search+Left -> Home
548 ui::VKEY_LEFT, ui::EF_COMMAND_DOWN, ui::VKEY_HOME, 0},
549 { // Search+Up -> Prior (aka PageUp)
550 ui::VKEY_UP, ui::EF_COMMAND_DOWN, ui::VKEY_PRIOR, 0},
551 { // Search+Right -> End
552 ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN, ui::VKEY_END, 0},
553 { // Search+Down -> Next (aka PageDown)
554 ui::VKEY_DOWN, ui::EF_COMMAND_DOWN, ui::VKEY_NEXT, 0},
555 { // Search+Period -> Insert
556 ui::VKEY_OEM_PERIOD, ui::EF_COMMAND_DOWN, ui::VKEY_INSERT, 0}};
558 rewritten = RewriteWithKeyboardRemappingsByKeyCode(
559 kSearchRemappings, arraysize(kSearchRemappings), incoming, state);
562 if (!rewritten && (incoming.flags & ui::EF_ALT_DOWN)) {
563 static const KeyboardRemapping kNonSearchRemappings[] = {
564 { // Alt+BackSpace -> Delete
565 ui::VKEY_BACK, ui::EF_ALT_DOWN, ui::VKEY_DELETE, 0},
566 { // Control+Alt+Up -> Home
567 ui::VKEY_UP, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, ui::VKEY_HOME, 0},
568 { // Alt+Up -> Prior (aka PageUp)
569 ui::VKEY_UP, ui::EF_ALT_DOWN, ui::VKEY_PRIOR, 0},
570 { // Control+Alt+Down -> End
571 ui::VKEY_DOWN, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, ui::VKEY_END, 0},
572 { // Alt+Down -> Next (aka PageDown)
573 ui::VKEY_DOWN, ui::EF_ALT_DOWN, ui::VKEY_NEXT, 0}};
575 rewritten = RewriteWithKeyboardRemappingsByKeyCode(
576 kNonSearchRemappings, arraysize(kNonSearchRemappings), incoming, state);
580 void EventRewriter::RewriteFunctionKeys(const ui::KeyEvent& key_event,
581 MutableKeyState* state) {
582 CHECK(key_event.type() == ui::ET_KEY_PRESSED ||
583 key_event.type() == ui::ET_KEY_RELEASED);
584 MutableKeyState incoming = *state;
585 bool rewritten = false;
587 if ((incoming.key_code >= ui::VKEY_F1) &&
588 (incoming.key_code <= ui::VKEY_F24)) {
589 // By default the top row (F1-F12) keys are system keys for back, forward,
590 // brightness, volume, etc. However, windows for v2 apps can optionally
591 // request raw function keys for these keys.
592 bool top_row_keys_are_function_keys = TopRowKeysAreFunctionKeys(key_event);
593 bool search_is_pressed = (incoming.flags & ui::EF_COMMAND_DOWN) != 0;
595 // Search? Top Row Result
596 // ------- -------- ------
598 // No System Fn -> System
599 // Yes Fn Fn -> System
600 // Yes System Search+Fn -> Fn
601 if (top_row_keys_are_function_keys == search_is_pressed) {
602 // Rewrite the F1-F12 keys on a Chromebook keyboard to system keys.
603 static const KeyboardRemapping kFkeysToSystemKeys[] = {
604 {ui::VKEY_F1, 0, ui::VKEY_BROWSER_BACK, 0},
605 {ui::VKEY_F2, 0, ui::VKEY_BROWSER_FORWARD, 0},
606 {ui::VKEY_F3, 0, ui::VKEY_BROWSER_REFRESH, 0},
607 {ui::VKEY_F4, 0, ui::VKEY_MEDIA_LAUNCH_APP2, 0},
608 {ui::VKEY_F5, 0, ui::VKEY_MEDIA_LAUNCH_APP1, 0},
609 {ui::VKEY_F6, 0, ui::VKEY_BRIGHTNESS_DOWN, 0},
610 {ui::VKEY_F7, 0, ui::VKEY_BRIGHTNESS_UP, 0},
611 {ui::VKEY_F8, 0, ui::VKEY_VOLUME_MUTE, 0},
612 {ui::VKEY_F9, 0, ui::VKEY_VOLUME_DOWN, 0},
613 {ui::VKEY_F10, 0, ui::VKEY_VOLUME_UP, 0},
615 MutableKeyState incoming_without_command = incoming;
616 incoming_without_command.flags &= ~ui::EF_COMMAND_DOWN;
618 RewriteWithKeyboardRemappingsByKeyCode(kFkeysToSystemKeys,
619 arraysize(kFkeysToSystemKeys),
620 incoming_without_command,
622 } else if (search_is_pressed) {
623 // Allow Search to avoid rewriting F1-F12.
624 state->flags &= ~ui::EF_COMMAND_DOWN;
629 if (!rewritten && (incoming.flags & ui::EF_COMMAND_DOWN)) {
630 // Remap Search+<number> to F<number>.
631 // We check the keycode here instead of the keysym, as these keys have
632 // different keysyms when modifiers are pressed, such as shift.
634 // TODO(danakj): On some i18n keyboards, these choices will be bad and we
635 // should make layout-specific choices here. For eg. on a french keyboard
636 // "-" and "6" are the same key, so F11 will not be accessible.
637 static const KeyboardRemapping kNumberKeysToFkeys[] = {
638 {ui::VKEY_1, ui::EF_COMMAND_DOWN, ui::VKEY_F1, 0},
639 {ui::VKEY_2, ui::EF_COMMAND_DOWN, ui::VKEY_F2, 0},
640 {ui::VKEY_3, ui::EF_COMMAND_DOWN, ui::VKEY_F3, 0},
641 {ui::VKEY_4, ui::EF_COMMAND_DOWN, ui::VKEY_F4, 0},
642 {ui::VKEY_5, ui::EF_COMMAND_DOWN, ui::VKEY_F5, 0},
643 {ui::VKEY_6, ui::EF_COMMAND_DOWN, ui::VKEY_F6, 0},
644 {ui::VKEY_7, ui::EF_COMMAND_DOWN, ui::VKEY_F7, 0},
645 {ui::VKEY_8, ui::EF_COMMAND_DOWN, ui::VKEY_F8, 0},
646 {ui::VKEY_9, ui::EF_COMMAND_DOWN, ui::VKEY_F9, 0},
647 {ui::VKEY_0, ui::EF_COMMAND_DOWN, ui::VKEY_F10, 0},
648 {ui::VKEY_OEM_MINUS, ui::EF_COMMAND_DOWN, ui::VKEY_F11, 0},
649 {ui::VKEY_OEM_PLUS, ui::EF_COMMAND_DOWN, ui::VKEY_F12, 0}};
650 rewritten = RewriteWithKeyboardRemappingsByKeyCode(
651 kNumberKeysToFkeys, arraysize(kNumberKeysToFkeys), incoming, state);
655 void EventRewriter::RewriteLocatedEvent(const ui::Event& event,
656 MutableKeyState* state) {
657 const PrefService* pref_service = GetPrefService();
661 // First, remap modifier masks.
662 state->flags = GetRemappedModifierMasks(*pref_service, event, state->flags);
665 // TODO(kpschoedel): de-X11 with unified device ids from crbug.com/360377
666 XEvent* xevent = event.native_event();
667 if (!xevent || xevent->type != GenericEvent)
669 XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xevent->xcookie.data);
670 if (xievent->evtype != XI_ButtonPress && xievent->evtype != XI_ButtonRelease)
673 // Then, remap Alt+Button1 to Button3.
674 if ((xievent->evtype == XI_ButtonPress ||
675 pressed_device_ids_.count(xievent->sourceid)) &&
676 (xievent->mods.effective & Mod1Mask) && xievent->detail == Button1) {
677 state->flags &= ~(ui::EF_ALT_DOWN | ui::EF_LEFT_MOUSE_BUTTON);
678 state->flags |= ui::EF_RIGHT_MOUSE_BUTTON;
679 xievent->mods.effective &= ~Mod1Mask;
680 xievent->detail = Button3;
681 if (xievent->evtype == XI_ButtonRelease) {
682 // On the release clear the left button from the existing state and the
683 // mods, and set the right button.
684 XISetMask(xievent->buttons.mask, Button3);
685 XIClearMask(xievent->buttons.mask, Button1);
686 xievent->mods.effective &= ~Button1Mask;
687 pressed_device_ids_.erase(xievent->sourceid);
689 pressed_device_ids_.insert(xievent->sourceid);
692 #endif // defined(USE_X11)
695 EventRewriter::DeviceType EventRewriter::DeviceAddedInternal(
697 const std::string& device_name) {
698 const DeviceType type = GetDeviceType(device_name);
699 if (type == kDeviceAppleKeyboard) {
700 VLOG(1) << "Apple keyboard '" << device_name << "' connected: "
701 << "id=" << device_id;
703 // Always overwrite the existing device_id since the X server may reuse a
704 // device id for an unattached device.
705 device_id_to_type_[device_id] = type;
710 void EventRewriter::WillProcessEvent(const ui::PlatformEvent& event) {
711 XEvent* xevent = event;
712 if (xevent->type == GenericEvent) {
713 XIDeviceEvent* xievent = static_cast<XIDeviceEvent*>(xevent->xcookie.data);
714 if (xievent->evtype == XI_KeyPress || xievent->evtype == XI_KeyRelease) {
715 if (xievent->deviceid == xievent->sourceid)
716 DeviceKeyPressedOrReleased(xievent->deviceid);
721 void EventRewriter::DidProcessEvent(const ui::PlatformEvent& event) {
724 void EventRewriter::DeviceHierarchyChanged() {
727 void EventRewriter::DeviceAdded(int device_id) {
728 DCHECK_NE(XIAllDevices, device_id);
729 DCHECK_NE(XIAllMasterDevices, device_id);
730 if (device_id == XIAllDevices || device_id == XIAllMasterDevices) {
731 LOG(ERROR) << "Unexpected device_id passed: " << device_id;
735 int ndevices_return = 0;
736 XIDeviceInfo* device_info =
737 XIQueryDevice(gfx::GetXDisplay(), device_id, &ndevices_return);
739 // Since |device_id| is neither XIAllDevices nor XIAllMasterDevices,
740 // the number of devices found should be either 0 (not found) or 1.
742 LOG(ERROR) << "XIQueryDevice: Device ID " << device_id << " is unknown.";
746 DCHECK_EQ(1, ndevices_return);
747 for (int i = 0; i < ndevices_return; ++i) {
748 DCHECK_EQ(device_id, device_info[i].deviceid); // see the comment above.
749 DCHECK(device_info[i].name);
750 DeviceAddedInternal(device_info[i].deviceid, device_info[i].name);
753 XIFreeDeviceInfo(device_info);
756 void EventRewriter::DeviceRemoved(int device_id) {
757 device_id_to_type_.erase(device_id);
761 } // namespace chromeos