diff options
author | wez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-09 02:22:23 +0000 |
---|---|---|
committer | wez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-04-09 02:22:23 +0000 |
commit | cb9bcfb3f87954a288a70dd1a0a90b74d0b01b56 (patch) | |
tree | 1aecdc1ecd62038e2f665cf491c10dd9e6afadb4 /remoting | |
parent | 26682cacab98c010ae458d808146f24862407b7b (diff) | |
download | chromium_src-cb9bcfb3f87954a288a70dd1a0a90b74d0b01b56.zip chromium_src-cb9bcfb3f87954a288a70dd1a0a90b74d0b01b56.tar.gz chromium_src-cb9bcfb3f87954a288a70dd1a0a90b74d0b01b56.tar.bz2 |
Add APIs to the client plugin to re-map and trap key events.
Re-mapped events allow keys to be re-mapped by the plugin before being delivered to the host.
Trapped events are posted to the web-app in a trappedKeyEvent message, allowing more complex processing to be performed, at the cost of higher input latency.
BUG=121787
Review URL: http://codereview.chromium.org/10025001
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@131321 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/client/key_event_mapper.cc | 63 | ||||
-rw-r--r-- | remoting/client/key_event_mapper.h | 54 | ||||
-rw-r--r-- | remoting/client/key_event_mapper_unittest.cc | 153 | ||||
-rw-r--r-- | remoting/client/plugin/chromoting_instance.cc | 42 | ||||
-rw-r--r-- | remoting/client/plugin/chromoting_instance.h | 7 | ||||
-rw-r--r-- | remoting/protocol/input_filter.cc | 3 | ||||
-rw-r--r-- | remoting/protocol/input_filter.h | 1 | ||||
-rw-r--r-- | remoting/remoting.gyp | 3 |
8 files changed, 324 insertions, 2 deletions
diff --git a/remoting/client/key_event_mapper.cc b/remoting/client/key_event_mapper.cc new file mode 100644 index 0000000..742feca --- /dev/null +++ b/remoting/client/key_event_mapper.cc @@ -0,0 +1,63 @@ +// 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. + +#include "remoting/client/key_event_mapper.h" + +#include "remoting/proto/event.pb.h" + +namespace remoting { + +KeyEventMapper::KeyEventMapper() { +} + +KeyEventMapper::KeyEventMapper(InputStub* stub) : protocol::InputFilter(stub) { +} + +KeyEventMapper::~KeyEventMapper() { +} + +void KeyEventMapper::SetTrapCallback(KeyTrapCallback callback) { + trap_callback = callback; +} + +void KeyEventMapper::TrapKey(uint32 usb_keycode, bool trap_key) { + if (trap_key) { + trapped_keys.insert(usb_keycode); + } else { + trapped_keys.erase(usb_keycode); + } +} + +void KeyEventMapper::RemapKey(uint32 in_usb_keycode, uint32 out_usb_keycode) { + if (in_usb_keycode == out_usb_keycode) { + mapped_keys.erase(in_usb_keycode); + } else { + mapped_keys[in_usb_keycode] = out_usb_keycode; + } +} + +void KeyEventMapper::InjectKeyEvent(const protocol::KeyEvent& event) { + if (event.has_usb_keycode()) { + // Deliver trapped keys to the callback, not the next stub. + if (!trap_callback.is_null() && event.has_pressed() && + (trapped_keys.find(event.usb_keycode()) != trapped_keys.end())) { + trap_callback.Run(event.usb_keycode(), event.pressed()); + return; + } + + // Re-map mapped keys to the new value before passing them on. + std::map<uint32,uint32>::iterator mapped = + mapped_keys.find(event.usb_keycode()); + if (mapped != mapped_keys.end()) { + protocol::KeyEvent new_event(event); + new_event.set_usb_keycode(mapped->second); + InputFilter::InjectKeyEvent(new_event); + return; + } + } + + InputFilter::InjectKeyEvent(event); +} + +} // namespace remoting diff --git a/remoting/client/key_event_mapper.h b/remoting/client/key_event_mapper.h new file mode 100644 index 0000000..c48e089 --- /dev/null +++ b/remoting/client/key_event_mapper.h @@ -0,0 +1,54 @@ +// 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. + +#ifndef REMOTING_CLIENT_KEY_EVENT_MAPPER_H_ +#define REMOTING_CLIENT_KEY_EVENT_MAPPER_H_ + +#include <map> +#include <set> + +#include "base/basictypes.h" +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "remoting/protocol/input_filter.h" + +namespace remoting { + +// Filtering InputStub which can be used to re-map the USB keycodes of events +// before they are passed on to the next InputStub in the chain, or to trap +// events with specific USB keycodes for special handling. +class KeyEventMapper : public protocol::InputFilter { + public: + KeyEventMapper(); + explicit KeyEventMapper(InputStub* input_stub); + virtual ~KeyEventMapper(); + + // Callback type for use with SetTrapCallback(), below. + typedef base::Callback<void(uint32, bool)> KeyTrapCallback; + + // Sets the callback to which trapped keys will be delivered. + void SetTrapCallback(KeyTrapCallback callback); + + // Causes events matching |usb_keycode| to be delivered to the trap callback. + // Trapped events are not dispatched to the next InputStub in the chain. + void TrapKey(uint32 usb_keycode, bool trap_key); + + // Causes events matching |in_usb_keycode| to be mapped to |out_usb_keycode|. + // Keys are remapped at most once. Traps are processed before remapping. + void RemapKey(uint32 in_usb_keycode, uint32 out_usb_keycode); + + // InputFilter overrides. + virtual void InjectKeyEvent(const protocol::KeyEvent& event) OVERRIDE; + + private: + std::map<uint32,uint32> mapped_keys; + std::set<uint32> trapped_keys; + KeyTrapCallback trap_callback; + + DISALLOW_COPY_AND_ASSIGN(KeyEventMapper); +}; + +} // namespace remoting + +#endif // REMOTING_CLIENT_KEY_EVENT_MAPPER_H_ diff --git a/remoting/client/key_event_mapper_unittest.cc b/remoting/client/key_event_mapper_unittest.cc new file mode 100644 index 0000000..f620dfa --- /dev/null +++ b/remoting/client/key_event_mapper_unittest.cc @@ -0,0 +1,153 @@ +// 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. + +#include "remoting/client/key_event_mapper.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::_; +using ::testing::ExpectationSet; +using ::testing::InSequence; + +namespace remoting { + +using protocol::InputStub; +using protocol::KeyEvent; +using protocol::MockInputStub; + +MATCHER_P2(EqualsVkeyEvent, keycode, pressed, "") { + return arg.keycode() == keycode && arg.pressed() == pressed; +} + +MATCHER_P2(EqualsUsbEvent, usb_keycode, pressed, "") { + return arg.usb_keycode() == static_cast<uint32>(usb_keycode) && + arg.pressed() == pressed; +} + +static KeyEvent NewVkeyEvent(int keycode, bool pressed) { + KeyEvent event; + event.set_keycode(keycode); + event.set_pressed(pressed); + return event; +} + +static void PressAndReleaseVkey(InputStub* input_stub, int keycode) { + input_stub->InjectKeyEvent(NewVkeyEvent(keycode, true)); + input_stub->InjectKeyEvent(NewVkeyEvent(keycode, false)); +} + +static KeyEvent NewUsbEvent(uint32 usb_keycode, bool pressed) { + KeyEvent event; + event.set_usb_keycode(usb_keycode); + event.set_pressed(pressed); + return event; +} + +static void PressAndReleaseUsb(InputStub* input_stub, + uint32 usb_keycode) { + input_stub->InjectKeyEvent(NewUsbEvent(usb_keycode, true)); + input_stub->InjectKeyEvent(NewUsbEvent(usb_keycode, false)); +} + +static void InjectTestSequence(InputStub* input_stub) { + for (int i = 1; i <= 5; ++i) + PressAndReleaseUsb(input_stub, i); +} + +// Verify that keys are passed through the KeyEventMapper by default. +TEST(KeyEventMapperTest, NoMappingOrTrapping) { + MockInputStub mock_stub; + KeyEventMapper event_mapper(&mock_stub); + + { + InSequence s; + + for (int i = 1; i <= 5; ++i) { + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(i, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(i, false))); + } + + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsVkeyEvent(3, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsVkeyEvent(3, false))); + } + + InjectTestSequence(&event_mapper); + PressAndReleaseVkey(&event_mapper, 3); +} + +// Verify that USB keys are remapped at most once, and VKEYs are not mapped. +TEST(KeyEventMapperTest, RemapKeys) { + MockInputStub mock_stub; + KeyEventMapper event_mapper(&mock_stub); + event_mapper.RemapKey(3, 4); + event_mapper.RemapKey(4, 3); + event_mapper.RemapKey(5, 3); + + { + InSequence s; + + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(1, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(1, false))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(2, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(2, false))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(4, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(4, false))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(3, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(3, false))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(3, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(3, false))); + + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsVkeyEvent(3, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsVkeyEvent(3, false))); + } + + InjectTestSequence(&event_mapper); + PressAndReleaseVkey(&event_mapper, 3); +} + +static void HandleTrappedKey(MockInputStub* stub, uint32 keycode, bool down) { + stub->InjectKeyEvent(NewUsbEvent(keycode, down)); +} + +// Verify that USB keys are trapped, not remapped, and VKEYs are not trapped. +TEST(KeyEventMapperTest, TrapKeys) { + MockInputStub mock_stub; + MockInputStub trap_stub; + KeyEventMapper event_mapper(&mock_stub); + KeyEventMapper::KeyTrapCallback callback = + base::Bind(&HandleTrappedKey, base::Unretained(&trap_stub)); + event_mapper.SetTrapCallback(callback); + event_mapper.TrapKey(4, true); + event_mapper.TrapKey(5, true); + event_mapper.RemapKey(3, 4); + event_mapper.RemapKey(4, 3); + event_mapper.RemapKey(5, 3); + + { + InSequence s; + + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(1, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(1, false))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(2, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(2, false))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(4, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsUsbEvent(4, false))); + + EXPECT_CALL(trap_stub, InjectKeyEvent(EqualsUsbEvent(4, true))); + EXPECT_CALL(trap_stub, InjectKeyEvent(EqualsUsbEvent(4, false))); + EXPECT_CALL(trap_stub, InjectKeyEvent(EqualsUsbEvent(5, true))); + EXPECT_CALL(trap_stub, InjectKeyEvent(EqualsUsbEvent(5, false))); + + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsVkeyEvent(3, true))); + EXPECT_CALL(mock_stub, InjectKeyEvent(EqualsVkeyEvent(3, false))); + } + + InjectTestSequence(&event_mapper); + PressAndReleaseVkey(&event_mapper, 3); +} + +} // namespace remoting diff --git a/remoting/client/plugin/chromoting_instance.cc b/remoting/client/plugin/chromoting_instance.cc index b88761d..c2f4585 100644 --- a/remoting/client/plugin/chromoting_instance.cc +++ b/remoting/client/plugin/chromoting_instance.cc @@ -102,7 +102,7 @@ static base::LazyInstance<base::Lock>::Leaky // String sent in the "hello" message to the plugin to describe features. const char ChromotingInstance::kApiFeatures[] = - "highQualityScaling injectKeyEvent sendClipboardItem"; + "highQualityScaling injectKeyEvent sendClipboardItem remapKey trapKey"; bool ChromotingInstance::ParseAuthMethods(const std::string& auth_methods_str, ClientConfig* config) { @@ -269,6 +269,26 @@ void ChromotingInstance::HandleMessage(const pp::Var& message) { // Even though new hosts will ignore keycode, it's a required field. event.set_keycode(0); InjectKeyEvent(event); + } else if (method == "remapKey") { + int from_keycode = 0; + int to_keycode = 0; + if (!data->GetInteger("fromKeycode", &from_keycode) || + !data->GetInteger("toKeycode", &to_keycode)) { + LOG(ERROR) << "Invalid remapKey."; + return; + } + + RemapKey(from_keycode, to_keycode); + } else if (method == "trapKey") { + int keycode = 0; + bool trap = false; + if (!data->GetInteger("keycode", &keycode) || + !data->GetBoolean("trap", &trap)) { + LOG(ERROR) << "Invalid trapKey."; + return; + } + + TrapKey(keycode, trap); } else if (method == "sendClipboardItem") { std::string mime_type; std::string item; @@ -370,8 +390,9 @@ void ChromotingInstance::Connect(const ClientConfig& config) { mouse_input_filter_->set_input_size(view_->get_view_size()); input_tracker_.reset( new protocol::InputEventTracker(mouse_input_filter_.get())); + key_mapper_.set_input_stub(input_tracker_.get()); input_handler_.reset( - new PepperInputHandler(input_tracker_.get())); + new PepperInputHandler(&key_mapper_)); LOG(INFO) << "Connecting to " << config.host_jid << ". Local jid: " << config.local_jid << "."; @@ -425,10 +446,20 @@ void ChromotingInstance::ReleaseAllKeys() { } void ChromotingInstance::InjectKeyEvent(const protocol::KeyEvent& event) { + // Inject after the KeyEventMapper, so the event won't get mapped or trapped. if (input_tracker_.get()) input_tracker_->InjectKeyEvent(event); } +void ChromotingInstance::RemapKey(uint32 in_usb_keycode, + uint32 out_usb_keycode) { + key_mapper_.RemapKey(in_usb_keycode, out_usb_keycode); +} + +void ChromotingInstance::TrapKey(uint32 usb_keycode, bool trap) { + key_mapper_.TrapKey(usb_keycode, trap); +} + void ChromotingInstance::SendClipboardItem(const std::string& mime_type, const std::string& item) { if (!host_connection_.get()) { @@ -458,6 +489,13 @@ void ChromotingInstance::PostChromotingMessage( PostMessage(pp::Var(message_json)); } +void ChromotingInstance::SendTrappedKey(uint32 usb_keycode, bool pressed) { + scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); + data->SetInteger("usbKeycode", usb_keycode); + data->SetBoolean("pressed", pressed); + PostChromotingMessage("trappedKeyEvent", data.Pass()); +} + void ChromotingInstance::SendOutgoingIq(const std::string& iq) { scoped_ptr<base::DictionaryValue> data(new base::DictionaryValue()); data->SetString("iq", iq); diff --git a/remoting/client/plugin/chromoting_instance.h b/remoting/client/plugin/chromoting_instance.h index 5e36fc8..7a58039 100644 --- a/remoting/client/plugin/chromoting_instance.h +++ b/remoting/client/plugin/chromoting_instance.h @@ -27,6 +27,7 @@ #include "ppapi/cpp/private/instance_private.h" #include "remoting/base/scoped_thread_proxy.h" #include "remoting/client/client_context.h" +#include "remoting/client/key_event_mapper.h" #include "remoting/client/plugin/pepper_plugin_thread_delegate.h" #include "remoting/proto/event.pb.h" #include "remoting/protocol/clipboard_stub.h" @@ -141,6 +142,8 @@ class ChromotingInstance : void OnIncomingIq(const std::string& iq); void ReleaseAllKeys(); void InjectKeyEvent(const protocol::KeyEvent& event); + void RemapKey(uint32 in_usb_keycode, uint32 out_usb_keycode); + void TrapKey(uint32 usb_keycode, bool trap); void SendClipboardItem(const std::string& mime_type, const std::string& item); // Return statistics record by ChromotingClient. @@ -176,6 +179,9 @@ class ChromotingInstance : void PostChromotingMessage(const std::string& method, scoped_ptr<base::DictionaryValue> data); + // Posts trapped keys to the web-app to handle. + void SendTrappedKey(uint32 usb_keycode, bool pressed); + // Callback for PepperXmppProxy. void SendOutgoingIq(const std::string& iq); @@ -194,6 +200,7 @@ class ChromotingInstance : scoped_refptr<RectangleUpdateDecoder> rectangle_decoder_; scoped_ptr<MouseInputFilter> mouse_input_filter_; scoped_ptr<protocol::InputEventTracker> input_tracker_; + KeyEventMapper key_mapper_; scoped_ptr<PepperInputHandler> input_handler_; scoped_ptr<ChromotingClient> client_; diff --git a/remoting/protocol/input_filter.cc b/remoting/protocol/input_filter.cc index c9be6a11..d749391 100644 --- a/remoting/protocol/input_filter.cc +++ b/remoting/protocol/input_filter.cc @@ -10,6 +10,9 @@ namespace protocol { InputFilter::InputFilter() : input_stub_(NULL) { } +InputFilter::InputFilter(InputStub* input_stub) : input_stub_(input_stub) { +} + InputFilter::~InputFilter() { } diff --git a/remoting/protocol/input_filter.h b/remoting/protocol/input_filter.h index 56ad8c1..ba8b640 100644 --- a/remoting/protocol/input_filter.h +++ b/remoting/protocol/input_filter.h @@ -16,6 +16,7 @@ namespace protocol { class InputFilter : public InputStub { public: InputFilter(); + explicit InputFilter(InputStub* input_stub); virtual ~InputFilter(); // Return the InputStub that events will be forwarded to. diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index ef927de..2509c8b 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -946,6 +946,8 @@ 'client/frame_consumer_proxy.cc', 'client/frame_consumer_proxy.h', 'client/frame_producer.h', + 'client/key_event_mapper.cc', + 'client/key_event_mapper.h', 'client/mouse_input_filter.cc', 'client/mouse_input_filter.h', 'client/rectangle_update_decoder.cc', @@ -1297,6 +1299,7 @@ 'base/base_mock_objects.cc', 'base/base_mock_objects.h', 'base/util_unittest.cc', + 'client/key_event_mapper_unittest.cc', 'client/mouse_input_filter_unittest.cc', 'host/capturer_linux_unittest.cc', 'host/capturer_mac_unittest.cc', |