Commit b8876671 authored by Erik Jensen's avatar Erik Jensen Committed by Commit Bot

remoting: Implement initial keyboard layout monitor for Windows.

This uses various Windows APIs to try to glean information about the
current layout. Unfortunately, there appears to be no way to get some of
the needed information in a clean fashion through these APIs, so the
implementation uses various guesses and hacks to try to infer it. In the
future, I'd like to look into reading the layout directly from the
layout files, which contain mapping tables that provide all of the
needed information in a known format.

This currently runs in the network process. That means it can't monitor
active window changes and thus never sends an updated layout after the
initial connection. A follow up CL will run the monitor in the desktop
process and communicate the result back via IPC.

Bug: 1026029
Change-Id: Iaba9c0bb0ef2c60fac249a9af392f9434b9c5f93
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1918356
Commit-Queue: Erik Jensen <rkjnsn@chromium.org>
Reviewed-by: default avatarYuwei Huang <yuweih@chromium.org>
Reviewed-by: default avatarGary Kacmarcik <garykac@chromium.org>
Cr-Commit-Position: refs/heads/master@{#720819}
parent a603a371
......@@ -215,6 +215,13 @@ void ChromotingClient::SetCursorShape(
user_interface_->GetCursorShapeStub()->SetCursorShape(cursor_shape);
}
void ChromotingClient::SetKeyboardLayout(
const protocol::KeyboardLayout& layout) {
DCHECK(thread_checker_.CalledOnValidThread());
user_interface_->GetKeyboardLayoutStub()->SetKeyboardLayout(layout);
}
void ChromotingClient::OnConnectionState(
protocol::ConnectionToHost::State state,
protocol::ErrorCode error) {
......
......@@ -98,6 +98,9 @@ class ChromotingClient : public SignalStrategy::Listener,
// CursorShapeStub implementation for receiving cursor shape updates.
void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) override;
// KeyboardLayoutStub implementation for sending keyboard layout to client.
void SetKeyboardLayout(const protocol::KeyboardLayout& layout) override;
// ConnectionToHost::HostEventCallback implementation.
void OnConnectionState(protocol::ConnectionToHost::State state,
protocol::ErrorCode error) override;
......
......@@ -95,7 +95,8 @@ struct SessionContext {
} // namespace
class ChromotingSession::Core : public ClientUserInterface,
public protocol::ClipboardStub {
public protocol::ClipboardStub,
public protocol::KeyboardLayoutStub {
public:
Core(ChromotingClientRuntime* runtime,
std::unique_ptr<ClientTelemetryLogger> logger,
......@@ -134,10 +135,14 @@ class ChromotingSession::Core : public ClientUserInterface,
const webrtc::DesktopVector& dpi) override;
protocol::ClipboardStub* GetClipboardStub() override;
protocol::CursorShapeStub* GetCursorShapeStub() override;
protocol::KeyboardLayoutStub* GetKeyboardLayoutStub() override;
// CursorShapeStub implementation.
// ClipboardStub implementation.
void InjectClipboardEvent(const protocol::ClipboardEvent& event) override;
// KeyboardLayoutStub implementation.
void SetKeyboardLayout(const protocol::KeyboardLayout& layout) override;
base::WeakPtr<Core> GetWeakPtr();
private:
......@@ -445,11 +450,20 @@ protocol::CursorShapeStub* ChromotingSession::Core::GetCursorShapeStub() {
return session_context_->cursor_shape_stub.get();
}
protocol::KeyboardLayoutStub* ChromotingSession::Core::GetKeyboardLayoutStub() {
return this;
}
void ChromotingSession::Core::InjectClipboardEvent(
const protocol::ClipboardEvent& event) {
NOTIMPLEMENTED();
}
void ChromotingSession::Core::SetKeyboardLayout(
const protocol::KeyboardLayout& layout) {
NOTIMPLEMENTED();
}
base::WeakPtr<ChromotingSession::Core> ChromotingSession::Core::GetWeakPtr() {
return weak_ptr_;
}
......
......@@ -22,6 +22,7 @@ namespace protocol {
class ClipboardStub;
class CursorShapeStub;
class ExtensionMessage;
class KeyboardLayoutStub;
class PairingResponse;
} // namespace protocol
......@@ -63,6 +64,9 @@ class ClientUserInterface {
// Get the view's CursorShapeStub implementation.
virtual protocol::CursorShapeStub* GetCursorShapeStub() = 0;
// Get the view's KeyboardLayoutStub implementation.
virtual protocol::KeyboardLayoutStub* GetKeyboardLayoutStub() = 0;
};
} // namespace remoting
......
......@@ -236,6 +236,11 @@ static_library("common") {
"ipc_video_frame_capturer.h",
"it2me_desktop_environment.cc",
"it2me_desktop_environment.h",
"keyboard_layout_monitor.cc",
"keyboard_layout_monitor.h",
"keyboard_layout_monitor_linux.cc",
"keyboard_layout_monitor_mac.cc",
"keyboard_layout_monitor_win.cc",
"me2me_desktop_environment.cc",
"me2me_desktop_environment.h",
"mouse_cursor_monitor_proxy.cc",
......
......@@ -25,6 +25,7 @@
#include "remoting/host/file_transfer/file_transfer_message_handler.h"
#include "remoting/host/host_extension_session.h"
#include "remoting/host/input_injector.h"
#include "remoting/host/keyboard_layout_monitor.h"
#include "remoting/host/mouse_shape_pump.h"
#include "remoting/host/screen_controls.h"
#include "remoting/host/screen_resolution.h"
......@@ -378,6 +379,12 @@ void ClientSession::OnConnectionChannelsConnected() {
new MouseShapePump(desktop_environment_->CreateMouseCursorMonitor(),
connection_->client_stub()));
// Create KeyboardLayoutMonitor to send keyboard layout.
keyboard_layout_monitor_ = KeyboardLayoutMonitor::Create(
base::BindRepeating(&protocol::KeyboardLayoutStub::SetKeyboardLayout,
base::Unretained(connection_->client_stub())));
keyboard_layout_monitor_->Start();
if (pending_video_layout_message_) {
connection_->client_stub()->SetVideoLayout(*pending_video_layout_message_);
pending_video_layout_message_.reset();
......@@ -406,6 +413,7 @@ void ClientSession::OnConnectionClosed(protocol::ErrorCode error) {
// longer valid once ConnectionToClient calls OnConnectionClosed().
audio_stream_.reset();
video_stream_.reset();
keyboard_layout_monitor_.reset();
mouse_shape_pump_.reset();
client_clipboard_factory_.InvalidateWeakPtrs();
input_injector_.reset();
......
......@@ -47,6 +47,7 @@ class AudioStream;
class DesktopEnvironment;
class DesktopEnvironmentFactory;
class InputInjector;
class KeyboardLayoutMonitor;
class MouseShapePump;
class ScreenControls;
......@@ -240,6 +241,7 @@ class ClientSession : public protocol::HostStub,
std::unique_ptr<protocol::VideoStream> video_stream_;
std::unique_ptr<protocol::AudioStream> audio_stream_;
std::unique_ptr<MouseShapePump> mouse_shape_pump_;
std::unique_ptr<KeyboardLayoutMonitor> keyboard_layout_monitor_;
// The set of all capabilities supported by the client.
std::unique_ptr<std::string> client_capabilities_;
......
// Copyright 2019 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/host/keyboard_layout_monitor.h"
#include "base/containers/span.h"
#include "ui/events/keycodes/dom/dom_code.h"
namespace remoting {
namespace {
const ui::DomCode kSupportedKeysArray[] = {
ui::DomCode::ALT_LEFT,
ui::DomCode::ALT_RIGHT,
ui::DomCode::ARROW_DOWN,
ui::DomCode::ARROW_RIGHT,
ui::DomCode::ARROW_LEFT,
ui::DomCode::ARROW_UP,
ui::DomCode::BACKQUOTE,
ui::DomCode::BACKSLASH,
ui::DomCode::BACKSPACE,
ui::DomCode::BRACKET_LEFT,
ui::DomCode::BRACKET_RIGHT,
ui::DomCode::CAPS_LOCK,
ui::DomCode::COMMA,
ui::DomCode::CONTEXT_MENU,
ui::DomCode::CONTROL_LEFT,
ui::DomCode::CONTROL_RIGHT,
ui::DomCode::CONVERT,
ui::DomCode::DEL,
ui::DomCode::DIGIT0,
ui::DomCode::DIGIT1,
ui::DomCode::DIGIT2,
ui::DomCode::DIGIT3,
ui::DomCode::DIGIT4,
ui::DomCode::DIGIT5,
ui::DomCode::DIGIT6,
ui::DomCode::DIGIT7,
ui::DomCode::DIGIT8,
ui::DomCode::DIGIT9,
ui::DomCode::END,
ui::DomCode::ENTER,
ui::DomCode::EQUAL,
ui::DomCode::ESCAPE,
ui::DomCode::F1,
ui::DomCode::F2,
ui::DomCode::F3,
ui::DomCode::F4,
ui::DomCode::F5,
ui::DomCode::F6,
ui::DomCode::F7,
ui::DomCode::F8,
ui::DomCode::F9,
ui::DomCode::F10,
ui::DomCode::F11,
ui::DomCode::F12,
ui::DomCode::HOME,
ui::DomCode::INSERT,
ui::DomCode::INTL_BACKSLASH,
ui::DomCode::INTL_RO,
ui::DomCode::INTL_YEN,
ui::DomCode::KANA_MODE,
ui::DomCode::LANG1,
ui::DomCode::LANG2,
ui::DomCode::META_LEFT,
ui::DomCode::META_RIGHT,
ui::DomCode::MINUS,
ui::DomCode::NON_CONVERT,
ui::DomCode::NUM_LOCK,
ui::DomCode::NUMPAD0,
ui::DomCode::NUMPAD1,
ui::DomCode::NUMPAD2,
ui::DomCode::NUMPAD3,
ui::DomCode::NUMPAD4,
ui::DomCode::NUMPAD5,
ui::DomCode::NUMPAD6,
ui::DomCode::NUMPAD7,
ui::DomCode::NUMPAD8,
ui::DomCode::NUMPAD9,
ui::DomCode::NUMPAD_ADD,
ui::DomCode::NUMPAD_COMMA,
ui::DomCode::NUMPAD_DECIMAL,
ui::DomCode::NUMPAD_DIVIDE,
ui::DomCode::NUMPAD_ENTER,
ui::DomCode::NUMPAD_EQUAL,
ui::DomCode::NUMPAD_MULTIPLY,
ui::DomCode::NUMPAD_SUBTRACT,
ui::DomCode::PAGE_DOWN,
ui::DomCode::PAGE_UP,
ui::DomCode::PAUSE,
ui::DomCode::PERIOD,
ui::DomCode::PRINT_SCREEN,
ui::DomCode::QUOTE,
ui::DomCode::SCROLL_LOCK,
ui::DomCode::SEMICOLON,
ui::DomCode::SHIFT_LEFT,
ui::DomCode::SHIFT_RIGHT,
ui::DomCode::SLASH,
ui::DomCode::SPACE,
ui::DomCode::TAB,
ui::DomCode::US_A,
ui::DomCode::US_B,
ui::DomCode::US_C,
ui::DomCode::US_D,
ui::DomCode::US_E,
ui::DomCode::US_F,
ui::DomCode::US_G,
ui::DomCode::US_H,
ui::DomCode::US_I,
ui::DomCode::US_J,
ui::DomCode::US_K,
ui::DomCode::US_L,
ui::DomCode::US_M,
ui::DomCode::US_N,
ui::DomCode::US_O,
ui::DomCode::US_P,
ui::DomCode::US_Q,
ui::DomCode::US_R,
ui::DomCode::US_S,
ui::DomCode::US_T,
ui::DomCode::US_U,
ui::DomCode::US_V,
ui::DomCode::US_W,
ui::DomCode::US_X,
ui::DomCode::US_Y,
ui::DomCode::US_Z,
};
} // namespace
// static
const base::span<const ui::DomCode> KeyboardLayoutMonitor::kSupportedKeys(
kSupportedKeysArray);
} // namespace remoting
// Copyright 2019 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_HOST_KEYBOARD_LAYOUT_MONITOR_H_
#define REMOTING_HOST_KEYBOARD_LAYOUT_MONITOR_H_
#include <memory>
#include "base/callback_forward.h"
#include "base/containers/span.h"
#include "ui/events/keycodes/dom/dom_code.h"
namespace remoting {
namespace protocol {
class KeyboardLayout;
} // namespace protocol
// KeyboardLayoutMonitor implementations are responsible for monitoring the
// active keyboard layout within the CRD session on the host, and triggering a
// callback whenever it changes.
class KeyboardLayoutMonitor {
public:
virtual ~KeyboardLayoutMonitor() = default;
KeyboardLayoutMonitor(const KeyboardLayoutMonitor&) = delete;
KeyboardLayoutMonitor& operator=(const KeyboardLayoutMonitor&) = delete;
// Starts monitoring the keyboard layout. This will generate a callback with
// the current layout, and an additional callback whenever the layout is
// changed. The callback is guaranteed not be be called after the
// KeyboardLayoutMonitor is destroyed.
virtual void Start() = 0;
// Creates a platform-specific KeyboardLayoutMonitor.
static std::unique_ptr<KeyboardLayoutMonitor> Create(
base::RepeatingCallback<void(const protocol::KeyboardLayout&)>);
protected:
KeyboardLayoutMonitor() = default;
// Physical keys to include in the keyboard map.
static const base::span<const ui::DomCode> kSupportedKeys;
};
} // namespace remoting
#endif // REMOTING_HOST_KEYBOARD_LAYOUT_MONITOR_H_
// Copyright 2019 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/host/keyboard_layout_monitor.h"
#include "base/callback.h"
namespace remoting {
namespace {
class KeyboardLayoutMonitorLinux : public KeyboardLayoutMonitor {
public:
KeyboardLayoutMonitorLinux(
base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback) {
}
~KeyboardLayoutMonitorLinux() override = default;
void Start() override {
// TODO(rkjnsn): Poll keyboard layout on Linux.
}
};
} // namespace
std::unique_ptr<KeyboardLayoutMonitor> KeyboardLayoutMonitor::Create(
base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback) {
return std::make_unique<KeyboardLayoutMonitorLinux>(std::move(callback));
}
} // namespace remoting
// Copyright 2019 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/host/keyboard_layout_monitor.h"
#include "base/callback.h"
namespace remoting {
namespace {
class KeyboardLayoutMonitorMac : public KeyboardLayoutMonitor {
public:
KeyboardLayoutMonitorMac(
base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback) {
}
~KeyboardLayoutMonitorMac() override = default;
void Start() override {
// TODO(rkjnsn): Poll keyboard layout on Mac.
}
};
} // namespace
std::unique_ptr<KeyboardLayoutMonitor> KeyboardLayoutMonitor::Create(
base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback) {
return std::make_unique<KeyboardLayoutMonitorMac>(std::move(callback));
}
} // namespace remoting
This diff is collapsed.
......@@ -70,6 +70,9 @@ class TestClientStub : public protocol::ClientStub {
// protocol::CursorShapeStub implementation.
void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) override;
// protocol::KeyboardLayoutStub implementation.
void SetKeyboardLayout(const protocol::KeyboardLayout& layout) override;
void WaitForDeliverHostMessage(base::TimeDelta max_timeout);
void CheckHostDataMessage(int id, const std::string& data);
......@@ -105,6 +108,9 @@ void TestClientStub::InjectClipboardEvent(
void TestClientStub::SetCursorShape(
const protocol::CursorShapeInfo& cursor_shape) {}
void TestClientStub::SetKeyboardLayout(const protocol::KeyboardLayout& layout) {
}
void TestClientStub::WaitForDeliverHostMessage(base::TimeDelta max_timeout) {
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop_->QuitClosure(), max_timeout);
......
......@@ -152,8 +152,9 @@ message KeyboardLayout {
// Next ID: 2
message KeyBehavior {
// Maps shift level to key action.
map<int32, KeyAction> actions = 1;
// Maps 0-based shift level to key action. (Note: because this is zero-
// based, it will be one less than the corresponding ISO shift level.)
map<uint32, KeyAction> actions = 1;
}
// Map USB code to key behavior.
......
......@@ -27,6 +27,7 @@ message ControlMessage {
optional ExtensionMessage extension_message = 9;
optional VideoLayout video_layout = 10;
optional SelectDesktopDisplayRequest select_display = 11;
optional KeyboardLayout keyboard_layout = 12;
}
// Defines an event message on the event channel.
......
......@@ -93,9 +93,6 @@ enum LayoutKeyFunction {
MUHENKAN = 55; // NonConvert
KATAKANA_HIRAGANA_ROMAJI = 56; // KanaMode
KANA = 57; // Lang1 (Mac keyboard)
// Note: Windows only identifies the key as "Caps Lock", so the Windows host
// currently sends the CAPS_LOCK function even for shift level 1 (unshifted)
// and doesn't use this function.
EISU = 58; // Unshifted CapsLock (Windows), Lang2 (Mac keyboard)
// Korean
HAN_YEONG = 59; // Lang1
......
......@@ -94,6 +94,7 @@ static_library("protocol") {
"jingle_session.h",
"jingle_session_manager.cc",
"jingle_session_manager.h",
"keyboard_layout_stub.h",
"me2me_host_authenticator_factory.cc",
"me2me_host_authenticator_factory.h",
"message_decoder.cc",
......
......@@ -13,6 +13,7 @@
#include "base/macros.h"
#include "remoting/protocol/clipboard_stub.h"
#include "remoting/protocol/cursor_shape_stub.h"
#include "remoting/protocol/keyboard_layout_stub.h"
namespace remoting {
namespace protocol {
......@@ -23,7 +24,8 @@ class PairingResponse;
class VideoLayout;
class ClientStub : public ClipboardStub,
public CursorShapeStub {
public CursorShapeStub,
public KeyboardLayoutStub {
public:
ClientStub() {}
~ClientStub() override {}
......
......@@ -78,6 +78,12 @@ void HostControlDispatcher::SetCursorShape(
message_pipe()->Send(&message, base::Closure());
}
void HostControlDispatcher::SetKeyboardLayout(const KeyboardLayout& layout) {
ControlMessage message;
message.mutable_keyboard_layout()->CopyFrom(layout);
message_pipe()->Send(&message, base::Closure());
}
void HostControlDispatcher::OnIncomingMessage(
std::unique_ptr<CompoundBuffer> buffer) {
DCHECK(clipboard_stub_);
......
......@@ -40,6 +40,9 @@ class HostControlDispatcher : public ChannelDispatcherBase,
// CursorShapeStub implementation for sending cursor shape to client.
void SetCursorShape(const CursorShapeInfo& cursor_shape) override;
// KeyboardLayoutStub implementation for sending keyboard layout to client.
void SetKeyboardLayout(const KeyboardLayout& layout) override;
// Sets the ClipboardStub that will be called for each incoming clipboard
// message. |clipboard_stub| must outlive this object.
void set_clipboard_stub(ClipboardStub* clipboard_stub) {
......
// Copyright 2019 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.
// Interface for an object that receives keyboard layout events.
#ifndef REMOTING_PROTOCOL_KEYBOARD_LAYOUT_STUB_H_
#define REMOTING_PROTOCOL_KEYBOARD_LAYOUT_STUB_H_
#include "base/macros.h"
namespace remoting {
namespace protocol {
class KeyboardLayout;
// Interface used to inform the client when the host's keyboard layout changes.
class KeyboardLayoutStub {
public:
virtual ~KeyboardLayoutStub() = default;
KeyboardLayoutStub(const KeyboardLayoutStub&) = delete;
KeyboardLayoutStub& operator=(const KeyboardLayoutStub&) = delete;
virtual void SetKeyboardLayout(const KeyboardLayout& keyboard_layout) = 0;
protected:
KeyboardLayoutStub() = default;
};
} // namespace protocol
} // namespace remoting
#endif // REMOTING_PROTOCOL_KEYBOARD_LAYOUT_STUB_H_
......@@ -159,6 +159,9 @@ class MockClientStub : public ClientStub {
// CursorShapeStub mock implementation.
MOCK_METHOD1(SetCursorShape, void(const CursorShapeInfo& cursor_shape));
// KeyboardLayoutStub mock implementation.
MOCK_METHOD1(SetKeyboardLayout, void(const KeyboardLayout& layout));
private:
DISALLOW_COPY_AND_ASSIGN(MockClientStub);
};
......
......@@ -203,6 +203,7 @@ class FakeConnectionEventLogger::CounterClientStub
void InjectClipboardEvent(const protocol::ClipboardEvent& event) override {}
void SetCapabilities(const protocol::Capabilities& capabilities) override {}
void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) override {}
void SetKeyboardLayout(const protocol::KeyboardLayout& layout) override {}
void SetPairingResponse(const protocol::PairingResponse& response) override {}
void SetVideoLayout(const protocol::VideoLayout& video_layout) override {}
};
......
......@@ -155,6 +155,9 @@ class ProtocolPerfTest
protocol::CursorShapeStub* GetCursorShapeStub() override {
return &cursor_shape_stub_;
}
protocol::KeyboardLayoutStub* GetKeyboardLayoutStub() override {
return nullptr;
}
// protocol::FrameConsumer interface.
std::unique_ptr<webrtc::DesktopFrame> AllocateFrame(
......
......@@ -266,6 +266,11 @@ protocol::CursorShapeStub* TestChromotingClient::GetCursorShapeStub() {
return this;
}
protocol::KeyboardLayoutStub* TestChromotingClient::GetKeyboardLayoutStub() {
VLOG(1) << "TestChromotingClient::GetKeyboardLayoutStub() Called";
return this;
}
void TestChromotingClient::InjectClipboardEvent(
const protocol::ClipboardEvent& event) {
VLOG(1) << "TestChromotingClient::InjectClipboardEvent() Called";
......@@ -276,5 +281,10 @@ void TestChromotingClient::SetCursorShape(
VLOG(1) << "TestChromotingClient::SetCursorShape() Called";
}
void TestChromotingClient::SetKeyboardLayout(
const protocol::KeyboardLayout& layout) {
VLOG(1) << "TestChromotingClient::SetKeyboardLayout() Called";
}
} // namespace test
} // namespace remoting
......@@ -15,6 +15,7 @@
#include "remoting/client/client_user_interface.h"
#include "remoting/protocol/clipboard_filter.h"
#include "remoting/protocol/cursor_shape_stub.h"
#include "remoting/protocol/keyboard_layout_stub.h"
#include "remoting/test/remote_connection_observer.h"
namespace remoting {
......@@ -40,7 +41,8 @@ struct ConnectionSetupInfo;
// A VideoRenderer can be passed in to customize the connection.
class TestChromotingClient : public ClientUserInterface,
public protocol::ClipboardStub,
public protocol::CursorShapeStub {
public protocol::CursorShapeStub,
public protocol::KeyboardLayoutStub {
public:
TestChromotingClient();
explicit TestChromotingClient(
......@@ -90,6 +92,7 @@ class TestChromotingClient : public ClientUserInterface,
const webrtc::DesktopVector& dpi) override;
protocol::ClipboardStub* GetClipboardStub() override;
protocol::CursorShapeStub* GetCursorShapeStub() override;
protocol::KeyboardLayoutStub* GetKeyboardLayoutStub() override;
// protocol::ClipboardStub interface.
void InjectClipboardEvent(const protocol::ClipboardEvent& event) override;
......@@ -97,6 +100,9 @@ class TestChromotingClient : public ClientUserInterface,
// protocol::CursorShapeStub interface.
void SetCursorShape(const protocol::CursorShapeInfo& cursor_shape) override;
// protocol::KeyboardLayoutStub interface.
void SetKeyboardLayout(const protocol::KeyboardLayout& layout) override;
// Tracks the current connection state.
protocol::ConnectionToHost::State connection_to_host_state_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment