// 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_split.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/dom_key.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 properties of remappable keys and/or remapping targets.
//
// This is used in two distinct ways: for rewriting key up/down events,
// and for rewriting modifier EventFlags on any kind of event.
//
// For the first case, rewriting key up/down events, |RewriteModifierKeys()|
// determines the preference name |prefs::kLanguageRemap...KeyTo| for the
// incoming key and, using |GetRemappedKey()|, gets the user preference
// value |input_method::k...Key| for the incoming key, and finally finds that
// value in this table to obtain the |result| properties of the target key.
//
// For the second case, rewriting modifier EventFlags,
// |GetRemappedModifierMasks()| processes every table entry whose |flag|
// is set in the incoming event. Using the |pref_name| in the table entry,
// it likewise uses |GetRemappedKey()| to find the properties of the
// user preference target key, and replaces the flag accordingly.
const struct ModifierRemapping {
  int flag;
  int remap_to;
  const char* pref_name;
  EventRewriter::MutableKeyState result;
} kModifierRemappings[] = {
    {// kModifierRemappingCtrl references this entry by index.
     ui::EF_CONTROL_DOWN,
     input_method::kControlKey,
     prefs::kLanguageRemapControlKeyTo,
     {ui::EF_CONTROL_DOWN, ui::DomCode::CONTROL_LEFT, ui::DomKey::CONTROL,
      ui::VKEY_CONTROL}},
    {// kModifierRemappingNeoMod3 references this entry by index.
     ui::EF_MOD3_DOWN | ui::EF_ALTGR_DOWN,
     input_method::kNumModifierKeys,
     nullptr,
     {ui::EF_MOD3_DOWN | ui::EF_ALTGR_DOWN, ui::DomCode::CAPS_LOCK,
      ui::DomKey::ALT_GRAPH, ui::VKEY_ALTGR}},
    {ui::EF_COMMAND_DOWN,
     input_method::kSearchKey,
     prefs::kLanguageRemapSearchKeyTo,
     {ui::EF_COMMAND_DOWN, ui::DomCode::OS_LEFT, ui::DomKey::OS,
      ui::VKEY_LWIN}},
    {ui::EF_ALT_DOWN,
     input_method::kAltKey,
     prefs::kLanguageRemapAltKeyTo,
     {ui::EF_ALT_DOWN, ui::DomCode::ALT_LEFT, ui::DomKey::ALT, ui::VKEY_MENU}},
    {ui::EF_NONE,
     input_method::kVoidKey,
     nullptr,
     {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::NONE, ui::VKEY_UNKNOWN}},
    {ui::EF_MOD3_DOWN,
     input_method::kCapsLockKey,
     prefs::kLanguageRemapCapsLockKeyTo,
     {ui::EF_MOD3_DOWN, ui::DomCode::CAPS_LOCK, ui::DomKey::CAPS_LOCK,
      ui::VKEY_CAPITAL}},
    {ui::EF_NONE,
     input_method::kEscapeKey,
     nullptr,
     {ui::EF_NONE, ui::DomCode::ESCAPE, ui::DomKey::ESCAPE, ui::VKEY_ESCAPE}},
    {ui::EF_NONE,
     input_method::kNumModifierKeys,
     prefs::kLanguageRemapDiamondKeyTo,
     {ui::EF_NONE, ui::DomCode::F15, ui::DomKey::F15, ui::VKEY_F15}}};

const ModifierRemapping* kModifierRemappingCtrl = &kModifierRemappings[0];
const ModifierRemapping* kModifierRemappingNeoMod3 = &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 (base::LowerCaseEqualsASCII(device_name, "virtual core keyboard"))
    return EventRewriter::kDeviceVirtualCoreKeyboard;

  std::vector<std::string> tokens = base::SplitString(
      device_name, " .", base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY);

  // 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 && base::LowerCaseEqualsASCII(tokens[i], "apple"))
      found_apple = true;
    if (!found_keyboard && base::LowerCaseEqualsASCII(tokens[i], "keyboard"))
      found_keyboard = true;
    if (found_apple && found_keyboard)
      return EventRewriter::kDeviceAppleKeyboard;
  }

  return EventRewriter::kDeviceUnknown;
}

struct KeyboardRemapping {
  // MatchKeyboardRemapping() succeeds if the tested has all of the specified
  // flags (and possibly other flags), and either the key_code matches or the
  // condition's key_code is VKEY_UNKNOWN.
  struct Condition {
    int flags;
    ui::KeyboardCode key_code;
  } condition;
  // ApplyRemapping(), which is the primary user of this structure,
  // conditionally sets the output fields from the |result| here.
  // - |dom_code| is set if |result.dom_code| is not NONE.
  // - |dom_key| and |character| are set if |result.dom_key| is not NONE.
  // -|key_code| is set if |result.key_code| is not VKEY_UNKNOWN.
  // - |flags| are always set from |result.flags|, but this can be |EF_NONE|.
  EventRewriter::MutableKeyState result;
};

bool MatchKeyboardRemapping(const EventRewriter::MutableKeyState& suspect,
                            const KeyboardRemapping::Condition& test) {
  return ((test.key_code == ui::VKEY_UNKNOWN) ||
          (test.key_code == suspect.key_code)) &&
         ((suspect.flags & test.flags) == test.flags);
}

void ApplyRemapping(const EventRewriter::MutableKeyState& changes,
                    EventRewriter::MutableKeyState* state) {
  state->flags |= changes.flags;
  if (changes.code != ui::DomCode::NONE)
    state->code = changes.code;
  if (changes.key != ui::DomKey::NONE)
    state->key = changes.key;
  if (changes.key_code != ui::VKEY_UNKNOWN)
    state->key_code = changes.key_code;
}

// Given a set of KeyboardRemapping structs, finds a matching struct
// if possible, and updates the remapped event values. Returns true if a
// remapping was found and remapped values were updated.
bool RewriteWithKeyboardRemappings(
    const KeyboardRemapping* mappings,
    size_t num_mappings,
    const EventRewriter::MutableKeyState& input_state,
    EventRewriter::MutableKeyState* remapped_state) {
  for (size_t i = 0; i < num_mappings; ++i) {
    const KeyboardRemapping& map = mappings[i];
    if (MatchKeyboardRemapping(input_state, map.condition)) {
      remapped_state->flags = (input_state.flags & ~map.condition.flags);
      ApplyRemapping(map.result, remapped_state);
      return true;
    }
  }
  return false;
}

void SetMeaningForLayout(ui::EventType type,
                         EventRewriter::MutableKeyState* state) {
  // Currently layout is applied by creating a temporary key event with the
  // current physical state, and extracting the layout results.
  ui::KeyEvent key(type, state->key_code, state->code, state->flags);
  state->key = key.GetDomKey();
}

ui::DomCode RelocateModifier(ui::DomCode code, ui::DomKeyLocation location) {
  bool right = (location == ui::DomKeyLocation::RIGHT);
  switch (code) {
    case ui::DomCode::CONTROL_LEFT:
    case ui::DomCode::CONTROL_RIGHT:
      return right ? ui::DomCode::CONTROL_RIGHT : ui::DomCode::CONTROL_LEFT;
    case ui::DomCode::SHIFT_LEFT:
    case ui::DomCode::SHIFT_RIGHT:
      return right ? ui::DomCode::SHIFT_RIGHT : ui::DomCode::SHIFT_LEFT;
    case ui::DomCode::ALT_LEFT:
    case ui::DomCode::ALT_RIGHT:
      return right ? ui::DomCode::ALT_RIGHT : ui::DomCode::ALT_LEFT;
    case ui::DomCode::OS_LEFT:
    case ui::DomCode::OS_RIGHT:
      return right ? ui::DomCode::OS_RIGHT : ui::DomCode::OS_LEFT;
    default:
      break;
  }
  return code;
}

}  // 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),
      pressed_modifier_latches_(ui::EF_NONE),
      latched_modifier_latches_(ui::EF_NONE),
      used_modifier_latches_(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,
    const MutableKeyState& state,
    scoped_ptr<ui::Event>* rewritten_event) {
  ui::KeyEvent* rewritten_key_event =
      new ui::KeyEvent(key_event.type(), state.key_code, state.code,
                       state.flags, state.key, key_event.time_stamp());
  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_ |
                        pressed_modifier_latches_ | latched_modifier_latches_;
  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;
      case ui::EF_MOD3_DOWN | ui::EF_ALTGR_DOWN:
        if ((original_flags & ui::EF_ALTGR_DOWN) &&
            IsISOLevel5ShiftUsedByCurrentInputMethod()) {
          remapped_key = kModifierRemappingNeoMod3;
        }
        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;
}

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.code(),
                           key_event.GetDomKey(), 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)) {
    if (RewriteModifierKeys(key_event, &state)) {
      // Early exit with completed event.
      BuildRewrittenKeyEvent(key_event, state, rewritten_event);
      return ui::EVENT_REWRITE_REWRITTEN;
    }
    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 flags have changed, this may change the interpretation of the key,
  // so reapply layout.
  if (state.flags != key_event.flags())
    SetMeaningForLayout(key_event.type(), &state);

  // 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, 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;
}

