summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorwez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-09 02:22:23 +0000
committerwez@chromium.org <wez@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-04-09 02:22:23 +0000
commitcb9bcfb3f87954a288a70dd1a0a90b74d0b01b56 (patch)
tree1aecdc1ecd62038e2f665cf491c10dd9e6afadb4 /remoting
parent26682cacab98c010ae458d808146f24862407b7b (diff)
downloadchromium_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.cc63
-rw-r--r--remoting/client/key_event_mapper.h54
-rw-r--r--remoting/client/key_event_mapper_unittest.cc153
-rw-r--r--remoting/client/plugin/chromoting_instance.cc42
-rw-r--r--remoting/client/plugin/chromoting_instance.h7
-rw-r--r--remoting/protocol/input_filter.cc3
-rw-r--r--remoting/protocol/input_filter.h1
-rw-r--r--remoting/remoting.gyp3
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',