| // 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/check.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 |