bool 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 false;

  const PrefService* pref_service = GetPrefService();
  if (!pref_service)
    return false;

  MutableKeyState incoming = *state;
  state->flags = ui::EF_NONE;
  int characteristic_flag = ui::EF_NONE;
  bool exact_event = false;

  // First, remap the key code.
  const ModifierRemapping* remapped_key = NULL;
  switch (incoming.key) {
    // On Chrome OS, F15 (XF86XK_Launch6) with NumLock (Mod2Mask) is sent
    // when Diamond key is pressed.
    case ui::DomKey::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->result.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::DomKey::F16:
      characteristic_flag = ui::EF_CAPS_LOCK_DOWN;
      remapped_key =
          GetRemappedKey(prefs::kLanguageRemapCapsLockKeyTo, *pref_service);
      break;
    case ui::DomKey::OS:
      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->result.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::DomKey::CONTROL:
      characteristic_flag = ui::EF_CONTROL_DOWN;
      remapped_key =
          GetRemappedKey(prefs::kLanguageRemapControlKeyTo, *pref_service);
      break;
    case ui::DomKey::ALT:
      // ALT key
      characteristic_flag = ui::EF_ALT_DOWN;
      remapped_key =
          GetRemappedKey(prefs::kLanguageRemapAltKeyTo, *pref_service);
      break;
    case ui::DomKey::ALT_GRAPH:
      // The Neo2 codes modifiers such that CapsLock appears as VKEY_ALTGR,
      // but AltGraph (right Alt) also appears as VKEY_ALTGR in Neo2,
      // as it does in other layouts. Neo2's "Mod3" is represented in
      // EventFlags by a combination of AltGr+Mod3, while its "Mod4" is
      // AltGr alone.
      if (IsISOLevel5ShiftUsedByCurrentInputMethod()) {
        if (incoming.code == ui::DomCode::CAPS_LOCK) {
          characteristic_flag = ui::EF_ALTGR_DOWN | ui::EF_MOD3_DOWN;
          remapped_key =
              GetRemappedKey(prefs::kLanguageRemapCapsLockKeyTo, *pref_service);
        } else {
          characteristic_flag = ui::EF_ALTGR_DOWN;
          remapped_key =
              GetRemappedKey(prefs::kLanguageRemapSearchKeyTo, *pref_service);
        }
      }
      if (remapped_key && remapped_key->result.key_code == ui::VKEY_CAPITAL)
        remapped_key = kModifierRemappingNeoMod3;
      break;
#if !defined(USE_X11)
    case ui::DomKey::ALT_GRAPH_LATCH:
      if (key_event.type() == ui::ET_KEY_PRESSED) {
        pressed_modifier_latches_ |= ui::EF_ALTGR_DOWN;
      } else {
        pressed_modifier_latches_ &= ~ui::EF_ALTGR_DOWN;
        if (used_modifier_latches_ & ui::EF_ALTGR_DOWN)
          used_modifier_latches_ &= ~ui::EF_ALTGR_DOWN;
        else
          latched_modifier_latches_ |= ui::EF_ALTGR_DOWN;
      }
      // Rewrite to AltGraph. When this key is used like a regular modifier,
      // the web-exposed result looks like a use of the regular modifier.
      // When it's used as a latch, the web-exposed result is a vacuous
      // modifier press-and-release, which should be harmless, but preserves
      // the event for applications using the |code| (e.g. remoting).
      state->key = ui::DomKey::ALT_GRAPH;
      state->key_code = ui::VKEY_ALTGR;
      exact_event = true;
      break;
#endif
    default:
      break;
  }

  if (remapped_key) {
    state->key_code = remapped_key->result.key_code;
    state->code = remapped_key->result.code;
    state->key = remapped_key->result.key;
    incoming.flags |= characteristic_flag;
    characteristic_flag = remapped_key->flag;
    state->code = RelocateModifier(
        state->code, ui::KeycodeConverter::DomCodeToLocation(incoming.code));
  }

  // 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;

  if (key_event.type() == ui::ET_KEY_PRESSED) {
    if (!ui::KeycodeConverter::IsDomKeyForModifier(state->key)) {
      used_modifier_latches_ |= pressed_modifier_latches_;
      latched_modifier_latches_ = ui::EF_NONE;
    }
    // Toggle Caps Lock if the remapped key is ui::VKEY_CAPITAL.
    if (state->key_code == ui::VKEY_CAPITAL
#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
        ) {
      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());
    }
  }
  return exact_event;
}

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);
  static const struct NumPadRemapping {
    ui::KeyboardCode input_key_code;
    EventRewriter::MutableKeyState result;
  } kNumPadRemappings[] = {
      {ui::VKEY_DELETE,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'.'>::Character,
        ui::VKEY_DECIMAL}},
      {ui::VKEY_INSERT,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'0'>::Character,
        ui::VKEY_NUMPAD0}},
      {ui::VKEY_END,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'1'>::Character,
        ui::VKEY_NUMPAD1}},
      {ui::VKEY_DOWN,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'2'>::Character,
        ui::VKEY_NUMPAD2}},
      {ui::VKEY_NEXT,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'3'>::Character,
        ui::VKEY_NUMPAD3}},
      {ui::VKEY_LEFT,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'4'>::Character,
        ui::VKEY_NUMPAD4}},
      {ui::VKEY_CLEAR,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'5'>::Character,
        ui::VKEY_NUMPAD5}},
      {ui::VKEY_RIGHT,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'6'>::Character,
        ui::VKEY_NUMPAD6}},
      {ui::VKEY_HOME,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'7'>::Character,
        ui::VKEY_NUMPAD7}},
      {ui::VKEY_UP,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'8'>::Character,
        ui::VKEY_NUMPAD8}},
      {ui::VKEY_PRIOR,
       {ui::EF_NONE, ui::DomCode::NONE, ui::DomKey::Constant<'9'>::Character,
        ui::VKEY_NUMPAD9}}};
  for (const auto& map : kNumPadRemappings) {
    if (state->key_code == map.input_key_code) {
      if (ui::KeycodeConverter::DomCodeToLocation(state->code) ==
          ui::DomKeyLocation::NUMPAD) {
        ApplyRemapping(map.result, state);
      }
      return;
    }
  }
}

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;

  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.
    // For these, we only remove the EF_COMMAND_DOWN flag.
    static const KeyboardRemapping::Condition kAvoidRemappings[] = {
        {// Alt+Backspace
         ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, ui::VKEY_BACK},
        {// Control+Alt+Up
         ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN,
         ui::VKEY_UP},
        {// Alt+Up
         ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, ui::VKEY_UP},
        {// Control+Alt+Down
         ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN | ui::EF_COMMAND_DOWN,
         ui::VKEY_DOWN},
        {// Alt+Down
         ui::EF_ALT_DOWN | ui::EF_COMMAND_DOWN, ui::VKEY_DOWN}};
    for (const auto& condition : kAvoidRemappings) {
      if (MatchKeyboardRemapping(*state, condition)) {
        state->flags = incoming.flags & ~ui::EF_COMMAND_DOWN;
        return;
      }
    }
  }

  if (incoming.flags & ui::EF_COMMAND_DOWN) {
    static const KeyboardRemapping kSearchRemappings[] = {
        {// Search+BackSpace -> Delete
         {ui::EF_COMMAND_DOWN, ui::VKEY_BACK},
         {ui::EF_NONE, ui::DomCode::DEL, ui::DomKey::DEL, ui::VKEY_DELETE}},
        {// Search+Left -> Home
         {ui::EF_COMMAND_DOWN, ui::VKEY_LEFT},
         {ui::EF_NONE, ui::DomCode::HOME, ui::DomKey::HOME, ui::VKEY_HOME}},
        {// Search+Up -> Prior (aka PageUp)
         {ui::EF_COMMAND_DOWN, ui::VKEY_UP},
         {ui::EF_NONE, ui::DomCode::PAGE_UP, ui::DomKey::PAGE_UP,
          ui::VKEY_PRIOR}},
        {// Search+Right -> End
         {ui::EF_COMMAND_DOWN, ui::VKEY_RIGHT},
         {ui::EF_NONE, ui::DomCode::END, ui::DomKey::END, ui::VKEY_END}},
        {// Search+Down -> Next (aka PageDown)
         {ui::EF_COMMAND_DOWN, ui::VKEY_DOWN},
         {ui::EF_NONE, ui::DomCode::PAGE_DOWN, ui::DomKey::PAGE_DOWN,
          ui::VKEY_NEXT}},
        {// Search+Period -> Insert
         {ui::EF_COMMAND_DOWN, ui::VKEY_OEM_PERIOD},
         {ui::EF_NONE, ui::DomCode::INSERT, ui::DomKey::INSERT,
          ui::VKEY_INSERT}}};
    if (RewriteWithKeyboardRemappings(
            kSearchRemappings, arraysize(kSearchRemappings), incoming, state)) {
      return;
    }
  }

  if (incoming.flags & ui::EF_ALT_DOWN) {
    static const KeyboardRemapping kNonSearchRemappings[] = {
        {// Alt+BackSpace -> Delete
         {ui::EF_ALT_DOWN, ui::VKEY_BACK},
         {ui::EF_NONE, ui::DomCode::DEL, ui::DomKey::DEL, ui::VKEY_DELETE}},
        {// Control+Alt+Up -> Home
         {ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, ui::VKEY_UP},
         {ui::EF_NONE, ui::DomCode::HOME, ui::DomKey::HOME, ui::VKEY_HOME}},
        {// Alt+Up -> Prior (aka PageUp)
         {ui::EF_ALT_DOWN, ui::VKEY_UP},
         {ui::EF_NONE, ui::DomCode::PAGE_UP, ui::DomKey::PAGE_UP,
          ui::VKEY_PRIOR}},
        {// Control+Alt+Down -> End
         {ui::EF_ALT_DOWN | ui::EF_CONTROL_DOWN, ui::VKEY_DOWN},
         {ui::EF_NONE, ui::DomCode::END, ui::DomKey::END, ui::VKEY_END}},
        {// Alt+Down -> Next (aka PageDown)
         {ui::EF_ALT_DOWN, ui::VKEY_DOWN},
         {ui::EF_NONE, ui::DomCode::PAGE_DOWN, ui::DomKey::PAGE_DOWN,
          ui::VKEY_NEXT}}};
    if (RewriteWithKeyboardRemappings(kNonSearchRemappings,
                                      arraysize(kNonSearchRemappings), incoming,
                                      state)) {
      return;
    }
  }
}

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);

  if ((state->key_code >= ui::VKEY_F1) && (state->key_code <= ui::VKEY_F12)) {
    // 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 = (state->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::EF_NONE, ui::VKEY_F1},
           {ui::EF_NONE, ui::DomCode::BROWSER_BACK, ui::DomKey::BROWSER_BACK,
            ui::VKEY_BROWSER_BACK}},
          {{ui::EF_NONE, ui::VKEY_F2},
           {ui::EF_NONE, ui::DomCode::BROWSER_FORWARD,
            ui::DomKey::BROWSER_FORWARD, ui::VKEY_BROWSER_FORWARD}},
          {{ui::EF_NONE, ui::VKEY_F3},
           {ui::EF_NONE, ui::DomCode::BROWSER_REFRESH,
            ui::DomKey::BROWSER_REFRESH, ui::VKEY_BROWSER_REFRESH}},
          {{ui::EF_NONE, ui::VKEY_F4},
           {ui::EF_NONE, ui::DomCode::ZOOM_TOGGLE, ui::DomKey::ZOOM_TOGGLE,
            ui::VKEY_MEDIA_LAUNCH_APP2}},
          {{ui::EF_NONE, ui::VKEY_F5},
           {ui::EF_NONE, ui::DomCode::SELECT_TASK,
            ui::DomKey::LAUNCH_MY_COMPUTER, ui::VKEY_MEDIA_LAUNCH_APP1}},
          {{ui::EF_NONE, ui::VKEY_F6},
           {ui::EF_NONE, ui::DomCode::BRIGHTNESS_DOWN,
            ui::DomKey::BRIGHTNESS_DOWN, ui::VKEY_BRIGHTNESS_DOWN}},
          {{ui::EF_NONE, ui::VKEY_F7},
           {ui::EF_NONE, ui::DomCode::BRIGHTNESS_UP, ui::DomKey::BRIGHTNESS_UP,
            ui::VKEY_BRIGHTNESS_UP}},
          {{ui::EF_NONE, ui::VKEY_F8},
           {ui::EF_NONE, ui::DomCode::VOLUME_MUTE, ui::DomKey::VOLUME_MUTE,
            ui::VKEY_VOLUME_MUTE}},
          {{ui::EF_NONE, ui::VKEY_F9},
           {ui::EF_NONE, ui::DomCode::VOLUME_DOWN, ui::DomKey::VOLUME_DOWN,
            ui::VKEY_VOLUME_DOWN}},
          {{ui::EF_NONE, ui::VKEY_F10},
           {ui::EF_NONE, ui::DomCode::VOLUME_UP, ui::DomKey::VOLUME_UP,
            ui::VKEY_VOLUME_UP}},
      };
      MutableKeyState incoming_without_command = *state;
      incoming_without_command.flags &= ~ui::EF_COMMAND_DOWN;
      if (RewriteWithKeyboardRemappings(kFkeysToSystemKeys,
                                        arraysize(kFkeysToSystemKeys),
                                        incoming_without_command, state)) {
        return;
      }
    } else if (search_is_pressed) {
      // Allow Search to avoid rewriting F1-F12.
      state->flags &= ~ui::EF_COMMAND_DOWN;
      return;
    }
  }

  if (state->flags & ui::EF_COMMAND_DOWN) {
    // Remap Search+<number> to F<number>.
    // We check the DOM3 |code| here instead of the VKEY, as these keys may
    // have different |KeyboardCode|s when modifiers are pressed, such as shift.
    static const struct {
      ui::DomCode input_dom_code;
      EventRewriter::MutableKeyState result;
    } kNumberKeysToFkeys[] = {
        {ui::DomCode::DIGIT1,
         {ui::EF_NONE, ui::DomCode::F1, ui::DomKey::F1, ui::VKEY_F1}},
        {ui::DomCode::DIGIT2,
         {ui::EF_NONE, ui::DomCode::F2, ui::DomKey::F2, ui::VKEY_F2}},
        {ui::DomCode::DIGIT3,
         {ui::EF_NONE, ui::DomCode::F3, ui::DomKey::F3, ui::VKEY_F3}},
        {ui::DomCode::DIGIT4,
         {ui::EF_NONE, ui::DomCode::F4, ui::DomKey::F4, ui::VKEY_F4}},
        {ui::DomCode::DIGIT5,
         {ui::EF_NONE, ui::DomCode::F5, ui::DomKey::F5, ui::VKEY_F5}},
        {ui::DomCode::DIGIT6,
         {ui::EF_NONE, ui::DomCode::F6, ui::DomKey::F6, ui::VKEY_F6}},
        {ui::DomCode::DIGIT7,
         {ui::EF_NONE, ui::DomCode::F7, ui::DomKey::F7, ui::VKEY_F7}},
        {ui::DomCode::DIGIT8,
         {ui::EF_NONE, ui::DomCode::F8, ui::DomKey::F8, ui::VKEY_F8}},
        {ui::DomCode::DIGIT9,
         {ui::EF_NONE, ui::DomCode::F9, ui::DomKey::F9, ui::VKEY_F9}},
        {ui::DomCode::DIGIT0,
         {ui::EF_NONE, ui::DomCode::F10, ui::DomKey::F10, ui::VKEY_F10}},
        {ui::DomCode::MINUS,
         {ui::EF_NONE, ui::DomCode::F11, ui::DomKey::F11, ui::VKEY_F11}},
        {ui::DomCode::EQUAL,
         {ui::EF_NONE, ui::DomCode::F12, ui::DomKey::F12, ui::VKEY_F12}}};
    for (const auto& map : kNumberKeysToFkeys) {
      if (state->code == map.input_dom_code) {
        state->flags &= ~ui::EF_COMMAND_DOWN;
        ApplyRemapping(map.result, state);
        return;
      }
    }
  }
}

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