diff options
author | wez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-15 21:53:15 +0000 |
---|---|---|
committer | wez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-08-15 21:53:15 +0000 |
commit | b35a866cd4ddf6ea33e7ca66789ad5bd3a51718c (patch) | |
tree | 29a91e67540fe16b208f56165d152d27dd7ce55a /remoting/client | |
parent | aaafcdcd232c3053d7d03826bbb1bdc3fca3e004 (diff) | |
download | chromium_src-b35a866cd4ddf6ea33e7ca66789ad5bd3a51718c.zip chromium_src-b35a866cd4ddf6ea33e7ca66789ad5bd3a51718c.tar.gz chromium_src-b35a866cd4ddf6ea33e7ca66789ad5bd3a51718c.tar.bz2 |
Work around OSKey being used as a rewriting modifier to get extended keys on CrOS.
Moves MacKeyEventProcessor to NormalizingInputFilterMac and adds a NormalizingInputFilterAsh which tries to properly distinguish the key-writing versus key-modifying behaviours of OSKey (CrOS' Search key).
BUG=230049
Committed: https://src.chromium.org/viewvc/chrome?view=rev&revision=217207
Review URL: https://chromiumcodereview.appspot.com/18345018
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@217841 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting/client')
-rw-r--r-- | remoting/client/plugin/chromoting_instance.cc | 10 | ||||
-rw-r--r-- | remoting/client/plugin/chromoting_instance.h | 6 | ||||
-rw-r--r-- | remoting/client/plugin/mac_key_event_processor.h | 76 | ||||
-rw-r--r-- | remoting/client/plugin/normalizing_input_filter.cc | 20 | ||||
-rw-r--r-- | remoting/client/plugin/normalizing_input_filter.h | 21 | ||||
-rw-r--r-- | remoting/client/plugin/normalizing_input_filter_cros.cc | 206 | ||||
-rw-r--r-- | remoting/client/plugin/normalizing_input_filter_cros_unittest.cc | 212 | ||||
-rw-r--r-- | remoting/client/plugin/normalizing_input_filter_mac.cc (renamed from remoting/client/plugin/mac_key_event_processor.cc) | 79 | ||||
-rw-r--r-- | remoting/client/plugin/normalizing_input_filter_mac_unittest.cc (renamed from remoting/client/plugin/mac_key_event_processor_unittest.cc) | 92 |
9 files changed, 582 insertions, 140 deletions
diff --git a/remoting/client/plugin/chromoting_instance.cc b/remoting/client/plugin/chromoting_instance.cc index e22452a..3469ca2 100644 --- a/remoting/client/plugin/chromoting_instance.cc +++ b/remoting/client/plugin/chromoting_instance.cc @@ -174,15 +174,9 @@ ChromotingInstance::ChromotingInstance(PP_Instance pp_instance) plugin_task_runner_(new PluginThreadTaskRunner(&plugin_thread_delegate_)), context_(plugin_task_runner_.get()), input_tracker_(&mouse_input_filter_), -#if defined(OS_MACOSX) - // On Mac we need an extra filter to inject missing keyup events. - // See remoting/client/plugin/mac_key_event_processor.h for more details. - mac_key_event_processor_(&input_tracker_), - key_mapper_(&mac_key_event_processor_), -#else key_mapper_(&input_tracker_), -#endif - input_handler_(&key_mapper_), + normalizing_input_filter_(CreateNormalizingInputFilter(&key_mapper_)), + input_handler_(normalizing_input_filter_.get()), use_async_pin_dialog_(false), weak_factory_(this) { RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE | PP_INPUTEVENT_CLASS_WHEEL); diff --git a/remoting/client/plugin/chromoting_instance.h b/remoting/client/plugin/chromoting_instance.h index 23c4c49..4c786e1 100644 --- a/remoting/client/plugin/chromoting_instance.h +++ b/remoting/client/plugin/chromoting_instance.h @@ -31,7 +31,7 @@ #include "remoting/client/client_context.h" #include "remoting/client/client_user_interface.h" #include "remoting/client/key_event_mapper.h" -#include "remoting/client/plugin/mac_key_event_processor.h" +#include "remoting/client/plugin/normalizing_input_filter.h" #include "remoting/client/plugin/pepper_input_handler.h" #include "remoting/client/plugin/pepper_plugin_thread_delegate.h" #include "remoting/proto/event.pb.h" @@ -248,10 +248,8 @@ class ChromotingInstance : // Input pipeline components, in reverse order of distance from input source. protocol::MouseInputFilter mouse_input_filter_; protocol::InputEventTracker input_tracker_; -#if defined(OS_MACOSX) - MacKeyEventProcessor mac_key_event_processor_; -#endif KeyEventMapper key_mapper_; + scoped_ptr<protocol::InputFilter> normalizing_input_filter_; PepperInputHandler input_handler_; // PIN Fetcher. diff --git a/remoting/client/plugin/mac_key_event_processor.h b/remoting/client/plugin/mac_key_event_processor.h deleted file mode 100644 index 56bc974..0000000 --- a/remoting/client/plugin/mac_key_event_processor.h +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2012 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. - -// MacKeyEventProcessor is designed to solve the problem of missing keyup -// events on Mac. -// -// PROBLEM -// -// On Mac if user presses CMD and then C key there is no keyup event generated -// for C when user releases the C key before the CMD key. -// The cause is that CMD + C triggers a system action and Chrome injects only a -// keydown event for the C key. Safari shares the same behavior. -// -// SOLUTION -// -// When a keyup event for CMD key happens we will check all prior keydown -// events received and inject corresponding keyup events artificially, with -// the exception of: -// -// SHIFT, CONTROL, OPTION, LEFT CMD, RIGHT CMD and CAPS LOCK -// -// because they are reported by Chrome correctly. -// -// There are a couple cases that this solution doesn't work perfectly, one -// of them leads to duplicated keyup events. -// -// User performs this sequence of actions: -// -// CMD DOWN, C DOWN, CMD UP, C UP -// -// In this case the algorithm will generate: -// -// CMD DOWN, C DOWN, C UP, CMD UP, C UP -// -// Because we artificially generate keyup events the C UP event is duplicated -// as user releases the key after CMD key. This would not be a problem as the -// receiver end will drop this duplicated keyup event. - -#ifndef REMOTING_CLIENT_PLUGIN_MAC_KEY_EVENT_PROCESSOR_H_ -#define REMOTING_CLIENT_PLUGIN_MAC_KEY_EVENT_PROCESSOR_H_ - -#include <map> - -#include "base/basictypes.h" -#include "remoting/proto/event.pb.h" -#include "remoting/protocol/input_filter.h" - -namespace remoting { - -namespace protocol { -class InputStub; -} // namespace protocol - -class MacKeyEventProcessor : public protocol::InputFilter { - public: - explicit MacKeyEventProcessor(protocol::InputStub* input_stub); - virtual ~MacKeyEventProcessor(); - - // InputFilter overrides. - virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE; - - private: - // Generate keyup events for any keys pressed with CMD. - void GenerateKeyupEvents(); - - // A map that stores pressed keycodes and the corresponding key event. - typedef std::map<int, protocol::KeyEvent> KeyPressedMap; - KeyPressedMap key_pressed_map_; - - DISALLOW_COPY_AND_ASSIGN(MacKeyEventProcessor); -}; - -} // namespace remoting - -#endif // REMOTING_CLIENT_PLUGIN_MAC_KEY_EVENT_PROCESSOR_H_ diff --git a/remoting/client/plugin/normalizing_input_filter.cc b/remoting/client/plugin/normalizing_input_filter.cc new file mode 100644 index 0000000..7206305 --- /dev/null +++ b/remoting/client/plugin/normalizing_input_filter.cc @@ -0,0 +1,20 @@ +// Copyright 2013 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/plugin/normalizing_input_filter.h" + +#include "remoting/protocol/input_filter.h" + +namespace remoting { + +using protocol::InputFilter; +using protocol::InputStub; + +#if !defined(OS_MACOSX) && !defined(OS_CHROMEOS) +scoped_ptr<InputFilter> CreateNormalizingInputFilter(InputStub* input_stub) { + return scoped_ptr<InputFilter>(new InputFilter(input_stub)); +} +#endif // !defined(OS_MACOSX) && !defined(OS_CHROMEOS) + +} diff --git a/remoting/client/plugin/normalizing_input_filter.h b/remoting/client/plugin/normalizing_input_filter.h new file mode 100644 index 0000000..ebe49fe --- /dev/null +++ b/remoting/client/plugin/normalizing_input_filter.h @@ -0,0 +1,21 @@ +// Copyright 2013 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. + +#ifndef REMOTING_CLIENT_PLUGIN_NORMALIZING_INPUT_FILTER_H_ +#define REMOTING_CLIENT_PLUGIN_NORMALIZING_INPUT_FILTER_H_ + +#include "base/memory/scoped_ptr.h" +#include "remoting/protocol/input_filter.h" + +namespace remoting { + +// Returns an InputFilter which re-writes input events to work around +// platform-specific behaviours. If no re-writing is required then a +// pass-through InputFilter is returned. +scoped_ptr<protocol::InputFilter> CreateNormalizingInputFilter( + protocol::InputStub* input_stub); + +} // namespace remoting + +#endif // REMOTING_CLIENT_PLUGIN_NORMALIZING_INPUT_FILTER_H_ diff --git a/remoting/client/plugin/normalizing_input_filter_cros.cc b/remoting/client/plugin/normalizing_input_filter_cros.cc new file mode 100644 index 0000000..81cec8d --- /dev/null +++ b/remoting/client/plugin/normalizing_input_filter_cros.cc @@ -0,0 +1,206 @@ +// Copyright 2013 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. + +// NormalizingInputFilterCros addresses the problems generated by key rewritings +// such as Down->PageDown, 1->F1, etc, when keys are pressed in combination with +// the OSKey (aka Search). Rewriting OSKey+Down, for example, causes us to +// receive the following: +// +// keydown OSKey +// keydown PageDown +// keyup PageDown +// keyup OSKey +// +// The host system will therefore behave as if OSKey+PageDown were pressed, +// rather than PageDown alone. +// +// This file must be kept up-to-date with changes to +// chrome/browser/ui/ash/event_rewriter.cc + +#include "remoting/client/plugin/normalizing_input_filter.h" + +#include "base/logging.h" +#include "remoting/proto/event.pb.h" + +namespace remoting { + +namespace { + +// Returns true for OSKey codes. +static bool IsOsKey(unsigned int code) { + const unsigned int kUsbLeftOsKey = 0x0700e3; + const unsigned int kUsbRightOsKey = 0x0700e7; + return code == kUsbLeftOsKey || code == kUsbRightOsKey; +} + +// Returns true for codes generated by EventRewriter::RewriteFunctionKeys(). +static bool IsRewrittenFunctionKey(unsigned int code) { + const unsigned int kUsbFunctionKeyMin = 0x07003a; + const unsigned int kUsbFunctionKeyMax = 0x070045; + return code >= kUsbFunctionKeyMin && code <= kUsbFunctionKeyMax; +} + +// Returns true for codes generated by EventRewriter::RewriteExtendedKeys(). +static bool IsRewrittenExtendedKey(unsigned int code) { + const unsigned int kUsbExtendedKeyMin = 0x070049; + const unsigned int kUsbExtendedKeyMax = 0x07004e; + return code >= kUsbExtendedKeyMin && code <= kUsbExtendedKeyMax; +} + +// Returns true for codes generated by EventRewriter::Rewrite(). +static bool IsRewrittenKey(unsigned int code) { + return IsRewrittenExtendedKey(code) || IsRewrittenFunctionKey(code); +} + +// 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. + +class NormalizingInputFilterCros : public protocol::InputFilter { + public: + explicit NormalizingInputFilterCros(protocol::InputStub* input_stub) + : protocol::InputFilter(input_stub), + deferred_key_is_rewriting_(false), + modifying_key_(0) { + } + virtual ~NormalizingInputFilterCros() {} + + // InputFilter overrides. + virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE { + DCHECK(event.has_usb_keycode()); + DCHECK(event.has_pressed()); + + if (event.pressed()) + ProcessKeyDown(event); + else + ProcessKeyUp(event); + } + + virtual void InjectMouseEvent(const protocol::MouseEvent& event) OVERRIDE { + if (deferred_keydown_event_.has_usb_keycode()) + SwitchRewritingKeyToModifying(); + InputFilter::InjectMouseEvent(event); + } + + private: + void ProcessKeyDown(const protocol::KeyEvent& event) { + // If |event| is |deferred_keydown_event_| auto-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(); + } + + InputFilter::InjectKeyEvent(event); + } + + void 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; + + InputFilter::InjectKeyEvent(event); + } + + void 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(); + } + + // Holds the keydown event for the most recent OSKey to have been pressed, + // while it is Rewriting, or we are not yet sure whether it is Normal, + // Rewriting or Modifying. The event is sent on if we switch to Modifying, or + // discarded if the OSKey is released while in Rewriting mode. + protocol::KeyEvent deferred_keydown_event_; + + // True while the |rewrite_keydown_event_| key is Rewriting, i.e. was followed + // by one or more Rewritten key events, and not by any Modified events. + bool deferred_key_is_rewriting_; + + // Stores the code of the OSKey while it is pressed for use as a Modifier. + uint32 modifying_key_; + + DISALLOW_COPY_AND_ASSIGN(NormalizingInputFilterCros); +}; + +} // namespace + +scoped_ptr<protocol::InputFilter> CreateNormalizingInputFilter( + protocol::InputStub* input_stub) { + return scoped_ptr<protocol::InputFilter>( + new NormalizingInputFilterCros(input_stub)); +} + +} // namespace remoting diff --git a/remoting/client/plugin/normalizing_input_filter_cros_unittest.cc b/remoting/client/plugin/normalizing_input_filter_cros_unittest.cc new file mode 100644 index 0000000..cf46d16 --- /dev/null +++ b/remoting/client/plugin/normalizing_input_filter_cros_unittest.cc @@ -0,0 +1,212 @@ +// Copyright 2013 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/plugin/normalizing_input_filter.h" +#include "remoting/proto/event.pb.h" +#include "remoting/protocol/protocol_mock_objects.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::InSequence; +using remoting::protocol::InputStub; +using remoting::protocol::KeyEvent; +using remoting::protocol::MockInputStub; +using remoting::protocol::MouseEvent; + +namespace remoting { + +namespace { + +const unsigned int kUsbLeftOsKey = 0x0700e3; +const unsigned int kUsbRightOsKey = 0x0700e7; + +const unsigned int kUsbFunctionKey = 0x07003a; // F1 +const unsigned int kUsbExtendedKey = 0x070049; // Insert +const unsigned int kUsbOtherKey = 0x07002b; // Tab + +// A hardcoded value used to verify |lock_states| is preserved. +static const uint32 kTestLockStates = protocol::KeyEvent::LOCK_STATES_NUMLOCK; + +MATCHER_P2(EqualsKeyEvent, usb_keycode, pressed, "") { + return arg.usb_keycode() == static_cast<uint32>(usb_keycode) && + arg.pressed() == pressed && + arg.lock_states() == kTestLockStates; +} + +KeyEvent MakeKeyEvent(uint32 keycode, bool pressed) { + KeyEvent event; + event.set_usb_keycode(keycode); + event.set_pressed(pressed); + event.set_lock_states(kTestLockStates); + return event; +} + +void PressAndReleaseKey(InputStub* input_stub, uint32 keycode) { + input_stub->InjectKeyEvent(MakeKeyEvent(keycode, true)); + input_stub->InjectKeyEvent(MakeKeyEvent(keycode, false)); +} + +MATCHER_P2(EqualsMouseMoveEvent, x, y, "") { + return arg.x() == x && arg.y() == y; +} + +static MouseEvent MakeMouseMoveEvent(int x, int y) { + MouseEvent event; + event.set_x(x); + event.set_y(y); + return event; +} + +} // namespace + +// Test OSKey press/release. +TEST(NormalizingInputFilterCrosTest, PressReleaseOsKey) { + MockInputStub stub; + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); + + { + InSequence s; + + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, false))); + + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbRightOsKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbRightOsKey, false))); + } + + // Inject press & release events for left & right OSKeys. + PressAndReleaseKey(processor.get(), kUsbLeftOsKey); + PressAndReleaseKey(processor.get(), kUsbRightOsKey); +} + +// Test OSKey key repeat switches it to "modifying" mode. +TEST(NormalizingInputFilterCrosTest, OSKeyRepeats) { + MockInputStub stub; + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); + + { + InSequence s; + + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, true))); + } + + // Inject a press and repeats for the left OSKey, but don't release it, and + // verify that the repeats result in press events. + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, true)); +} + +// Test OSKey press followed by function key press and release results in +// just the function key events. +TEST(NormalizingInputFilterCrosTest, FunctionKey) { + MockInputStub stub; + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); + + { + InSequence s; + + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbFunctionKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbFunctionKey, false))); + } + + // Hold the left OSKey while pressing & releasing the function key. + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, true)); + PressAndReleaseKey(processor.get(), kUsbFunctionKey); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, false)); +} + +// Test OSKey press followed by extended key press and release results in +// just the function key events. +TEST(NormalizingInputFilterCrosTest, ExtendedKey) { + MockInputStub stub; + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); + + { + InSequence s; + + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbExtendedKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbExtendedKey, false))); + } + + // Hold the left OSKey while pressing & releasing the function key. + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, true)); + PressAndReleaseKey(processor.get(), kUsbExtendedKey); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, false)); +} + +// Test OSKey press followed by non-function, non-extended key press and release +// results in normal-looking sequence. +TEST(NormalizingInputFilterCrosTest, OtherKey) { + MockInputStub stub; + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); + + { + InSequence s; + + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbOtherKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbOtherKey, false))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, false))); + } + + // Hold the left OSKey while pressing & releasing the function key. + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, true)); + PressAndReleaseKey(processor.get(), kUsbOtherKey); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, false)); +} + +// Test OSKey press followed by extended key press, then normal key press +// results in OSKey switching to modifying mode for the normal key. +TEST(NormalizingInputFilterCrosTest, ExtendedThenOtherKey) { + MockInputStub stub; + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); + + { + InSequence s; + + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbExtendedKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbExtendedKey, false))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbOtherKey, true))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbOtherKey, false))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, false))); + } + + // Hold the left OSKey while pressing & releasing the function key. + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, true)); + PressAndReleaseKey(processor.get(), kUsbExtendedKey); + PressAndReleaseKey(processor.get(), kUsbOtherKey); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, false)); +} + +// Test OSKey press followed by mouse event puts the OSKey into modifying mode. +TEST(NormalizingInputFilterCrosTest, MouseEvent) { + MockInputStub stub; + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); + + { + InSequence s; + + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, true))); + EXPECT_CALL(stub, InjectMouseEvent(EqualsMouseMoveEvent(0, 0))); + EXPECT_CALL(stub, InjectKeyEvent(EqualsKeyEvent(kUsbLeftOsKey, false))); + } + + // Hold the left OSKey while pressing & releasing the function key. + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, true)); + processor->InjectMouseEvent(MakeMouseMoveEvent(0, 0)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOsKey, false)); +} + +} // namespace remoting diff --git a/remoting/client/plugin/mac_key_event_processor.cc b/remoting/client/plugin/normalizing_input_filter_mac.cc index 175924bf..56b327e 100644 --- a/remoting/client/plugin/mac_key_event_processor.cc +++ b/remoting/client/plugin/normalizing_input_filter_mac.cc @@ -1,12 +1,49 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright 2013 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/plugin/mac_key_event_processor.h" - +// NormalizingInputFilterMac is designed to solve the problem of missing keyup +// events on Mac. +// +// PROBLEM +// +// On Mac if user presses CMD and then C key there is no keyup event generated +// for C when user releases the C key before the CMD key. +// The cause is that CMD + C triggers a system action and Chrome injects only a +// keydown event for the C key. Safari shares the same behavior. +// +// SOLUTION +// +// When a keyup event for CMD key happens we will check all prior keydown +// events received and inject corresponding keyup events artificially, with +// the exception of: +// +// SHIFT, CONTROL, OPTION, LEFT CMD, RIGHT CMD and CAPS LOCK +// +// because they are reported by Chrome correctly. +// +// There are a couple cases that this solution doesn't work perfectly, one +// of them leads to duplicated keyup events. +// +// User performs this sequence of actions: +// +// CMD DOWN, C DOWN, CMD UP, C UP +// +// In this case the algorithm will generate: +// +// CMD DOWN, C DOWN, C UP, CMD UP, C UP +// +// Because we artificially generate keyup events the C UP event is duplicated +// as user releases the key after CMD key. This would not be a problem as the +// receiver end will drop this duplicated keyup event. + +#include "remoting/client/plugin/normalizing_input_filter.h" + +#include <map> #include <vector> #include "base/logging.h" +#include "remoting/proto/event.pb.h" namespace remoting { @@ -25,14 +62,32 @@ const unsigned int kUsbTab = 0x07002b; } // namespace -MacKeyEventProcessor::MacKeyEventProcessor(protocol::InputStub* input_stub) - : protocol::InputFilter(input_stub) { -} +class NormalizingInputFilterMac : public protocol::InputFilter { + public: + explicit NormalizingInputFilterMac(protocol::InputStub* input_stub); + virtual ~NormalizingInputFilterMac() {} + + // InputFilter overrides. + virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE; + + private: + // Generate keyup events for any keys pressed with CMD. + void GenerateKeyupEvents(); -MacKeyEventProcessor::~MacKeyEventProcessor() { + // A map that stores pressed keycodes and the corresponding key event. + typedef std::map<int, protocol::KeyEvent> KeyPressedMap; + KeyPressedMap key_pressed_map_; + + DISALLOW_COPY_AND_ASSIGN(NormalizingInputFilterMac); +}; + +NormalizingInputFilterMac::NormalizingInputFilterMac( + protocol::InputStub* input_stub) + : protocol::InputFilter(input_stub) { } -void MacKeyEventProcessor::InjectKeyEvent(const protocol::KeyEvent& event) { +void NormalizingInputFilterMac::InjectKeyEvent(const protocol::KeyEvent& event) +{ DCHECK(event.has_usb_keycode()); bool is_special_key = event.usb_keycode() == kUsbLeftControl || @@ -76,7 +131,7 @@ void MacKeyEventProcessor::InjectKeyEvent(const protocol::KeyEvent& event) { InputFilter::InjectKeyEvent(event); } -void MacKeyEventProcessor::GenerateKeyupEvents() { +void NormalizingInputFilterMac::GenerateKeyupEvents() { for (KeyPressedMap::iterator i = key_pressed_map_.begin(); i != key_pressed_map_.end(); ++i) { // The generated key up event will have the same key code and lock states @@ -90,4 +145,10 @@ void MacKeyEventProcessor::GenerateKeyupEvents() { key_pressed_map_.clear(); } +scoped_ptr<protocol::InputFilter> CreateNormalizingInputFilter( + protocol::InputStub* input_stub) { + return scoped_ptr<protocol::InputFilter>( + new NormalizingInputFilterMac(input_stub)); +} + } // namespace remoting diff --git a/remoting/client/plugin/mac_key_event_processor_unittest.cc b/remoting/client/plugin/normalizing_input_filter_mac_unittest.cc index a2604d5..8221223 100644 --- a/remoting/client/plugin/mac_key_event_processor_unittest.cc +++ b/remoting/client/plugin/normalizing_input_filter_mac_unittest.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright 2013 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/plugin/mac_key_event_processor.h" +#include "remoting/client/plugin/normalizing_input_filter.h" #include "remoting/proto/event.pb.h" #include "remoting/protocol/protocol_mock_objects.h" #include "testing/gmock/include/gmock/gmock.h" @@ -44,9 +44,10 @@ KeyEvent MakeKeyEvent(uint32 keycode, bool pressed) { } // namespace // Test CapsLock press/release. -TEST(MacKeyEventProcessorTest, CapsLock) { +TEST(NormalizingInputFilterMacTest, CapsLock) { MockInputStub stub; - MacKeyEventProcessor processor(&stub); + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); { InSequence s; @@ -57,13 +58,14 @@ TEST(MacKeyEventProcessorTest, CapsLock) { } // Injecting a CapsLock down event with NumLock on. - processor.InjectKeyEvent(MakeKeyEvent(kUsbCapsLock, true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbCapsLock, true)); } // Test without pressing command key. -TEST(MacKeyEventProcessorTest, NoInjection) { +TEST(NormalizingInputFilterMacTest, NoInjection) { MockInputStub stub; - MacKeyEventProcessor processor(&stub); + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); { InSequence s; @@ -75,14 +77,15 @@ TEST(MacKeyEventProcessorTest, NoInjection) { } // C Down and C Up. - processor.InjectKeyEvent(MakeKeyEvent('C', true)); - processor.InjectKeyEvent(MakeKeyEvent('C', false)); + processor->InjectKeyEvent(MakeKeyEvent('C', true)); + processor->InjectKeyEvent(MakeKeyEvent('C', false)); } // Test pressing command key and other normal keys. -TEST(MacKeyEventProcessorTest, CmdKey) { +TEST(NormalizingInputFilterMacTest, CmdKey) { MockInputStub stub; - MacKeyEventProcessor processor(&stub); + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); { InSequence s; @@ -123,26 +126,27 @@ TEST(MacKeyEventProcessorTest, CmdKey) { } // Left command key. - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, true)); - processor.InjectKeyEvent(MakeKeyEvent('C', true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, false)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, true)); + processor->InjectKeyEvent(MakeKeyEvent('C', true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, false)); // Right command key. - processor.InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, true)); - processor.InjectKeyEvent(MakeKeyEvent('C', true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, false)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, true)); + processor->InjectKeyEvent(MakeKeyEvent('C', true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, false)); // More than one keys after CMD. - processor.InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, true)); - processor.InjectKeyEvent(MakeKeyEvent('C', true)); - processor.InjectKeyEvent(MakeKeyEvent('V', true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, false)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, true)); + processor->InjectKeyEvent(MakeKeyEvent('C', true)); + processor->InjectKeyEvent(MakeKeyEvent('V', true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, false)); } // Test pressing command and special keys. -TEST(MacKeyEventProcessorTest, SpecialKeys) { +TEST(NormalizingInputFilterMacTest, SpecialKeys) { MockInputStub stub; - MacKeyEventProcessor processor(&stub); + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); { InSequence s; @@ -169,22 +173,23 @@ TEST(MacKeyEventProcessorTest, SpecialKeys) { } // Command + Shift. - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftShift, true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, false)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftShift, false)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftShift, true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, false)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftShift, false)); // Command + Option. - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftOption, true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, false)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftOption, false)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOption, true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, false)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftOption, false)); } // Test pressing multiple command keys. -TEST(MacKeyEventProcessorTest, MultipleCmdKeys) { +TEST(NormalizingInputFilterMacTest, MultipleCmdKeys) { MockInputStub stub; - MacKeyEventProcessor processor(&stub); + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); { InSequence s; @@ -203,16 +208,17 @@ TEST(MacKeyEventProcessorTest, MultipleCmdKeys) { // Test multiple CMD keys at the same time. // L CMD Down, C Down, R CMD Down, L CMD Up. - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, true)); - processor.InjectKeyEvent(MakeKeyEvent('C', true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, false)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, true)); + processor->InjectKeyEvent(MakeKeyEvent('C', true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbLeftCmd, false)); } // Test press C key before command key. -TEST(MacKeyEventProcessorTest, BeforeCmdKey) { +TEST(NormalizingInputFilterMacTest, BeforeCmdKey) { MockInputStub stub; - MacKeyEventProcessor processor(&stub); + scoped_ptr<protocol::InputFilter> processor = + CreateNormalizingInputFilter(&stub); { InSequence s; @@ -230,10 +236,10 @@ TEST(MacKeyEventProcessorTest, BeforeCmdKey) { } // Press C before command key. - processor.InjectKeyEvent(MakeKeyEvent('C', true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, true)); - processor.InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, false)); - processor.InjectKeyEvent(MakeKeyEvent('C', false)); + processor->InjectKeyEvent(MakeKeyEvent('C', true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, true)); + processor->InjectKeyEvent(MakeKeyEvent(kUsbRightCmd, false)); + processor->InjectKeyEvent(MakeKeyEvent('C', false)); } } // namespace remoting |