blob: 8c201c289ba77827b42bfcf5258afa5f407c766e [file] [log] [blame]
// Copyright 2015 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 "remoting/client/input/normalizing_input_filter_cros.h"
#include "base/logging.h"
#include "ui/events/keycodes/dom/dom_code.h"
namespace remoting {
namespace {
// Returns true for OSKey codes.
static bool IsOsKey(uint32_t code) {
return code == static_cast<uint32_t>(ui::DomCode::META_LEFT) ||
code == static_cast<uint32_t>(ui::DomCode::META_RIGHT);
}
// Returns true for codes generated by EventRewriter::RewriteFunctionKeys().
static bool IsRewrittenFunctionKey(uint32_t code) {
return code >= static_cast<uint32_t>(ui::DomCode::F1) &&
code <= static_cast<uint32_t>(ui::DomCode::F12);
}
// Returns true for codes generated by EventRewriter::RewriteExtendedKeys().
static bool IsRewrittenExtendedKey(uint32_t code) {
return code >= static_cast<uint32_t>(ui::DomCode::INSERT) &&
code <= static_cast<uint32_t>(ui::DomCode::PAGE_DOWN);
}
// Returns true for codes generated by EventRewriter::Rewrite().
static bool IsRewrittenKey(uint32_t code) {
return IsRewrittenExtendedKey(code) || IsRewrittenFunctionKey(code);
}
} // namespace
// The input filter tries to avoid sending keydown/keyup events for OSKey
// (aka Search, WinKey, Cmd, Super) when it is used to rewrite other key events.
// Rewriting via other combinations is not currently handled.
//
// OSKey events can be categorised as one of three kinds:
// - Modifying - Holding the key down while executing other input modifies the
// effect of that input, e.g. OSKey+L causes the workstation to lock, e.g.
// OSKey + mouse-move performs an extended selection.
// - Rewriting (ChromeOS only) - Holding the key down while pressing certain
// keys causes them to be treated as different keys, e.g. OSKey causes the
// Down key to behave as PageDown.
// - Normal - Press & release of the key trigger an action, e.g. showing the
// Start menu.
//
// The input filter has four states:
// 1. No OSKey has been pressed.
// - When an OSKey keydown is received, the event is deferred, and we move to
// State #2.
// 2. An OSKey is pressed, but may be Normal, Rewriting or Modifying.
// - If the OSKey keyup is received, the key is Normal, both events are sent
// and we return to State #1.
// - If a Rewritten event is received we move to State #3.
// - If a Modified event is received the OSKey keydown is sent and we enter
// State #4.
// 3. An OSKey is pressed, and is being used to Rewrite other key events.
// - If the OSKey keyup is received then it is suppressed, and we move to
// State #1.
// - If a Modified event is received the OSKey keydown is sent and we enter
// State #4.
// - If a Rewritten event is received then we stay in State #3.
// 4. An OSKey is pressed, and is Modifying.
// - If the OSKey keyup is received then we send it and we move to State #1.
// - All other key event pass through the filter unchanged.
//
// ChromeOS also maps Alt+LeftClick to RightClick (even for an external mouse).
// As with the OSKey remapping described above, this is fed into this filter
// as Alt followed by RightClick. However, because there are other ways to
// generate RightClick (two-finger tap, for example), rather than suppressing
// the Alt key as we do for the OSKey (which would allow Alt+LeftClick to be
// interpreted as interpreted as RightClick as per the ChromeOS idiom), the
// filter maps RightClick to LeftClick while LeftAlt is held, which allows
// Alt+LeftClick to be injected. The equivalent mapping using RightAlt is
// unchanged, allowing Alt+RightClick also to be injected, as long as the
// target application doesn't distinguish between left and right Alt keys.
//
// This file must be kept up to date with changes to
// ui/chromeos/events/event_rewriter_chromeos.cc
NormalizingInputFilterCros::NormalizingInputFilterCros(
protocol::InputStub* input_stub)
: protocol::InputFilter(input_stub),
deferred_key_is_rewriting_(false),
modifying_key_(0),
left_alt_is_pressed_(false),
right_alt_is_pressed_(false),
previous_mouse_x_(-1),
previous_mouse_y_(-1) {}
NormalizingInputFilterCros::~NormalizingInputFilterCros() = default;
void NormalizingInputFilterCros::InjectKeyEvent(
const protocol::KeyEvent& event_arg) {
DCHECK(event_arg.has_usb_keycode());
DCHECK(event_arg.has_pressed());
// ChromeOS doesn't have a concept of num lock, so unset the field.
protocol::KeyEvent event(event_arg);
event.clear_num_lock_state();
if (event.pressed()) {
ProcessKeyDown(event);
} else {
ProcessKeyUp(event);
}
}
void NormalizingInputFilterCros::InjectMouseEvent(
const protocol::MouseEvent& event) {
// If there's a rewriting/modifier decision pending, assume that it's
// intended to be used as a modifying key for this mouse event and send it.
if (deferred_keydown_event_.has_usb_keycode()) {
// TODO(jamiewalch): Until crbug.com/489468 is fixed, a spurious mouse move
// event is generated in response to certain key combinations, so check that
// this is actually an "interesting" event.
if (event.has_button() || event.x() != previous_mouse_x_ ||
event.y() != previous_mouse_y_) {
SwitchRewritingKeyToModifying();
}
}
previous_mouse_x_ = event.x();
previous_mouse_y_ = event.y();
protocol::MouseEvent newEvent = event;
if (left_alt_is_pressed_ && event.has_button() &&
event.button() == protocol::MouseEvent::BUTTON_RIGHT) {
newEvent.set_button(protocol::MouseEvent::BUTTON_LEFT);
}
InputFilter::InjectMouseEvent(newEvent);
}
void NormalizingInputFilterCros::ProcessKeyDown(
const protocol::KeyEvent& event) {
// If |event| is |deferred_keydown_event_| repeat then assume that the user is
// holding the key down rather than using it to Rewrite.
if (deferred_keydown_event_.has_usb_keycode() &&
deferred_keydown_event_.usb_keycode() == event.usb_keycode()) {
SwitchRewritingKeyToModifying();
}
// If |event| is a |modifying_key_| repeat then let it pass through.
if (modifying_key_ == event.usb_keycode()) {
InputFilter::InjectKeyEvent(event);
return;
}
// If |event| is for an OSKey and we don't know whether it's a Normal,
// Rewriting or Modifying use, then hold the keydown event.
if (IsOsKey(event.usb_keycode())) {
deferred_keydown_event_ = event;
deferred_key_is_rewriting_ = false;
return;
}
// If |event| is for a Rewritten key then set a flag to prevent any deferred
// OSKey keydown from being sent when keyup is received for it. Otherwise,
// inject the deferred OSKey keydown, if any, and switch that key into
// Modifying mode.
if (IsRewrittenKey(event.usb_keycode())) {
// Note that there may not be a deferred OSKey event if there is a full
// PC keyboard connected, which can generate e.g. PageDown without
// rewriting.
deferred_key_is_rewriting_ = true;
} else {
if (deferred_keydown_event_.has_usb_keycode())
SwitchRewritingKeyToModifying();
}
ui::DomCode dom_code = static_cast<ui::DomCode>(event.usb_keycode());
if (dom_code == ui::DomCode::ALT_LEFT) {
left_alt_is_pressed_ = true;
} else if (dom_code == ui::DomCode::ALT_RIGHT) {
right_alt_is_pressed_ = true;
}
protocol::KeyEvent updated_event = event;
UndoAltKeyMapping(&updated_event);
InputFilter::InjectKeyEvent(updated_event);
}
void NormalizingInputFilterCros::ProcessKeyUp(const protocol::KeyEvent& event) {
if (deferred_keydown_event_.has_usb_keycode() &&
deferred_keydown_event_.usb_keycode() == event.usb_keycode()) {
if (deferred_key_is_rewriting_) {
// If we never sent the keydown then don't send a keyup.
deferred_keydown_event_ = protocol::KeyEvent();
return;
}
// If the OSKey hasn't Rewritten anything then treat as Modifying.
SwitchRewritingKeyToModifying();
}
if (modifying_key_ == event.usb_keycode())
modifying_key_ = 0;
ui::DomCode dom_code = static_cast<ui::DomCode>(event.usb_keycode());
if (dom_code == ui::DomCode::ALT_LEFT) {
left_alt_is_pressed_ = false;
} else if (dom_code == ui::DomCode::ALT_RIGHT) {
right_alt_is_pressed_ = false;
}
protocol::KeyEvent updated_event = event;
UndoAltKeyMapping(&updated_event);
InputFilter::InjectKeyEvent(updated_event);
}
void NormalizingInputFilterCros::SwitchRewritingKeyToModifying() {
DCHECK(deferred_keydown_event_.has_usb_keycode());
modifying_key_ = deferred_keydown_event_.usb_keycode();
InputFilter::InjectKeyEvent(deferred_keydown_event_);
deferred_keydown_event_ = protocol::KeyEvent();
}
void NormalizingInputFilterCros::UndoAltKeyMapping(protocol::KeyEvent* event) {
if (!left_alt_is_pressed_ && !right_alt_is_pressed_) {
return;
}
// If the keycode is one for which the Alt and Search keyboard shortcuts are
// the same, map it back to the original key.
switch (event->usb_keycode()) {
case static_cast<uint32_t>(ui::DomCode::PAGE_DOWN):
event->set_usb_keycode(static_cast<uint32_t>(ui::DomCode::ARROW_DOWN));
break;
case static_cast<uint32_t>(ui::DomCode::PAGE_UP):
event->set_usb_keycode(static_cast<uint32_t>(ui::DomCode::ARROW_UP));
break;
case static_cast<uint32_t>(ui::DomCode::DEL):
event->set_usb_keycode(static_cast<uint32_t>(ui::DomCode::BACKSPACE));
break;
}
}
} // namespace remoting