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 9 namespace remoting { 10 11 namespace { 12 13 // Returns true for OSKey codes. 14 static 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. 21 static 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(). 27 static 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(). 34 static 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(). 41 static 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 96 NormalizingInputFilterCros::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 104 NormalizingInputFilterCros::~NormalizingInputFilterCros() {} 105 106 void 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 118 void 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 131 void 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 174 void 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 196 void 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