// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/ui/ash/event_rewriter.h" #include #include "ash/shell.h" #include "base/logging.h" #include "base/prefs/pref_service.h" #include "base/strings/string_util.h" #include "chrome/browser/profiles/profile_manager.h" #include "ui/aura/root_window.h" #include "ui/events/event.h" #include "ui/events/event_utils.h" #include "ui/events/keycodes/keyboard_code_conversion.h" #if defined(OS_CHROMEOS) #include #include #include #include // Get rid of a macro from Xlib.h that conflicts with OwnershipService class. #undef Status #include "ash/wm/window_state.h" #include "base/command_line.h" #include "base/sys_info.h" #include "chrome/browser/chromeos/keyboard_driven_event_rewriter.h" #include "chrome/browser/chromeos/login/login_display_host_impl.h" #include "chrome/browser/chromeos/login/user_manager.h" #include "chrome/browser/chromeos/xinput_hierarchy_changed_event_listener.h" #include "chrome/common/pref_names.h" #include "chromeos/chromeos_switches.h" #include "chromeos/ime/input_method_manager.h" #include "chromeos/ime/xkeyboard.h" #include "ui/base/x/x11_util.h" #include "ui/events/keycodes/keyboard_code_conversion_x.h" #include "ui/views/corewm/window_util.h" #endif namespace { const int kBadDeviceId = -1; #if defined(OS_CHROMEOS) const char kNeo2LayoutId[] = "xkb:de:neo:ger"; const char kCaMultixLayoutId[] = "xkb:ca:multix:fra"; // A key code and a flag we should use when a key is remapped to |remap_to|. const struct ModifierRemapping { int remap_to; int flag; unsigned int native_modifier; ui::KeyboardCode keycode; KeySym native_keysyms[4]; // left, right, shift+left, shift+right. } kModifierRemappings[] = { { chromeos::input_method::kSearchKey, 0, Mod4Mask, ui::VKEY_LWIN, { XK_Super_L, XK_Super_L, XK_Super_L, XK_Super_L }}, { chromeos::input_method::kControlKey, ui::EF_CONTROL_DOWN, ControlMask, ui::VKEY_CONTROL, { XK_Control_L, XK_Control_R, XK_Control_L, XK_Control_R }}, { chromeos::input_method::kAltKey, ui::EF_ALT_DOWN, Mod1Mask, ui::VKEY_MENU, { XK_Alt_L, XK_Alt_R, XK_Meta_L, XK_Meta_R }}, { chromeos::input_method::kVoidKey, 0, 0U, ui::VKEY_UNKNOWN, { XK_VoidSymbol, XK_VoidSymbol, XK_VoidSymbol, XK_VoidSymbol }}, { chromeos::input_method::kCapsLockKey, 0, 0U, ui::VKEY_CAPITAL, { XK_Caps_Lock, XK_Caps_Lock, XK_Caps_Lock, XK_Caps_Lock }}, { chromeos::input_method::kEscapeKey, 0, 0U, ui::VKEY_ESCAPE, { XK_Escape, XK_Escape, XK_Escape, XK_Escape }}, }; const ModifierRemapping* kModifierRemappingCtrl = &kModifierRemappings[1]; // A structure for converting |native_modifier| to a pair of |flag| and // |pref_name|. const struct ModifierFlagToPrefName { unsigned int native_modifier; int flag; const char* pref_name; } kModifierFlagToPrefName[] = { // TODO(yusukes): When the device has a Chrome keyboard (i.e. the one without // Caps Lock), we should not check kLanguageRemapCapsLockKeyTo. { Mod3Mask, 0, prefs::kLanguageRemapCapsLockKeyTo }, { Mod4Mask, 0, prefs::kLanguageRemapSearchKeyTo }, { ControlMask, ui::EF_CONTROL_DOWN, prefs::kLanguageRemapControlKeyTo }, { Mod1Mask, ui::EF_ALT_DOWN, prefs::kLanguageRemapAltKeyTo }, { Mod2Mask, 0, prefs::kLanguageRemapDiamondKeyTo }, }; // Gets a remapped key for |pref_name| key. For example, to find out which // key Search is currently remapped to, call the function with // prefs::kLanguageRemapSearchKeyTo. const ModifierRemapping* GetRemappedKey(const std::string& pref_name, const PrefService& pref_service) { if (!pref_service.FindPreference(pref_name.c_str())) return NULL; // The |pref_name| hasn't been registered. On login screen? const int value = pref_service.GetInteger(pref_name.c_str()); for (size_t i = 0; i < arraysize(kModifierRemappings); ++i) { if (value == kModifierRemappings[i].remap_to) return &kModifierRemappings[i]; } return NULL; } bool IsRight(KeySym native_keysym) { switch (native_keysym) { case XK_Alt_R: case XK_Control_R: case XK_Hyper_R: case XK_Meta_R: case XK_Shift_R: case XK_Super_R: return true; default: break; } return false; } bool HasDiamondKey() { return CommandLine::ForCurrentProcess()->HasSwitch( chromeos::switches::kHasChromeOSDiamondKey); } bool IsMod3UsedByCurrentInputMethod() { // Since both German Neo2 XKB layout and Caps Lock depend on Mod3Mask, // it's not possible to make both features work. For now, we don't remap // Mod3Mask when Neo2 is in use. // TODO(yusukes): Remove the restriction. chromeos::input_method::InputMethodManager* manager = chromeos::input_method::InputMethodManager::Get(); return manager->GetCurrentInputMethod().id() == kNeo2LayoutId || manager->GetCurrentInputMethod().id() == kCaMultixLayoutId; } #endif // defined(OS_CHROMEOS) } // namespace EventRewriter::EventRewriter() : last_device_id_(kBadDeviceId), #if defined(OS_CHROMEOS) xkeyboard_for_testing_(NULL), keyboard_driven_event_rewriter_( new chromeos::KeyboardDrivenEventRewriter), #endif pref_service_for_testing_(NULL) { // The ash shell isn't instantiated for our unit tests. if (ash::Shell::HasInstance()) { ash::Shell::GetPrimaryRootWindow()->GetDispatcher()-> AddRootWindowObserver(this); } #if defined(OS_CHROMEOS) if (base::SysInfo::IsRunningOnChromeOS()) { chromeos::XInputHierarchyChangedEventListener::GetInstance() ->AddObserver(this); } RefreshKeycodes(); #endif } EventRewriter::~EventRewriter() { if (ash::Shell::HasInstance()) { ash::Shell::GetPrimaryRootWindow()->GetDispatcher()-> RemoveRootWindowObserver(this); } #if defined(OS_CHROMEOS) if (base::SysInfo::IsRunningOnChromeOS()) { chromeos::XInputHierarchyChangedEventListener::GetInstance() ->RemoveObserver(this); } #endif } EventRewriter::DeviceType EventRewriter::DeviceAddedForTesting( int device_id, const std::string& device_name) { return DeviceAddedInternal(device_id, device_name); } // static EventRewriter::DeviceType EventRewriter::GetDeviceType( const std::string& device_name) { std::vector tokens; Tokenize(device_name, " .", &tokens); // If the |device_name| contains the two words, "apple" and "keyboard", treat // it as an Apple keyboard. bool found_apple = false; bool found_keyboard = false; for (size_t i = 0; i < tokens.size(); ++i) { if (!found_apple && LowerCaseEqualsASCII(tokens[i], "apple")) found_apple = true; if (!found_keyboard && LowerCaseEqualsASCII(tokens[i], "keyboard")) found_keyboard = true; if (found_apple && found_keyboard) return kDeviceAppleKeyboard; } return kDeviceUnknown; } void EventRewriter::RewriteForTesting(ui::KeyEvent* event) { Rewrite(event); } ash::EventRewriterDelegate::Action EventRewriter::RewriteOrFilterKeyEvent( ui::KeyEvent* event) { if (event->HasNativeEvent()) Rewrite(event); return ash::EventRewriterDelegate::ACTION_REWRITE_EVENT; } ash::EventRewriterDelegate::Action EventRewriter::RewriteOrFilterLocatedEvent( ui::LocatedEvent* event) { if (event->HasNativeEvent()) RewriteLocatedEvent(event); return ash::EventRewriterDelegate::ACTION_REWRITE_EVENT; } void EventRewriter::OnKeyboardMappingChanged(const aura::RootWindow* root) { #if defined(OS_CHROMEOS) RefreshKeycodes(); #endif } #if defined(OS_CHROMEOS) void EventRewriter::DeviceAdded(int device_id) { DCHECK_NE(XIAllDevices, device_id); DCHECK_NE(XIAllMasterDevices, device_id); if (device_id == XIAllDevices || device_id == XIAllMasterDevices) { LOG(ERROR) << "Unexpected device_id passed: " << device_id; return; } int ndevices_return = 0; XIDeviceInfo* device_info = XIQueryDevice(gfx::GetXDisplay(), device_id, &ndevices_return); // Since |device_id| is neither XIAllDevices nor XIAllMasterDevices, // the number of devices found should be either 0 (not found) or 1. if (!device_info) { LOG(ERROR) << "XIQueryDevice: Device ID " << device_id << " is unknown."; return; } DCHECK_EQ(1, ndevices_return); for (int i = 0; i < ndevices_return; ++i) { DCHECK_EQ(device_id, device_info[i].deviceid); // see the comment above. DCHECK(device_info[i].name); DeviceAddedInternal(device_info[i].deviceid, device_info[i].name); } XIFreeDeviceInfo(device_info); } void EventRewriter::DeviceRemoved(int device_id) { device_id_to_type_.erase(device_id); } void EventRewriter::DeviceKeyPressedOrReleased(int device_id) { std::map::const_iterator iter = device_id_to_type_.find(device_id); if (iter == device_id_to_type_.end()) { // |device_id| is unknown. This means the device was connected before // booting the OS. Query the name of the device and add it to the map. DeviceAdded(device_id); } last_device_id_ = device_id; } void EventRewriter::RefreshKeycodes() { keysym_to_keycode_map_.clear(); } KeyCode EventRewriter::NativeKeySymToNativeKeycode(KeySym keysym) { if (keysym_to_keycode_map_.count(keysym)) return keysym_to_keycode_map_[keysym]; XDisplay* display = gfx::GetXDisplay(); KeyCode keycode = XKeysymToKeycode(display, keysym); keysym_to_keycode_map_[keysym] = keycode; return keycode; } bool EventRewriter::TopRowKeysAreFunctionKeys(ui::KeyEvent* event) const { const PrefService* prefs = GetPrefService(); if (prefs && prefs->FindPreference(prefs::kLanguageSendFunctionKeys) && prefs->GetBoolean(prefs::kLanguageSendFunctionKeys)) return true; aura::Window* target = static_cast(event->target()); if (!target) return false; aura::Window* top_level = views::corewm::GetToplevelWindow(target); return top_level && ash::wm::GetWindowState(top_level)->top_row_keys_are_function_keys(); } bool EventRewriter::RewriteWithKeyboardRemappingsByKeySym( const KeyboardRemapping* remappings, size_t num_remappings, KeySym keysym, unsigned int native_mods, unsigned int mods, KeySym* remapped_native_keysym, unsigned int* remapped_native_mods, ui::KeyboardCode* remapped_keycode, unsigned int* remapped_mods) { for (size_t i = 0; i < num_remappings; ++i) { const KeyboardRemapping& map = remappings[i]; if (keysym != map.input_keysym) continue; unsigned int matched_mods = native_mods & map.input_native_mods; if (matched_mods != map.input_native_mods) continue; *remapped_native_keysym = map.output_keysym; *remapped_keycode = map.output_keycode; *remapped_native_mods = (native_mods & ~map.input_native_mods) | map.output_native_mods; *remapped_mods = (mods & ~map.input_mods) | map.output_mods; return true; } return false; } bool EventRewriter::RewriteWithKeyboardRemappingsByKeyCode( const KeyboardRemapping* remappings, size_t num_remappings, KeyCode keycode, unsigned int native_mods, unsigned int mods, KeySym* remapped_native_keysym, unsigned int* remapped_native_mods, ui::KeyboardCode* remapped_keycode, unsigned int* remapped_mods) { for (size_t i = 0; i < num_remappings; ++i) { const KeyboardRemapping& map = remappings[i]; KeyCode input_keycode = NativeKeySymToNativeKeycode(map.input_keysym); if (keycode != input_keycode) continue; unsigned int matched_mods = native_mods & map.input_native_mods; if (matched_mods != map.input_native_mods) continue; *remapped_native_keysym = map.output_keysym; *remapped_keycode = map.output_keycode; *remapped_native_mods = (native_mods & ~map.input_native_mods) | map.output_native_mods; *remapped_mods = (mods & ~map.input_mods) | map.output_mods; return true; } return false; } #endif // defined(OS_CHROMEOS) const PrefService* EventRewriter::GetPrefService() const { if (pref_service_for_testing_) return pref_service_for_testing_; Profile* profile = ProfileManager::GetDefaultProfile(); return profile ? profile->GetPrefs() : NULL; } void EventRewriter::Rewrite(ui::KeyEvent* event) { #if defined(OS_CHROMEOS) // Do not rewrite an event sent by ui_controls::SendKeyPress(). See // crbug.com/136465. if (event->native_event()->xkey.send_event) return; // Keyboard driven rewriting happen first. Skip further processing if event is // changed. if (keyboard_driven_event_rewriter_->RewriteIfKeyboardDrivenOnLoginScreen( event)) { return; } #endif RewriteModifiers(event); RewriteNumPadKeys(event); RewriteExtendedKeys(event); RewriteFunctionKeys(event); } bool EventRewriter::IsAppleKeyboard() const { if (last_device_id_ == kBadDeviceId) return false; // Check which device generated |event|. std::map::const_iterator iter = device_id_to_type_.find(last_device_id_); if (iter == device_id_to_type_.end()) { LOG(ERROR) << "Device ID " << last_device_id_ << " is unknown."; return false; } const DeviceType type = iter->second; return type == kDeviceAppleKeyboard; } void EventRewriter::GetRemappedModifierMasks( int original_flags, unsigned int original_native_modifiers, int* remapped_flags, unsigned int* remapped_native_modifiers) const { #if defined(OS_CHROMEOS) // TODO(glotov): remove the following condition when we do not restart chrome // when user logs in as guest. See Rewrite() for details. if (chromeos::UserManager::Get()->IsLoggedInAsGuest() && chromeos::LoginDisplayHostImpl::default_host()) { return; } const PrefService* pref_service = GetPrefService(); if (!pref_service) return; // When a diamond key is not available, a Mod2Mask should not treated as a // configurable modifier because Mod2Mask may be worked as NumLock mask. // (cf. http://crbug.com/173956) const bool skip_mod2 = !HasDiamondKey(); // If Mod3 is used by the current input method, don't allow the CapsLock // pref to remap it, or the keyboard behavior will be broken. const bool skip_mod3 = IsMod3UsedByCurrentInputMethod(); for (size_t i = 0; i < arraysize(kModifierFlagToPrefName); ++i) { if ((skip_mod2 && kModifierFlagToPrefName[i].native_modifier == Mod2Mask) || (skip_mod3 && kModifierFlagToPrefName[i].native_modifier == Mod3Mask)) { continue; } if (original_native_modifiers & kModifierFlagToPrefName[i].native_modifier) { const ModifierRemapping* remapped_key = GetRemappedKey(kModifierFlagToPrefName[i].pref_name, *pref_service); // Rewrite Command-L/R key presses on an Apple keyboard to Control-L/R. if (IsAppleKeyboard() && (kModifierFlagToPrefName[i].native_modifier == Mod4Mask)) { remapped_key = kModifierRemappingCtrl; } if (remapped_key) { *remapped_flags |= remapped_key->flag; *remapped_native_modifiers |= remapped_key->native_modifier; } else { *remapped_flags |= kModifierFlagToPrefName[i].flag; *remapped_native_modifiers |= kModifierFlagToPrefName[i].native_modifier; } } } *remapped_flags = (original_flags & ~(ui::EF_CONTROL_DOWN | ui::EF_ALT_DOWN)) | *remapped_flags; unsigned int native_mask = Mod4Mask | ControlMask | Mod1Mask; if (!skip_mod2) native_mask |= Mod2Mask; if (!skip_mod3) native_mask |= Mod3Mask; *remapped_native_modifiers = (original_native_modifiers & ~native_mask) | *remapped_native_modifiers; #endif } bool EventRewriter::RewriteModifiers(ui::KeyEvent* event) { #if defined(OS_CHROMEOS) // Do nothing if we have just logged in as guest but have not restarted chrome // process yet (so we are still on the login screen). In this situations we // have no user profile so can not do anything useful. // Note that currently, unlike other accounts, when user logs in as guest, we // restart chrome process. In future this is to be changed. // TODO(glotov): remove the following condition when we do not restart chrome // when user logs in as guest. if (chromeos::UserManager::Get()->IsLoggedInAsGuest() && chromeos::LoginDisplayHostImpl::default_host()) return false; const PrefService* pref_service = GetPrefService(); if (!pref_service) return false; DCHECK_EQ(chromeos::input_method::kControlKey, kModifierRemappingCtrl->remap_to); XEvent* xev = event->native_event(); XKeyEvent* xkey = &(xev->xkey); KeySym keysym = XLookupKeysym(xkey, 0); ui::KeyboardCode remapped_keycode = event->key_code(); KeyCode remapped_native_keycode = xkey->keycode; // First, remap |keysym|. const ModifierRemapping* remapped_key = NULL; switch (keysym) { // On Chrome OS, XF86XK_Launch6 (F15) with Mod2Mask is sent when Diamond // key is pressed. case XF86XK_Launch6: // When diamond key is not available, the configuration UI for Diamond // key is not shown. Therefore, ignore the kLanguageRemapDiamondKeyTo // syncable pref. if (HasDiamondKey()) remapped_key = GetRemappedKey(prefs::kLanguageRemapDiamondKeyTo, *pref_service); // Default behavior is Ctrl key. if (!remapped_key) remapped_key = kModifierRemappingCtrl; break; // On Chrome OS, XF86XK_Launch7 (F16) with Mod3Mask is sent when Caps Lock // is pressed (with one exception: when IsMod3UsedByCurrentInputMethod() is // true, the key generates XK_ISO_Level3_Shift with Mod3Mask, not // XF86XK_Launch7). case XF86XK_Launch7: remapped_key = GetRemappedKey(prefs::kLanguageRemapCapsLockKeyTo, *pref_service); break; case XK_Super_L: case XK_Super_R: // Rewrite Command-L/R key presses on an Apple keyboard to Control-L/R. if (IsAppleKeyboard()) remapped_key = kModifierRemappingCtrl; else remapped_key = GetRemappedKey(prefs::kLanguageRemapSearchKeyTo, *pref_service); // Default behavior is Super key, hence don't remap the event if the pref // is unavailable. break; case XK_Control_L: case XK_Control_R: remapped_key = GetRemappedKey(prefs::kLanguageRemapControlKeyTo, *pref_service); break; case XK_Alt_L: case XK_Alt_R: case XK_Meta_L: case XK_Meta_R: remapped_key = GetRemappedKey(prefs::kLanguageRemapAltKeyTo, *pref_service); break; default: break; } if (remapped_key) { remapped_keycode = remapped_key->keycode; const size_t level = (event->IsShiftDown() ? (1 << 1) : 0) + (IsRight(keysym) ? (1 << 0) : 0); const KeySym native_keysym = remapped_key->native_keysyms[level]; remapped_native_keycode = NativeKeySymToNativeKeycode(native_keysym); } // Next, remap modifier bits. int remapped_flags = 0; unsigned int remapped_native_modifiers = 0U; GetRemappedModifierMasks(event->flags(), xkey->state, &remapped_flags, &remapped_native_modifiers); // Toggle Caps Lock if the remapped key is ui::VKEY_CAPITAL, but do nothing if // the original key is ui::VKEY_CAPITAL (i.e. a Caps Lock key on an external // keyboard is pressed) since X can handle that case. if ((event->type() == ui::ET_KEY_PRESSED) && (event->key_code() != ui::VKEY_CAPITAL) && (remapped_keycode == ui::VKEY_CAPITAL)) { chromeos::input_method::XKeyboard* xkeyboard = xkeyboard_for_testing_ ? xkeyboard_for_testing_ : chromeos::input_method::InputMethodManager::Get()->GetXKeyboard(); xkeyboard->SetCapsLockEnabled(!xkeyboard->CapsLockIsEnabled()); } OverwriteEvent(event, remapped_native_keycode, remapped_native_modifiers, remapped_keycode, remapped_flags); return true; #else // TODO(yusukes): Support Ash on other platforms if needed. return false; #endif } bool EventRewriter::RewriteNumPadKeys(ui::KeyEvent* event) { bool rewritten = false; #if defined(OS_CHROMEOS) XEvent* xev = event->native_event(); XKeyEvent* xkey = &(xev->xkey); const KeySym keysym = XLookupKeysym(xkey, 0); switch (keysym) { case XK_KP_Insert: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_0), xkey->state | Mod2Mask, ui::VKEY_NUMPAD0, event->flags()); rewritten = true; break; case XK_KP_Delete: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_Decimal), xkey->state | Mod2Mask, ui::VKEY_DECIMAL, event->flags()); rewritten = true; break; case XK_KP_End: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_1), xkey->state | Mod2Mask, ui::VKEY_NUMPAD1, event->flags()); rewritten = true; break; case XK_KP_Down: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_2), xkey->state | Mod2Mask, ui::VKEY_NUMPAD2, event->flags()); rewritten = true; break; case XK_KP_Next: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_3), xkey->state | Mod2Mask, ui::VKEY_NUMPAD3, event->flags()); rewritten = true; break; case XK_KP_Left: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_4), xkey->state | Mod2Mask, ui::VKEY_NUMPAD4, event->flags()); rewritten = true; break; case XK_KP_Begin: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_5), xkey->state | Mod2Mask, ui::VKEY_NUMPAD5, event->flags()); rewritten = true; break; case XK_KP_Right: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_6), xkey->state | Mod2Mask, ui::VKEY_NUMPAD6, event->flags()); rewritten = true; break; case XK_KP_Home: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_7), xkey->state | Mod2Mask, ui::VKEY_NUMPAD7, event->flags()); rewritten = true; break; case XK_KP_Up: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_8), xkey->state | Mod2Mask, ui::VKEY_NUMPAD8, event->flags()); rewritten = true; break; case XK_KP_Prior: OverwriteEvent(event, NativeKeySymToNativeKeycode(XK_KP_9), xkey->state | Mod2Mask, ui::VKEY_NUMPAD9, event->flags()); rewritten = true; break; case XK_KP_Divide: case XK_KP_Multiply: case XK_KP_Subtract: case XK_KP_Add: case XK_KP_Enter: // Add Mod2Mask for consistency. OverwriteEvent(event, xkey->keycode, xkey->state | Mod2Mask, event->key_code(), event->flags()); rewritten = true; break; default: break; } #else // TODO(yusukes): Support Ash on other platforms if needed. #endif return rewritten; } bool EventRewriter::RewriteExtendedKeys(ui::KeyEvent* event) { #if defined(OS_CHROMEOS) XEvent* xev = event->native_event(); XKeyEvent* xkey = &(xev->xkey); const KeySym keysym = XLookupKeysym(xkey, 0); KeySym remapped_native_keysym = 0; unsigned int remapped_native_mods = 0; ui::KeyboardCode remapped_keycode = ui::VKEY_UNKNOWN; unsigned int remapped_mods = 0; if (xkey->state & Mod4Mask) { // Allow Search to avoid rewriting extended keys. static const KeyboardRemapping kAvoidRemappings[] = { { // Alt+Backspace XK_BackSpace, ui::EF_ALT_DOWN, Mod1Mask | Mod4Mask, XK_BackSpace, ui::VKEY_BACK, ui::EF_ALT_DOWN, Mod1Mask, }, { // Control+Alt+Up XK_Up, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, Mod1Mask | ControlMask | Mod4Mask, XK_Up, ui::VKEY_UP, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, Mod1Mask | ControlMask, }, { // Alt+Up XK_Up, ui::EF_ALT_DOWN, Mod1Mask | Mod4Mask, XK_Up, ui::VKEY_UP, ui::EF_ALT_DOWN, Mod1Mask, }, { // Control+Alt+Down XK_Down, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, Mod1Mask | ControlMask | Mod4Mask, XK_Down, ui::VKEY_DOWN, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, Mod1Mask | ControlMask, }, { // Alt+Down XK_Down, ui::EF_ALT_DOWN, Mod1Mask | Mod4Mask, XK_Down, ui::VKEY_DOWN, ui::EF_ALT_DOWN, Mod1Mask, } }; RewriteWithKeyboardRemappingsByKeySym(kAvoidRemappings, arraysize(kAvoidRemappings), keysym, xkey->state, event->flags(), &remapped_native_keysym, &remapped_native_mods, &remapped_keycode, &remapped_mods); } if (remapped_keycode == ui::VKEY_UNKNOWN) { static const KeyboardRemapping kSearchRemappings[] = { { // Search+BackSpace -> Delete XK_BackSpace, 0, Mod4Mask, XK_Delete, ui::VKEY_DELETE, 0, 0 }, { // Search+Left -> Home XK_Left, 0, Mod4Mask, XK_Home, ui::VKEY_HOME, 0, 0 }, { // Search+Up -> Prior (aka PageUp) XK_Up, 0, Mod4Mask, XK_Prior, ui::VKEY_PRIOR, 0, 0 }, { // Search+Right -> End XK_Right, 0, Mod4Mask, XK_End, ui::VKEY_END, 0, 0 }, { // Search+Down -> Next (aka PageDown) XK_Down, 0, Mod4Mask, XK_Next, ui::VKEY_NEXT, 0, 0 }, { // Search+Period -> Insert XK_period, 0, Mod4Mask, XK_Insert, ui::VKEY_INSERT, 0, 0 } }; RewriteWithKeyboardRemappingsByKeySym(kSearchRemappings, arraysize(kSearchRemappings), keysym, xkey->state, event->flags(), &remapped_native_keysym, &remapped_native_mods, &remapped_keycode, &remapped_mods); } if (remapped_keycode == ui::VKEY_UNKNOWN) { static const KeyboardRemapping kNonSearchRemappings[] = { { // Alt+BackSpace -> Delete XK_BackSpace, ui::EF_ALT_DOWN, Mod1Mask, XK_Delete, ui::VKEY_DELETE, 0, 0 }, { // Control+Alt+Up -> Home XK_Up, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, Mod1Mask | ControlMask, XK_Home, ui::VKEY_HOME, 0, 0 }, { // Alt+Up -> Prior (aka PageUp) XK_Up, ui::EF_ALT_DOWN, Mod1Mask, XK_Prior, ui::VKEY_PRIOR, 0, 0 }, { // Control+Alt+Down -> End XK_Down, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, Mod1Mask | ControlMask, XK_End, ui::VKEY_END, 0, 0 }, { // Alt+Down -> Next (aka PageDown) XK_Down, ui::EF_ALT_DOWN, Mod1Mask, XK_Next, ui::VKEY_NEXT, 0, 0 } }; RewriteWithKeyboardRemappingsByKeySym(kNonSearchRemappings, arraysize(kNonSearchRemappings), keysym, xkey->state, event->flags(), &remapped_native_keysym, &remapped_native_mods, &remapped_keycode, &remapped_mods); } if (remapped_keycode == ui::VKEY_UNKNOWN) return false; OverwriteEvent(event, NativeKeySymToNativeKeycode(remapped_native_keysym), remapped_native_mods, remapped_keycode, remapped_mods); return true; #else // TODO(yusukes): Support Ash on other platforms if needed. return false; #endif } bool EventRewriter::RewriteFunctionKeys(ui::KeyEvent* event) { #if defined(OS_CHROMEOS) XEvent* xev = event->native_event(); XKeyEvent* xkey = &(xev->xkey); const KeySym keysym = XLookupKeysym(xkey, 0); KeySym remapped_native_keysym = 0; unsigned int remapped_native_mods = 0; ui::KeyboardCode remapped_keycode = ui::VKEY_UNKNOWN; unsigned int remapped_mods = 0; // By default the top row (F1-F12) keys are special keys for back, forward, // brightness, volume, etc. However, windows for v2 apps can optionally // request raw function keys for these keys. bool top_row_keys_are_special_keys = !TopRowKeysAreFunctionKeys(event); if ((xkey->state & Mod4Mask) && top_row_keys_are_special_keys) { // Allow Search to avoid rewriting F1-F12. static const KeyboardRemapping kFkeysToFkeys[] = { { XK_F1, 0, Mod4Mask, XK_F1, ui::VKEY_F1, }, { XK_F2, 0, Mod4Mask, XK_F2, ui::VKEY_F2, }, { XK_F3, 0, Mod4Mask, XK_F3, ui::VKEY_F3, }, { XK_F4, 0, Mod4Mask, XK_F4, ui::VKEY_F4, }, { XK_F5, 0, Mod4Mask, XK_F5, ui::VKEY_F5, }, { XK_F6, 0, Mod4Mask, XK_F6, ui::VKEY_F6, }, { XK_F7, 0, Mod4Mask, XK_F7, ui::VKEY_F7, }, { XK_F8, 0, Mod4Mask, XK_F8, ui::VKEY_F8, }, { XK_F9, 0, Mod4Mask, XK_F9, ui::VKEY_F9, }, { XK_F10, 0, Mod4Mask, XK_F10, ui::VKEY_F10, }, { XK_F11, 0, Mod4Mask, XK_F11, ui::VKEY_F11, }, { XK_F12, 0, Mod4Mask, XK_F12, ui::VKEY_F12, }, }; RewriteWithKeyboardRemappingsByKeySym(kFkeysToFkeys, arraysize(kFkeysToFkeys), keysym, xkey->state, event->flags(), &remapped_native_keysym, &remapped_native_mods, &remapped_keycode, &remapped_mods); } if (remapped_keycode == ui::VKEY_UNKNOWN) { static const KeyboardRemapping kFkeysToSpecialKeys[] = { { XK_F1, 0, 0, XF86XK_Back, ui::VKEY_BROWSER_BACK, 0, 0 }, { XK_F2, 0, 0, XF86XK_Forward, ui::VKEY_BROWSER_FORWARD, 0, 0 }, { XK_F3, 0, 0, XF86XK_Reload, ui::VKEY_BROWSER_REFRESH, 0, 0 }, { XK_F4, 0, 0, XF86XK_LaunchB, ui::VKEY_MEDIA_LAUNCH_APP2, 0, 0 }, { XK_F5, 0, 0, XF86XK_LaunchA, ui::VKEY_MEDIA_LAUNCH_APP1, 0, 0 }, { XK_F6, 0, 0, XF86XK_MonBrightnessDown, ui::VKEY_BRIGHTNESS_DOWN, 0, 0 }, { XK_F7, 0, 0, XF86XK_MonBrightnessUp, ui::VKEY_BRIGHTNESS_UP, 0, 0 }, { XK_F8, 0, 0, XF86XK_AudioMute, ui::VKEY_VOLUME_MUTE, 0, 0 }, { XK_F9, 0, 0, XF86XK_AudioLowerVolume, ui::VKEY_VOLUME_DOWN, 0, 0 }, { XK_F10, 0, 0, XF86XK_AudioRaiseVolume, ui::VKEY_VOLUME_UP, 0, 0 }, }; if (top_row_keys_are_special_keys) { // Rewrite the F1-F12 keys on a Chromebook keyboard to special keys. RewriteWithKeyboardRemappingsByKeySym(kFkeysToSpecialKeys, arraysize(kFkeysToSpecialKeys), keysym, xkey->state, event->flags(), &remapped_native_keysym, &remapped_native_mods, &remapped_keycode, &remapped_mods); } else if (xkey->state & Mod4Mask) { // Use Search + F1-F12 for the special keys. RewriteWithKeyboardRemappingsByKeySym(kFkeysToSpecialKeys, arraysize(kFkeysToSpecialKeys), keysym, xkey->state & !Mod4Mask, event->flags(), &remapped_native_keysym, &remapped_native_mods, &remapped_keycode, &remapped_mods); } } if (remapped_keycode == ui::VKEY_UNKNOWN && xkey->state & Mod4Mask) { // Remap Search+ to F. // We check the keycode here instead of the keysym, as these keys have // different keysyms when modifiers are pressed, such as shift. // TODO(danakj): On some i18n keyboards, these choices will be bad and we // should make layout-specific choices here. For eg. on a french keyboard // "-" and "6" are the same key, so F11 will not be accessible. static const KeyboardRemapping kNumberKeysToFkeys[] = { { XK_1, 0, Mod4Mask, XK_F1, ui::VKEY_F1, 0, 0 }, { XK_2, 0, Mod4Mask, XK_F2, ui::VKEY_F2, 0, 0 }, { XK_3, 0, Mod4Mask, XK_F3, ui::VKEY_F3, 0, 0 }, { XK_4, 0, Mod4Mask, XK_F4, ui::VKEY_F4, 0, 0 }, { XK_5, 0, Mod4Mask, XK_F5, ui::VKEY_F5, 0, 0 }, { XK_6, 0, Mod4Mask, XK_F6, ui::VKEY_F6, 0, 0 }, { XK_7, 0, Mod4Mask, XK_F7, ui::VKEY_F7, 0, 0 }, { XK_8, 0, Mod4Mask, XK_F8, ui::VKEY_F8, 0, 0 }, { XK_9, 0, Mod4Mask, XK_F9, ui::VKEY_F9, 0, 0 }, { XK_0, 0, Mod4Mask, XK_F10, ui::VKEY_F10, 0, 0 }, { XK_minus, 0, Mod4Mask, XK_F11, ui::VKEY_F11, 0, 0 }, { XK_equal, 0, Mod4Mask, XK_F12, ui::VKEY_F12, 0, 0 } }; RewriteWithKeyboardRemappingsByKeyCode(kNumberKeysToFkeys, arraysize(kNumberKeysToFkeys), xkey->keycode, xkey->state, event->flags(), &remapped_native_keysym, &remapped_native_mods, &remapped_keycode, &remapped_mods); } if (remapped_keycode == ui::VKEY_UNKNOWN) return false; OverwriteEvent(event, NativeKeySymToNativeKeycode(remapped_native_keysym), remapped_native_mods, remapped_keycode, remapped_mods); return true; #else // TODO(danakj): Support Ash on other platforms if needed. return false; #endif } void EventRewriter::RewriteLocatedEvent(ui::LocatedEvent* event) { #if defined(OS_CHROMEOS) if (event->flags() & ui::EF_IS_SYNTHESIZED) return; XEvent* xevent = event->native_event(); if (!xevent || xevent->type != GenericEvent) return; XIDeviceEvent* xievent = static_cast(xevent->xcookie.data); if (xievent->evtype != XI_ButtonPress && xievent->evtype != XI_ButtonRelease) return; // First, remap modifier masks. int remapped_flags = 0; unsigned int remapped_native_modifiers = 0U; GetRemappedModifierMasks(event->flags(), xievent->mods.effective, &remapped_flags, &remapped_native_modifiers); xievent->mods.effective = remapped_native_modifiers; // Then, remap Alt+Button1 to Button3. if ((xievent->mods.effective & Mod1Mask) && xievent->detail == 1) { xievent->mods.effective &= ~Mod1Mask; xievent->detail = 3; if (xievent->evtype == XI_ButtonRelease) { // On the release clear the left button from the existing state and the // mods, and set the right button. XISetMask(xievent->buttons.mask, 3); XIClearMask(xievent->buttons.mask, 1); xievent->mods.effective &= ~Button1Mask; } } const int mouse_event_flags = event->flags() & (ui::EF_IS_DOUBLE_CLICK | ui::EF_IS_TRIPLE_CLICK | ui::EF_IS_NON_CLIENT | ui::EF_IS_SYNTHESIZED | ui::EF_FROM_TOUCH); event->set_flags(mouse_event_flags | ui::EventFlagsFromNative(xevent)); #else // TODO(yusukes): Support Ash on other platforms if needed. #endif } void EventRewriter::OverwriteEvent(ui::KeyEvent* event, unsigned int new_native_keycode, unsigned int new_native_state, ui::KeyboardCode new_keycode, int new_flags) { #if defined(OS_CHROMEOS) XEvent* xev = event->native_event(); XKeyEvent* xkey = &(xev->xkey); xkey->keycode = new_native_keycode; xkey->state = new_native_state; event->set_key_code(new_keycode); event->set_character(ui::GetCharacterFromKeyCode(event->key_code(), new_flags)); event->set_flags(new_flags); event->NormalizeFlags(); #else // TODO(yusukes): Support Ash on other platforms if needed. #endif } EventRewriter::DeviceType EventRewriter::DeviceAddedInternal( int device_id, const std::string& device_name) { const DeviceType type = EventRewriter::GetDeviceType(device_name); if (type == kDeviceAppleKeyboard) { VLOG(1) << "Apple keyboard '" << device_name << "' connected: " << "id=" << device_id; } // Always overwrite the existing device_id since the X server may reuse a // device id for an unattached device. device_id_to_type_[device_id] = type; return type; }