blob: 4d75023c76cd46f35164b674c5ed2466654d835f [file] [log] [blame]
// Copyright 2014 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/chromeos/events/event_rewriter.h"
#include <vector>
#include "ash/sticky_keys/sticky_keys_controller.h"
#include "ash/wm/window_state.h"
#include "ash/wm/window_util.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_util.h"
#include "base/sys_info.h"
#include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
#include "chrome/browser/extensions/extension_commands_global_registry.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/pref_names.h"
#include "chromeos/chromeos_switches.h"
#include "components/user_manager/user_manager.h"
#include "ui/base/ime/chromeos/ime_keyboard.h"
#include "ui/base/ime/chromeos/input_method_manager.h"
#include "ui/events/devices/device_data_manager.h"
#include "ui/events/event.h"
#include "ui/events/event_utils.h"
#include "ui/events/keycodes/dom/dom_code.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/events/keycodes/keyboard_code_conversion.h"
#include "ui/wm/core/window_util.h"
#if defined(USE_X11)
#include <X11/extensions/XInput2.h>
#include <X11/Xlib.h>
// Get rid of macros from Xlib.h that conflicts with other parts of the code.
#undef RootWindow
#undef Status
#include "ui/base/x/x11_util.h"
#include "ui/events/keycodes/keyboard_code_conversion_x.h"
#endif
namespace chromeos {
namespace {
// Hotrod controller vendor/product ids.
const int kHotrodRemoteVendorId = 0x0471;
const int kHotrodRemoteProductId = 0x21cc;
const int kUnknownVendorId = -1;
const int kUnknownProductId = -1;
// Table of key properties of remappable keys and/or remapping targets.
// This is searched in two distinct ways:
// - |remap_to| is an |input_method::ModifierKey|, which is the form
// held in user preferences. |GetRemappedKey()| maps this to the
// corresponding |key_code| and characterstic event |flag|.
// - |flag| is a |ui::EventFlags|. |GetRemappedModifierMasks()| maps this
// to the corresponding user preference |pref_name| for that flag's
// key, so that it can then be remapped as above.
// In addition |kModifierRemappingCtrl| is a direct reference to the
// Control key entry in the table, used in handling Chromebook Diamond
// and Apple Command keys.
const struct ModifierRemapping {
int remap_to;
int flag;
ui::KeyboardCode key_code;
const char* pref_name;
} kModifierRemappings[] = {
{input_method::kSearchKey, ui::EF_COMMAND_DOWN, ui::VKEY_LWIN,
prefs::kLanguageRemapSearchKeyTo},
{input_method::kControlKey, ui::EF_CONTROL_DOWN, ui::VKEY_CONTROL,
prefs::kLanguageRemapControlKeyTo},
{input_method::kAltKey, ui::EF_ALT_DOWN, ui::VKEY_MENU,
prefs::kLanguageRemapAltKeyTo},
{input_method::kVoidKey, 0, ui::VKEY_UNKNOWN, NULL},
{input_method::kCapsLockKey, ui::EF_MOD3_DOWN, ui::VKEY_CAPITAL,
prefs::kLanguageRemapCapsLockKeyTo},
{input_method::kEscapeKey, 0, ui::VKEY_ESCAPE, NULL},
{0, 0, ui::VKEY_F15, prefs::kLanguageRemapDiamondKeyTo},
};
const ModifierRemapping* kModifierRemappingCtrl = &kModifierRemappings[1];
// 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 HasDiamondKey() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
chromeos::switches::kHasChromeOSDiamondKey);
}
bool IsISOLevel5ShiftUsedByCurrentInputMethod() {
// 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.
input_method::InputMethodManager* manager =
input_method::InputMethodManager::Get();
return manager->IsISOLevel5ShiftUsedByCurrentInputMethod();
}
bool IsExtensionCommandRegistered(ui::KeyboardCode key_code, int flags) {
// Some keyboard events for ChromeOS get rewritten, such as:
// Search+Shift+Left gets converted to Shift+Home (BeginDocument).
// This doesn't make sense if the user has assigned that shortcut
// to an extension. Because:
// 1) The extension would, upon seeing a request for Ctrl+Shift+Home have
// to register for Shift+Home, instead.
// 2) The conversion is unnecessary, because Shift+Home (BeginDocument) isn't
// going to be executed.
// Therefore, we skip converting the accelerator if an extension has
// registered for this shortcut.
Profile* profile = ProfileManager::GetActiveUserProfile();
if (!profile || !extensions::ExtensionCommandsGlobalRegistry::Get(profile))
return false;
int modifiers = flags & (ui::EF_SHIFT_DOWN | ui::EF_CONTROL_DOWN |
ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN);
ui::Accelerator accelerator(key_code, modifiers);
return extensions::ExtensionCommandsGlobalRegistry::Get(profile)
->IsRegistered(accelerator);
}
EventRewriter::DeviceType GetDeviceType(const std::string& device_name,
int vendor_id,
int product_id) {
if (vendor_id == kHotrodRemoteVendorId &&
product_id == kHotrodRemoteProductId) {
return EventRewriter::kDeviceHotrodRemote;
}
if (LowerCaseEqualsASCII(device_name, "virtual core keyboard"))
return EventRewriter::kDeviceVirtualCoreKeyboard;
std::vector<std::string> 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 EventRewriter::kDeviceAppleKeyboard;
}
return EventRewriter::kDeviceUnknown;
}
} // namespace
EventRewriter::EventRewriter(ash::StickyKeysController* sticky_keys_controller)
: last_keyboard_device_id_(ui::ED_UNKNOWN_DEVICE),
ime_keyboard_for_testing_(NULL),
pref_service_for_testing_(NULL),
sticky_keys_controller_(sticky_keys_controller),
current_diamond_key_modifier_flags_(ui::EF_NONE) {
}
EventRewriter::~EventRewriter() {
}
EventRewriter::DeviceType EventRewriter::KeyboardDeviceAddedForTesting(
int device_id,
const std::string& device_name) {
// Tests must avoid XI2 reserved device IDs.
DCHECK((device_id < 0) || (device_id > 1));
return KeyboardDeviceAddedInternal(device_id,
device_name,
kUnknownVendorId,
kUnknownProductId);
}
void EventRewriter::RewriteMouseButtonEventForTesting(
const ui::MouseEvent& event,
scoped_ptr<ui::Event>* rewritten_event) {
RewriteMouseButtonEvent(event, rewritten_event);
}
ui::EventRewriteStatus EventRewriter::RewriteEvent(
const ui::Event& event,
scoped_ptr<ui::Event>* rewritten_event) {
if ((event.type() == ui::ET_KEY_PRESSED) ||
(event.type() == ui::ET_KEY_RELEASED)) {
return RewriteKeyEvent(static_cast<const ui::KeyEvent&>(event),
rewritten_event);
}
if ((event.type() == ui::ET_MOUSE_PRESSED) ||
(event.type() == ui::ET_MOUSE_RELEASED)) {
return RewriteMouseButtonEvent(static_cast<const ui::MouseEvent&>(event),
rewritten_event);
}
if (event.type() == ui::ET_MOUSEWHEEL) {
return RewriteMouseWheelEvent(
static_cast<const ui::MouseWheelEvent&>(event), rewritten_event);
}
if ((event.type() == ui::ET_TOUCH_PRESSED) ||
(event.type() == ui::ET_TOUCH_RELEASED)) {
return RewriteTouchEvent(static_cast<const ui::TouchEvent&>(event),
rewritten_event);
}
if (event.IsScrollEvent()) {
return RewriteScrollEvent(static_cast<const ui::ScrollEvent&>(event),
rewritten_event);
}
return ui::EVENT_REWRITE_CONTINUE;
}
ui::EventRewriteStatus EventRewriter::NextDispatchEvent(
const ui::Event& last_event,
scoped_ptr<ui::Event>* new_event) {
if (sticky_keys_controller_) {
// In the case of sticky keys, we know what the events obtained here are:
// modifier key releases that match the ones previously discarded. So, we
// know that they don't have to be passed through the post-sticky key
// rewriting phases, |RewriteExtendedKeys()| and |RewriteFunctionKeys()|,
// because those phases do nothing with modifier key releases.
return sticky_keys_controller_->NextDispatchEvent(new_event);
}
NOTREACHED();
return ui::EVENT_REWRITE_CONTINUE;
}
void EventRewriter::BuildRewrittenKeyEvent(
const ui::KeyEvent& key_event,
ui::KeyboardCode key_code,
int flags,
scoped_ptr<ui::Event>* rewritten_event) {
ui::KeyEvent* rewritten_key_event = NULL;
#if defined(USE_X11)
XEvent* xev = key_event.native_event();
if (xev) {
XEvent xkeyevent;
// Convert all XI2-based key events into X11 core-based key events,
// until consumers no longer depend on receiving X11 core events.
if (xev->type == GenericEvent)
ui::InitXKeyEventFromXIDeviceEvent(*xev, &xkeyevent);
else
xkeyevent.xkey = xev->xkey;
unsigned int original_x11_keycode = xkeyevent.xkey.keycode;
// Update native event to match rewritten |ui::Event|.
// The X11 keycode represents a physical key position, so it shouldn't
// change unless we have actually changed keys, not just modifiers.
// This is one guard against problems like crbug.com/390263.
if (key_event.key_code() != key_code) {
xkeyevent.xkey.keycode =
XKeyCodeForWindowsKeyCode(key_code, flags, gfx::GetXDisplay());
}
ui::KeyEvent x11_key_event(&xkeyevent);
rewritten_key_event = new ui::KeyEvent(x11_key_event);
// For numpad keys, the key char should always NOT be changed because
// XKeyCodeForWindowsKeyCode method cannot handle non-US keyboard layout.
// The correct key char can be got from original X11 keycode but not for the
// rewritten X11 keycode.
// For Shift+NumpadKey cases, use the rewritten X11 keycode (US layout).
// Please see crbug.com/335644.
if (key_code >= ui::VKEY_NUMPAD0 && key_code <= ui::VKEY_DIVIDE) {
XEvent numpad_xevent;
numpad_xevent.xkey = xkeyevent.xkey;
// Remove the shift state before getting key char.
// Because X11/XKB sometimes returns unexpected key char for
// Shift+NumpadKey. e.g. Shift+Numpad_4 returns 'D', etc.
numpad_xevent.xkey.state &= ~ShiftMask;
numpad_xevent.xkey.state |= Mod2Mask; // Always set NumLock mask.
if (!(flags & ui::EF_SHIFT_DOWN))
numpad_xevent.xkey.keycode = original_x11_keycode;
rewritten_key_event->set_character(
ui::GetCharacterFromXEvent(&numpad_xevent));
rewritten_key_event->native_event()->xkey.state |= Mod2Mask;
}
}
#endif
if (!rewritten_key_event)
rewritten_key_event = new ui::KeyEvent(key_event);
rewritten_key_event->set_flags(flags);
rewritten_key_event->set_key_code(key_code);
#if defined(USE_X11)
ui::UpdateX11EventForFlags(rewritten_key_event);
rewritten_key_event->NormalizeFlags();
#endif
rewritten_event->reset(rewritten_key_event);
}
void EventRewriter::DeviceKeyPressedOrReleased(int device_id) {
std::map<int, DeviceType>::const_iterator iter =
device_id_to_type_.find(device_id);
DeviceType type;
if (iter != device_id_to_type_.end())
type = iter->second;
else
type = KeyboardDeviceAdded(device_id);
// Ignore virtual Xorg keyboard (magic that generates key repeat
// events). Pretend that the previous real keyboard is the one that is still
// in use.
if (type == kDeviceVirtualCoreKeyboard)
return;
last_keyboard_device_id_ = device_id;
}
const PrefService* EventRewriter::GetPrefService() const {
if (pref_service_for_testing_)
return pref_service_for_testing_;
Profile* profile = ProfileManager::GetActiveUserProfile();
return profile ? profile->GetPrefs() : NULL;
}
bool EventRewriter::IsAppleKeyboard() const {
return IsLastKeyboardOfType(kDeviceAppleKeyboard);
}
bool EventRewriter::IsHotrodRemote() const {
return IsLastKeyboardOfType(kDeviceHotrodRemote);
}
bool EventRewriter::IsLastKeyboardOfType(DeviceType device_type) const {
if (last_keyboard_device_id_ == ui::ED_UNKNOWN_DEVICE)
return false;
// Check which device generated |event|.
std::map<int, DeviceType>::const_iterator iter =
device_id_to_type_.find(last_keyboard_device_id_);
if (iter == device_id_to_type_.end()) {
LOG(ERROR) << "Device ID " << last_keyboard_device_id_ << " is unknown.";
return false;
}
const DeviceType type = iter->second;
return type == device_type;
}
bool EventRewriter::TopRowKeysAreFunctionKeys(const ui::KeyEvent& event) const {
const PrefService* prefs = GetPrefService();
if (prefs && prefs->FindPreference(prefs::kLanguageSendFunctionKeys) &&
prefs->GetBoolean(prefs::kLanguageSendFunctionKeys))
return true;
ash::wm::WindowState* state = ash::wm::GetActiveWindowState();
return state ? state->top_row_keys_are_function_keys() : false;
}
int EventRewriter::GetRemappedModifierMasks(const PrefService& pref_service,
const ui::Event& event,
int original_flags) const {
int unmodified_flags = original_flags;
int rewritten_flags = current_diamond_key_modifier_flags_;
for (size_t i = 0; unmodified_flags && (i < arraysize(kModifierRemappings));
++i) {
const ModifierRemapping* remapped_key = NULL;
if (!(unmodified_flags & kModifierRemappings[i].flag))
continue;
switch (kModifierRemappings[i].flag) {
case ui::EF_COMMAND_DOWN:
// Rewrite Command key presses on an Apple keyboard to Control.
if (IsAppleKeyboard()) {
DCHECK_EQ(ui::EF_CONTROL_DOWN, kModifierRemappingCtrl->flag);
remapped_key = kModifierRemappingCtrl;
}
break;
case ui::EF_MOD3_DOWN:
// If EF_MOD3_DOWN is used by the current input method, leave it alone;
// it is not remappable.
if (IsISOLevel5ShiftUsedByCurrentInputMethod())
continue;
// Otherwise, Mod3Mask is set on X events when the Caps Lock key
// is down, but, if Caps Lock is remapped, CapsLock is NOT set,
// because pressing the key does not invoke caps lock. So, the
// kModifierRemappings[] table uses EF_MOD3_DOWN for the Caps
// Lock remapping.
break;
default:
break;
}
if (!remapped_key && kModifierRemappings[i].pref_name) {
remapped_key =
GetRemappedKey(kModifierRemappings[i].pref_name, pref_service);
}
if (remapped_key) {
unmodified_flags &= ~kModifierRemappings[i].flag;
rewritten_flags |= remapped_key->flag;
}
}
return rewritten_flags | unmodified_flags;
}
bool EventRewriter::RewriteWithKeyboardRemappingsByKeyCode(
const KeyboardRemapping* remappings,
size_t num_remappings,
const MutableKeyState& input,
MutableKeyState* remapped_state) {
for (size_t i = 0; i < num_remappings; ++i) {
const KeyboardRemapping& map = remappings[i];
if (input.key_code != map.input_key_code)
continue;
if ((input.flags & map.input_flags) != map.input_flags)
continue;
remapped_state->key_code = map.output_key_code;
remapped_state->flags = (input.flags & ~map.input_flags) | map.output_flags;
return true;
}
return false;
}
ui::EventRewriteStatus EventRewriter::RewriteKeyEvent(
const ui::KeyEvent& key_event,
scoped_ptr<ui::Event>* rewritten_event) {
if (IsExtensionCommandRegistered(key_event.key_code(), key_event.flags()))
return ui::EVENT_REWRITE_CONTINUE;
if (key_event.source_device_id() != ui::ED_UNKNOWN_DEVICE)
DeviceKeyPressedOrReleased(key_event.source_device_id());
// Drop repeated keys from Hotrod remote.
if ((key_event.flags() & ui::EF_IS_REPEAT) &&
(key_event.type() == ui::ET_KEY_PRESSED) &&
IsHotrodRemote() && key_event.key_code() != ui::VKEY_BACK) {
return ui::EVENT_REWRITE_DISCARD;
}
MutableKeyState state = {key_event.flags(), key_event.key_code()};
// Do not rewrite an event sent by ui_controls::SendKeyPress(). See
// crbug.com/136465.
if (!(key_event.flags() & ui::EF_FINAL)) {
RewriteModifierKeys(key_event, &state);
RewriteNumPadKeys(key_event, &state);
}
ui::EventRewriteStatus status = ui::EVENT_REWRITE_CONTINUE;
bool is_sticky_key_extension_command = false;
if (sticky_keys_controller_) {
status = sticky_keys_controller_->RewriteKeyEvent(
key_event, state.key_code, &state.flags);
if (status == ui::EVENT_REWRITE_DISCARD)
return ui::EVENT_REWRITE_DISCARD;
is_sticky_key_extension_command =
IsExtensionCommandRegistered(state.key_code, state.flags);
}
// If sticky key rewrites the event, and it matches an extension command, do
// not further rewrite the event since it won't match the extension command
// thereafter.
if (!is_sticky_key_extension_command && !(key_event.flags() & ui::EF_FINAL)) {
RewriteExtendedKeys(key_event, &state);
RewriteFunctionKeys(key_event, &state);
}
if ((key_event.flags() == state.flags) &&
(key_event.key_code() == state.key_code) &&
#if defined(USE_X11)
// TODO(kpschoedel): This test is present because several consumers of
// key events depend on having a native core X11 event, so we rewrite
// all XI2 key events (GenericEvent) into corresponding core X11 key
// events. Remove this when event consumers no longer care about
// native X11 event details (crbug.com/380349).
(!key_event.HasNativeEvent() ||
(key_event.native_event()->type != GenericEvent)) &&
#endif
(status == ui::EVENT_REWRITE_CONTINUE)) {
return ui::EVENT_REWRITE_CONTINUE;
}
// Sticky keys may have returned a result other than |EVENT_REWRITE_CONTINUE|,
// in which case we need to preserve that return status. Alternatively, we
// might be here because key_event changed, in which case we need to
// return |EVENT_REWRITE_REWRITTEN|.
if (status == ui::EVENT_REWRITE_CONTINUE)
status = ui::EVENT_REWRITE_REWRITTEN;
BuildRewrittenKeyEvent(
key_event, state.key_code, state.flags, rewritten_event);
return status;
}
ui::EventRewriteStatus EventRewriter::RewriteMouseButtonEvent(
const ui::MouseEvent& mouse_event,
scoped_ptr<ui::Event>* rewritten_event) {
int flags = mouse_event.flags();
RewriteLocatedEvent(mouse_event, &flags);
ui::EventRewriteStatus status = ui::EVENT_REWRITE_CONTINUE;
if (sticky_keys_controller_)
status = sticky_keys_controller_->RewriteMouseEvent(mouse_event, &flags);
int changed_button = ui::EF_NONE;
if ((mouse_event.type() == ui::ET_MOUSE_PRESSED) ||
(mouse_event.type() == ui::ET_MOUSE_RELEASED)) {
changed_button = RewriteModifierClick(mouse_event, &flags);
}
if ((mouse_event.flags() == flags) &&
(status == ui::EVENT_REWRITE_CONTINUE)) {
return ui::EVENT_REWRITE_CONTINUE;
}
if (status == ui::EVENT_REWRITE_CONTINUE)
status = ui::EVENT_REWRITE_REWRITTEN;
ui::MouseEvent* rewritten_mouse_event = new ui::MouseEvent(mouse_event);
rewritten_event->reset(rewritten_mouse_event);
rewritten_mouse_event->set_flags(flags);
#if defined(USE_X11)
ui::UpdateX11EventForFlags(rewritten_mouse_event);
#endif
if (changed_button != ui::EF_NONE) {
rewritten_mouse_event->set_changed_button_flags(changed_button);
#if defined(USE_X11)
ui::UpdateX11EventForChangedButtonFlags(rewritten_mouse_event);
#endif
}
return status;
}
ui::EventRewriteStatus EventRewriter::RewriteMouseWheelEvent(
const ui::MouseWheelEvent& wheel_event,
scoped_ptr<ui::Event>* rewritten_event) {
if (!sticky_keys_controller_)
return ui::EVENT_REWRITE_CONTINUE;
int flags = wheel_event.flags();
ui::EventRewriteStatus status =
sticky_keys_controller_->RewriteMouseEvent(wheel_event, &flags);
if ((wheel_event.flags() == flags) &&
(status == ui::EVENT_REWRITE_CONTINUE)) {
return ui::EVENT_REWRITE_CONTINUE;
}
if (status == ui::EVENT_REWRITE_CONTINUE)
status = ui::EVENT_REWRITE_REWRITTEN;
ui::MouseWheelEvent* rewritten_wheel_event =
new ui::MouseWheelEvent(wheel_event);
rewritten_event->reset(rewritten_wheel_event);
rewritten_wheel_event->set_flags(flags);
#if defined(USE_X11)
ui::UpdateX11EventForFlags(rewritten_wheel_event);
#endif
return status;
}
ui::EventRewriteStatus EventRewriter::RewriteTouchEvent(
const ui::TouchEvent& touch_event,
scoped_ptr<ui::Event>* rewritten_event) {
int flags = touch_event.flags();
RewriteLocatedEvent(touch_event, &flags);
if (touch_event.flags() == flags)
return ui::EVENT_REWRITE_CONTINUE;
ui::TouchEvent* rewritten_touch_event = new ui::TouchEvent(touch_event);
rewritten_event->reset(rewritten_touch_event);
rewritten_touch_event->set_flags(flags);
#if defined(USE_X11)
ui::UpdateX11EventForFlags(rewritten_touch_event);
#endif
return ui::EVENT_REWRITE_REWRITTEN;
}
ui::EventRewriteStatus EventRewriter::RewriteScrollEvent(
const ui::ScrollEvent& scroll_event,
scoped_ptr<ui::Event>* rewritten_event) {
int flags = scroll_event.flags();
ui::EventRewriteStatus status = ui::EVENT_REWRITE_CONTINUE;
if (sticky_keys_controller_)
status = sticky_keys_controller_->RewriteScrollEvent(scroll_event, &flags);
if (status == ui::EVENT_REWRITE_CONTINUE)
return status;
ui::ScrollEvent* rewritten_scroll_event = new ui::ScrollEvent(scroll_event);
rewritten_event->reset(rewritten_scroll_event);
rewritten_scroll_event->set_flags(flags);
#if defined(USE_X11)
ui::UpdateX11EventForFlags(rewritten_scroll_event);
#endif
return status;
}
void EventRewriter::RewriteModifierKeys(const ui::KeyEvent& key_event,
MutableKeyState* state) {
DCHECK(key_event.type() == ui::ET_KEY_PRESSED ||
key_event.type() == ui::ET_KEY_RELEASED);
// 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.
// TODO(kpschoedel): check whether this is still necessary.
if (user_manager::UserManager::Get()->IsLoggedInAsGuest() &&
LoginDisplayHostImpl::default_host())
return;
const PrefService* pref_service = GetPrefService();
if (!pref_service)
return;
MutableKeyState incoming = *state;
state->flags = ui::EF_NONE;
int characteristic_flag = ui::EF_NONE;
// First, remap the key code.
const ModifierRemapping* remapped_key = NULL;
switch (incoming.key_code) {
// On Chrome OS, F15 (XF86XK_Launch6) with NumLock (Mod2Mask) is sent
// when Diamond key is pressed.
case ui::VKEY_F15:
// 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 of F15 is Control, even if --has-chromeos-diamond-key
// is absent, according to unit test comments.
if (!remapped_key) {
DCHECK_EQ(ui::VKEY_CONTROL, kModifierRemappingCtrl->key_code);
remapped_key = kModifierRemappingCtrl;
}
// F15 is not a modifier key, so we need to track its state directly.
if (key_event.type() == ui::ET_KEY_PRESSED) {
int remapped_flag = remapped_key->flag;
if (remapped_key->remap_to == input_method::kCapsLockKey)
remapped_flag |= ui::EF_CAPS_LOCK_DOWN;
current_diamond_key_modifier_flags_ = remapped_flag;
} else {
current_diamond_key_modifier_flags_ = ui::EF_NONE;
}
break;
// On Chrome OS, XF86XK_Launch7 (F16) with Mod3Mask is sent when Caps Lock
// is pressed (with one exception: when
// IsISOLevel5ShiftUsedByCurrentInputMethod() is true, the key generates
// XK_ISO_Level3_Shift with Mod3Mask, not XF86XK_Launch7).
case ui::VKEY_F16:
characteristic_flag = ui::EF_CAPS_LOCK_DOWN;
remapped_key =
GetRemappedKey(prefs::kLanguageRemapCapsLockKeyTo, *pref_service);
break;
case ui::VKEY_LWIN:
case ui::VKEY_RWIN:
characteristic_flag = ui::EF_COMMAND_DOWN;
// Rewrite Command-L/R key presses on an Apple keyboard to Control.
if (IsAppleKeyboard()) {
DCHECK_EQ(ui::VKEY_CONTROL, kModifierRemappingCtrl->key_code);
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 ui::VKEY_CONTROL:
characteristic_flag = ui::EF_CONTROL_DOWN;
remapped_key =
GetRemappedKey(prefs::kLanguageRemapControlKeyTo, *pref_service);
break;
case ui::VKEY_MENU:
// ALT key
characteristic_flag = ui::EF_ALT_DOWN;
remapped_key =
GetRemappedKey(prefs::kLanguageRemapAltKeyTo, *pref_service);
break;
default:
break;
}
if (remapped_key) {
state->key_code = remapped_key->key_code;
incoming.flags |= characteristic_flag;
characteristic_flag = remapped_key->flag;
}
// Next, remap modifier bits.
state->flags |=
GetRemappedModifierMasks(*pref_service, key_event, incoming.flags);
if (key_event.type() == ui::ET_KEY_PRESSED)
state->flags |= characteristic_flag;
else
state->flags &= ~characteristic_flag;
// Toggle Caps Lock if the remapped key is ui::VKEY_CAPITAL.
if (key_event.type() == ui::ET_KEY_PRESSED &&
#if defined(USE_X11)
// ... but for X11, do nothing if the original key is ui::VKEY_CAPITAL
// (i.e. a Caps Lock key on an external keyboard is pressed) since X
// handles that itself.
incoming.key_code != ui::VKEY_CAPITAL &&
#endif
state->key_code == ui::VKEY_CAPITAL) {
chromeos::input_method::ImeKeyboard* ime_keyboard =
ime_keyboard_for_testing_
? ime_keyboard_for_testing_
: chromeos::input_method::InputMethodManager::Get()
->GetImeKeyboard();
ime_keyboard->SetCapsLockEnabled(!ime_keyboard->CapsLockIsEnabled());
}
}
void EventRewriter::RewriteNumPadKeys(const ui::KeyEvent& key_event,
MutableKeyState* state) {
DCHECK(key_event.type() == ui::ET_KEY_PRESSED ||
key_event.type() == ui::ET_KEY_RELEASED);
MutableKeyState incoming = *state;
static const struct NumPadRemapping {
ui::DomCode input_dom_code;
ui::KeyboardCode input_key_code;
ui::KeyboardCode output_key_code;
} kNumPadRemappings[] = {
{ui::DomCode::NUMPAD_DECIMAL, ui::VKEY_DELETE, ui::VKEY_DECIMAL},
{ui::DomCode::NUMPAD0, ui::VKEY_INSERT, ui::VKEY_NUMPAD0},
{ui::DomCode::NUMPAD1, ui::VKEY_END, ui::VKEY_NUMPAD1},
{ui::DomCode::NUMPAD2, ui::VKEY_DOWN, ui::VKEY_NUMPAD2},
{ui::DomCode::NUMPAD3, ui::VKEY_NEXT, ui::VKEY_NUMPAD3},
{ui::DomCode::NUMPAD4, ui::VKEY_LEFT, ui::VKEY_NUMPAD4},
{ui::DomCode::NUMPAD5, ui::VKEY_CLEAR, ui::VKEY_NUMPAD5},
{ui::DomCode::NUMPAD6, ui::VKEY_RIGHT, ui::VKEY_NUMPAD6},
{ui::DomCode::NUMPAD7, ui::VKEY_HOME, ui::VKEY_NUMPAD7},
{ui::DomCode::NUMPAD8, ui::VKEY_UP, ui::VKEY_NUMPAD8},
{ui::DomCode::NUMPAD9, ui::VKEY_PRIOR, ui::VKEY_NUMPAD9},
};
for (const auto& map : kNumPadRemappings) {
if ((incoming.key_code == map.input_key_code) &&
(key_event.code() == map.input_dom_code)) {
state->key_code = map.output_key_code;
break;
}
}
}
void EventRewriter::RewriteExtendedKeys(const ui::KeyEvent& key_event,
MutableKeyState* state) {
DCHECK(key_event.type() == ui::ET_KEY_PRESSED ||
key_event.type() == ui::ET_KEY_RELEASED);
MutableKeyState incoming = *state;
bool rewritten = false;
if ((incoming.flags & (ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN)) ==
(ui::EF_COMMAND_DOWN | ui::EF_ALT_DOWN)) {
// Allow Search to avoid rewriting extended keys.
static const KeyboardRemapping kAvoidRemappings[] = {
{ // Alt+Backspace
ui::VKEY_BACK, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, ui::VKEY_BACK,
ui::EF_ALT_DOWN,
},
{ // Control+Alt+Up
ui::VKEY_UP,
ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN,
ui::VKEY_UP, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN,
},
{ // Alt+Up
ui::VKEY_UP, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, ui::VKEY_UP,
ui::EF_ALT_DOWN,
},
{ // Control+Alt+Down
ui::VKEY_DOWN,
ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN,
ui::VKEY_DOWN, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN,
},
{ // Alt+Down
ui::VKEY_DOWN, ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, ui::VKEY_DOWN,
ui::EF_ALT_DOWN,
}};
rewritten = RewriteWithKeyboardRemappingsByKeyCode(
kAvoidRemappings, arraysize(kAvoidRemappings), incoming, state);
}
if (!rewritten && (incoming.flags & ui::EF_COMMAND_DOWN)) {
static const KeyboardRemapping kSearchRemappings[] = {
{ // Search+BackSpace -> Delete
ui::VKEY_BACK, ui::EF_COMMAND_DOWN, ui::VKEY_DELETE, 0},
{ // Search+Left -> Home
ui::VKEY_LEFT, ui::EF_COMMAND_DOWN, ui::VKEY_HOME, 0},
{ // Search+Up -> Prior (aka PageUp)
ui::VKEY_UP, ui::EF_COMMAND_DOWN, ui::VKEY_PRIOR, 0},
{ // Search+Right -> End
ui::VKEY_RIGHT, ui::EF_COMMAND_DOWN, ui::VKEY_END, 0},
{ // Search+Down -> Next (aka PageDown)
ui::VKEY_DOWN, ui::EF_COMMAND_DOWN, ui::VKEY_NEXT, 0},
{ // Search+Period -> Insert
ui::VKEY_OEM_PERIOD, ui::EF_COMMAND_DOWN, ui::VKEY_INSERT, 0}};
rewritten = RewriteWithKeyboardRemappingsByKeyCode(
kSearchRemappings, arraysize(kSearchRemappings), incoming, state);
}
if (!rewritten && (incoming.flags & ui::EF_ALT_DOWN)) {
static const KeyboardRemapping kNonSearchRemappings[] = {
{ // Alt+BackSpace -> Delete
ui::VKEY_BACK, ui::EF_ALT_DOWN, ui::VKEY_DELETE, 0},
{ // Control+Alt+Up -> Home
ui::VKEY_UP, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, ui::VKEY_HOME, 0},
{ // Alt+Up -> Prior (aka PageUp)
ui::VKEY_UP, ui::EF_ALT_DOWN, ui::VKEY_PRIOR, 0},
{ // Control+Alt+Down -> End
ui::VKEY_DOWN, ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, ui::VKEY_END, 0},
{ // Alt+Down -> Next (aka PageDown)
ui::VKEY_DOWN, ui::EF_ALT_DOWN, ui::VKEY_NEXT, 0}};
rewritten = RewriteWithKeyboardRemappingsByKeyCode(
kNonSearchRemappings, arraysize(kNonSearchRemappings), incoming, state);
}
}
void EventRewriter::RewriteFunctionKeys(const ui::KeyEvent& key_event,
MutableKeyState* state) {
CHECK(key_event.type() == ui::ET_KEY_PRESSED ||
key_event.type() == ui::ET_KEY_RELEASED);
MutableKeyState incoming = *state;
bool rewritten = false;
if ((incoming.key_code >= ui::VKEY_F1) &&
(incoming.key_code <= ui::VKEY_F24)) {
// By default the top row (F1-F12) keys are system 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_function_keys = TopRowKeysAreFunctionKeys(key_event);
bool search_is_pressed = (incoming.flags & ui::EF_COMMAND_DOWN) != 0;
// Search? Top Row Result
// ------- -------- ------
// No Fn Unchanged
// No System Fn -> System
// Yes Fn Fn -> System
// Yes System Search+Fn -> Fn
if (top_row_keys_are_function_keys == search_is_pressed) {
// Rewrite the F1-F12 keys on a Chromebook keyboard to system keys.
static const KeyboardRemapping kFkeysToSystemKeys[] = {
{ui::VKEY_F1, 0, ui::VKEY_BROWSER_BACK, 0},
{ui::VKEY_F2, 0, ui::VKEY_BROWSER_FORWARD, 0},
{ui::VKEY_F3, 0, ui::VKEY_BROWSER_REFRESH, 0},
{ui::VKEY_F4, 0, ui::VKEY_MEDIA_LAUNCH_APP2, 0},
{ui::VKEY_F5, 0, ui::VKEY_MEDIA_LAUNCH_APP1, 0},
{ui::VKEY_F6, 0, ui::VKEY_BRIGHTNESS_DOWN, 0},
{ui::VKEY_F7, 0, ui::VKEY_BRIGHTNESS_UP, 0},
{ui::VKEY_F8, 0, ui::VKEY_VOLUME_MUTE, 0},
{ui::VKEY_F9, 0, ui::VKEY_VOLUME_DOWN, 0},
{ui::VKEY_F10, 0, ui::VKEY_VOLUME_UP, 0},
};
MutableKeyState incoming_without_command = incoming;
incoming_without_command.flags &= ~ui::EF_COMMAND_DOWN;
rewritten =
RewriteWithKeyboardRemappingsByKeyCode(kFkeysToSystemKeys,
arraysize(kFkeysToSystemKeys),
incoming_without_command,
state);
} else if (search_is_pressed) {
// Allow Search to avoid rewriting F1-F12.
state->flags &= ~ui::EF_COMMAND_DOWN;
rewritten = true;
}
}
if (!rewritten && (incoming.flags & ui::EF_COMMAND_DOWN)) {
// Remap Search+<number> to F<number>.
// 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[] = {
{ui::VKEY_1, ui::EF_COMMAND_DOWN, ui::VKEY_F1, 0},
{ui::VKEY_2, ui::EF_COMMAND_DOWN, ui::VKEY_F2, 0},
{ui::VKEY_3, ui::EF_COMMAND_DOWN, ui::VKEY_F3, 0},
{ui::VKEY_4, ui::EF_COMMAND_DOWN, ui::VKEY_F4, 0},
{ui::VKEY_5, ui::EF_COMMAND_DOWN, ui::VKEY_F5, 0},
{ui::VKEY_6, ui::EF_COMMAND_DOWN, ui::VKEY_F6, 0},
{ui::VKEY_7, ui::EF_COMMAND_DOWN, ui::VKEY_F7, 0},
{ui::VKEY_8, ui::EF_COMMAND_DOWN, ui::VKEY_F8, 0},
{ui::VKEY_9, ui::EF_COMMAND_DOWN, ui::VKEY_F9, 0},
{ui::VKEY_0, ui::EF_COMMAND_DOWN, ui::VKEY_F10, 0},
{ui::VKEY_OEM_MINUS, ui::EF_COMMAND_DOWN, ui::VKEY_F11, 0},
{ui::VKEY_OEM_PLUS, ui::EF_COMMAND_DOWN, ui::VKEY_F12, 0}};
rewritten = RewriteWithKeyboardRemappingsByKeyCode(
kNumberKeysToFkeys, arraysize(kNumberKeysToFkeys), incoming, state);
}
}
void EventRewriter::RewriteLocatedEvent(const ui::Event& event,
int* flags) {
const PrefService* pref_service = GetPrefService();
if (!pref_service)
return;
*flags = GetRemappedModifierMasks(*pref_service, event, *flags);
}
int EventRewriter::RewriteModifierClick(const ui::MouseEvent& mouse_event,
int* flags) {
// Remap Alt+Button1 to Button3.
const int kAltLeftButton = (ui::EF_ALT_DOWN | ui::EF_LEFT_MOUSE_BUTTON);
if (((*flags & kAltLeftButton) == kAltLeftButton) &&
((mouse_event.type() == ui::ET_MOUSE_PRESSED) ||
pressed_device_ids_.count(mouse_event.source_device_id()))) {
*flags &= ~kAltLeftButton;
*flags |= ui::EF_RIGHT_MOUSE_BUTTON;
if (mouse_event.type() == ui::ET_MOUSE_PRESSED)
pressed_device_ids_.insert(mouse_event.source_device_id());
else
pressed_device_ids_.erase(mouse_event.source_device_id());
return ui::EF_RIGHT_MOUSE_BUTTON;
}
return ui::EF_NONE;
}
EventRewriter::DeviceType EventRewriter::KeyboardDeviceAddedInternal(
int device_id,
const std::string& device_name,
int vendor_id,
int product_id) {
const DeviceType type = GetDeviceType(device_name, vendor_id, product_id);
if (type == kDeviceAppleKeyboard) {
VLOG(1) << "Apple keyboard '" << device_name << "' connected: "
<< "id=" << device_id;
} else if (type == kDeviceHotrodRemote) {
VLOG(1) << "Hotrod remote '" << device_name << "' connected: "
<< "id=" << device_id;
} else if (type == kDeviceVirtualCoreKeyboard) {
VLOG(1) << "Xorg virtual '" << device_name << "' connected: "
<< "id=" << device_id;
} else {
VLOG(1) << "Unknown 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;
}
EventRewriter::DeviceType EventRewriter::KeyboardDeviceAdded(int device_id) {
if (!ui::DeviceDataManager::HasInstance())
return kDeviceUnknown;
const std::vector<ui::KeyboardDevice>& keyboards =
ui::DeviceDataManager::GetInstance()->keyboard_devices();
for (const auto& keyboard : keyboards) {
if (keyboard.id == device_id) {
return KeyboardDeviceAddedInternal(
keyboard.id, keyboard.name, keyboard.vendor_id, keyboard.product_id);
}
}
return kDeviceUnknown;
}
} // namespace chromeos