1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "remoting/client/plugin/normalizing_input_filter_cros.h"
6
7#include "base/logging.h"
8
9namespace remoting {
10
11namespace {
12
13// Returns true for OSKey codes.
14static bool IsOsKey(unsigned int code) {
15  const unsigned int kUsbLeftOsKey = 0x0700e3;
16  const unsigned int kUsbRightOsKey = 0x0700e7;
17  return code == kUsbLeftOsKey || code == kUsbRightOsKey;
18}
19
20// Returns true for the left-hand Alt key.
21static bool IsLeftAltKey(unsigned int code) {
22  const unsigned int kUsbLeftAltKey = 0x0700e2;
23  return code == kUsbLeftAltKey;
24}
25
26// Returns true for codes generated by EventRewriter::RewriteFunctionKeys().
27static bool IsRewrittenFunctionKey(unsigned int code) {
28  const unsigned int kUsbFunctionKeyMin = 0x07003a;
29  const unsigned int kUsbFunctionKeyMax = 0x070045;
30  return code >= kUsbFunctionKeyMin && code <= kUsbFunctionKeyMax;
31}
32
33// Returns true for codes generated by EventRewriter::RewriteExtendedKeys().
34static bool IsRewrittenExtendedKey(unsigned int code) {
35  const unsigned int kUsbExtendedKeyMin = 0x070049;
36  const unsigned int kUsbExtendedKeyMax = 0x07004e;
37  return code >= kUsbExtendedKeyMin && code <= kUsbExtendedKeyMax;
38}
39
40// Returns true for codes generated by EventRewriter::Rewrite().
41static bool IsRewrittenKey(unsigned int code) {
42  return IsRewrittenExtendedKey(code) || IsRewrittenFunctionKey(code);
43}
44
45}  // namespace
46
47// The input filter tries to avoid sending keydown/keyup events for OSKey
48// (aka Search, WinKey, Cmd, Super) when it is used to rewrite other key events.
49// Rewriting via other combinations is not currently handled.
50//
51// OSKey events can be categorised as one of three kinds:
52// - Modifying - Holding the key down while executing other input modifies the
53//     effect of that input, e.g. OSKey+L causes the workstation to lock, e.g.
54//     OSKey + mouse-move performs an extended selection.
55// - Rewriting (ChromeOS only) - Holding the key down while pressing certain
56//     keys causes them to be treated as different keys, e.g. OSKey causes the
57//     Down key to behave as PageDown.
58// - Normal - Press & release of the key trigger an action, e.g. showing the
59//     Start menu.
60//
61// The input filter has four states:
62// 1. No OSKey has been pressed.
63//    - When an OSKey keydown is received, the event is deferred, and we move to
64//      State #2.
65// 2. An OSKey is pressed, but may be Normal, Rewriting or Modifying.
66//    - If the OSKey keyup is received, the key is Normal, both events are sent
67//      and we return to State #1.
68//    - If a Rewritten event is received we move to State #3.
69//    - If a Modified event is received the OSKey keydown is sent and we enter
70//      State #4.
71// 3. An OSKey is pressed, and is being used to Rewrite other key events.
72//    - If the OSKey keyup is received then it is suppressed, and we move to
73//      State #1.
74//    - If a Modified event is received the OSKey keydown is sent and we enter
75//      State #4.
76//    - If a Rewritten event is received then we stay in State #3.
77// 4. An OSKey is pressed, and is Modifying.
78//    - If the OSKey keyup is received then we send it and we move to State #1.
79//    - All other key event pass through the filter unchanged.
80//
81// ChromeOS also maps Alt+LeftClick to RightClick (even for an external mouse).
82// As with the OSKey remapping described above, this is fed into this filter
83// as Alt followed by RightClick. However, because there are other ways to
84// generate RightClick (two-finger tap, for example), rather than suppressing
85// the Alt key as we do for the OSKey (which would allow Alt+LeftClick to be
86// interpreted as interpreted as RightClick as per the ChromeOS idiom), the
87// filter maps RightClick to LeftClick while LeftAlt is held, which allows
88// Alt+LeftClick to be injected. The equivalent mapping using RightAlt is
89// unchanged, allowing Alt+RightClick also to be injected, as long as the
90// target application doesn't distinguish between left and right Alt keys.
91//
92// This file must be kept up-to-date with changes to
93// chrome/browser/chromeos/events/event_rewriter.cc
94
95
96NormalizingInputFilterCros::NormalizingInputFilterCros(
97    protocol::InputStub* input_stub)
98    : protocol::InputFilter(input_stub),
99      deferred_key_is_rewriting_(false),
100      modifying_key_(0),
101      left_alt_is_pressed_(false) {
102}
103
104NormalizingInputFilterCros::~NormalizingInputFilterCros() {}
105
106void NormalizingInputFilterCros::InjectKeyEvent(
107    const protocol::KeyEvent& event) {
108  DCHECK(event.has_usb_keycode());
109  DCHECK(event.has_pressed());
110
111  if (event.pressed()) {
112    ProcessKeyDown(event);
113  } else {
114    ProcessKeyUp(event);
115  }
116}
117
118void NormalizingInputFilterCros::InjectMouseEvent(
119    const protocol::MouseEvent& event) {
120  if (deferred_keydown_event_.has_usb_keycode())
121    SwitchRewritingKeyToModifying();
122  protocol::MouseEvent newEvent = event;
123  if (left_alt_is_pressed_ &&
124      event.has_button() &&
125      event.button() == protocol::MouseEvent::BUTTON_RIGHT) {
126    newEvent.set_button(protocol::MouseEvent::BUTTON_LEFT);
127  }
128  InputFilter::InjectMouseEvent(newEvent);
129}
130
131void NormalizingInputFilterCros::ProcessKeyDown(
132    const protocol::KeyEvent& event) {
133  // If |event| is |deferred_keydown_event_| repeat then assume that the user is
134  // holding the key down rather than using it to Rewrite.
135  if (deferred_keydown_event_.has_usb_keycode() &&
136      deferred_keydown_event_.usb_keycode() == event.usb_keycode()) {
137    SwitchRewritingKeyToModifying();
138  }
139
140  // If |event| is a |modifying_key_| repeat then let it pass through.
141  if (modifying_key_ == event.usb_keycode()) {
142    InputFilter::InjectKeyEvent(event);
143    return;
144  }
145
146  // If |event| is for an OSKey and we don't know whether it's a Normal,
147  // Rewriting or Modifying use, then hold the keydown event.
148  if (IsOsKey(event.usb_keycode())) {
149    deferred_keydown_event_ = event;
150    deferred_key_is_rewriting_ = false;
151    return;
152  }
153
154  // If |event| is for a Rewritten key then set a flag to prevent any deferred
155  // OSKey keydown from being sent when keyup is received for it. Otherwise,
156  // inject the deferred OSKey keydown, if any, and switch that key into
157  // Modifying mode.
158  if (IsRewrittenKey(event.usb_keycode())) {
159    // Note that there may not be a deferred OSKey event if there is a full
160    // PC keyboard connected, which can generate e.g. PageDown without
161    // rewriting.
162    deferred_key_is_rewriting_ = true;
163  } else {
164    if (deferred_keydown_event_.has_usb_keycode())
165      SwitchRewritingKeyToModifying();
166  }
167
168  if (IsLeftAltKey(event.usb_keycode()))
169    left_alt_is_pressed_ = true;
170
171  InputFilter::InjectKeyEvent(event);
172}
173
174void NormalizingInputFilterCros::ProcessKeyUp(const protocol::KeyEvent& event) {
175  if (deferred_keydown_event_.has_usb_keycode() &&
176      deferred_keydown_event_.usb_keycode() == event.usb_keycode()) {
177    if (deferred_key_is_rewriting_) {
178      // If we never sent the keydown then don't send a keyup.
179      deferred_keydown_event_ = protocol::KeyEvent();
180      return;
181    }
182
183    // If the OSKey hasn't Rewritten anything then treat as Modifying.
184    SwitchRewritingKeyToModifying();
185  }
186
187  if (modifying_key_ == event.usb_keycode())
188    modifying_key_ = 0;
189
190  if (IsLeftAltKey(event.usb_keycode()))
191    left_alt_is_pressed_ = false;
192
193  InputFilter::InjectKeyEvent(event);
194}
195
196void NormalizingInputFilterCros::SwitchRewritingKeyToModifying() {
197  DCHECK(deferred_keydown_event_.has_usb_keycode());
198  modifying_key_ = deferred_keydown_event_.usb_keycode();
199  InputFilter::InjectKeyEvent(deferred_keydown_event_);
200  deferred_keydown_event_ = protocol::KeyEvent();
201}
202
203}  // namespace remoting
204