| // 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 <windows.h> |
| #include <ime.h> |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/compiler_specific.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/no_destructor.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/stl_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/post_task.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "base/threading/thread_local.h" |
| #include "base/timer/timer.h" |
| #include "remoting/proto/control.pb.h" |
| #include "third_party/webrtc/modules/desktop_capture/win/desktop.h" |
| #include "third_party/webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" |
| #include "ui/events/keycodes/dom/dom_code.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| |
| namespace remoting { |
| |
| namespace { |
| |
| constexpr base::TimeDelta POLL_INTERVAL = |
| base::TimeDelta::FromMilliseconds(1000); |
| // If second is equivalent to first (generates the same functions/characters at |
| // all shift levels), second will be removed from the map. |
| constexpr std::pair<ui::DomCode, ui::DomCode> POSSIBLE_EQUIVALENTS[] = { |
| // These are equivalent on the US QWERTY layout, among others. |
| {ui::DomCode::BACKSLASH, ui::DomCode::INTL_BACKSLASH}}; |
| |
| class KeyboardLayoutMonitorWin : public KeyboardLayoutMonitor { |
| public: |
| KeyboardLayoutMonitorWin( |
| base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback, |
| scoped_refptr<base::SingleThreadTaskRunner> input_task_runner); |
| ~KeyboardLayoutMonitorWin() override; |
| void Start() override; |
| |
| private: |
| // Check the current layout, and invoke the callback if it has changed. |
| void QueryLayout(); |
| void ResetTimer(); |
| static void QueryLayoutOnInputThread( |
| scoped_refptr<base::SequencedTaskRunner> reply_sequence, |
| base::WeakPtr<KeyboardLayoutMonitorWin> monitor, |
| HKL previous_layout); |
| void OnLayoutChanged(HKL new_layout, protocol::KeyboardLayout layout_details); |
| |
| HKL previous_layout_ = nullptr; |
| base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback_; |
| base::RetainingOneShotTimer timer_; |
| scoped_refptr<base::SingleThreadTaskRunner> input_task_runner_; |
| base::WeakPtrFactory<KeyboardLayoutMonitorWin> weak_ptr_factory_; |
| }; |
| |
| KeyboardLayoutMonitorWin::KeyboardLayoutMonitorWin( |
| base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback, |
| scoped_refptr<base::SingleThreadTaskRunner> input_task_runner) |
| : callback_(std::move(callback)), |
| input_task_runner_(std::move(input_task_runner)), |
| weak_ptr_factory_(this) {} |
| |
| KeyboardLayoutMonitorWin::~KeyboardLayoutMonitorWin() = default; |
| |
| void KeyboardLayoutMonitorWin::Start() { |
| timer_.Start(FROM_HERE, POLL_INTERVAL, this, |
| &KeyboardLayoutMonitorWin::QueryLayout); |
| } |
| |
| void ClearDeadKeys(HKL layout); |
| bool IsNumpadKey(ui::DomCode code); |
| UINT TranslateVirtualKey(bool numlock_state, |
| bool shift_state, |
| UINT virtual_key, |
| ui::DomCode code); |
| protocol::LayoutKeyFunction VirtualKeyToLayoutKeyFunction(UINT virtual_key, |
| LANGID lang); |
| |
| void KeyboardLayoutMonitorWin::QueryLayout() { |
| // Only reset the timer once the task has completed. This ensures that a delay |
| // on the input thread doesn't result in us queuing up a bunch of redundant |
| // tasks. |
| input_task_runner_->PostTaskAndReply( |
| FROM_HERE, |
| base::BindOnce(&QueryLayoutOnInputThread, |
| base::SequencedTaskRunnerHandle::Get(), |
| weak_ptr_factory_.GetWeakPtr(), previous_layout_), |
| base::BindOnce(&KeyboardLayoutMonitorWin::ResetTimer, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void KeyboardLayoutMonitorWin::ResetTimer() { |
| timer_.Reset(); |
| } |
| |
| // static |
| void KeyboardLayoutMonitorWin::QueryLayoutOnInputThread( |
| scoped_refptr<base::SequencedTaskRunner> reply_sequence, |
| base::WeakPtr<KeyboardLayoutMonitorWin> monitor, |
| HKL previous_layout) { |
| // Switch to the active desktop. |
| webrtc::ScopedThreadDesktop desktop; |
| std::unique_ptr<webrtc::Desktop> input_desktop( |
| webrtc::Desktop::GetInputDesktop()); |
| if (input_desktop && !desktop.IsSame(*input_desktop)) { |
| desktop.SetThreadDesktop(input_desktop.release()); |
| } |
| |
| // Get the keyboard layout for the active window. |
| DWORD thread_id = 0; |
| HWND foreground_window = GetForegroundWindow(); |
| if (foreground_window) { |
| thread_id = GetWindowThreadProcessId(foreground_window, nullptr); |
| } else if (previous_layout != 0) { |
| // There's no currently active window, so keep using the previous layout. |
| return; |
| } |
| // If there is no previous layout and there's no active window |
| // (thread_id == 0), this will return the layout associated with this |
| // thread, which is better than nothing. |
| HKL layout = GetKeyboardLayout(thread_id); |
| if (layout == previous_layout) { |
| return; |
| } |
| |
| protocol::KeyboardLayout layout_message; |
| // TODO(rkjnsn): Windows doesn't provide an API to read the keyboard layout |
| // directly. We can use the various key translation functions to mostly get |
| // the information we need, but it requires some hacks, could miss some edge |
| // cases, and modifies the system keyboard state. Windows keyboard layouts |
| // consist of DLLs providing data tables in a reasonable straight-forward |
| // format, so it may make sense to look at reading them directly. |
| |
| // It would be nice if we could use MapVirtualKeyEx to translate virtual |
| // keys to characters, as it neither is affected by nor modifies the system |
| // keyboard state. Unfortunately, it provides no way to specify shift |
| // states, so it can only be used to retrieve the unshifted character for a |
| // given key. Instead, we use ToUnicodeEx, which is affected by (and |
| // affects) the system keyboard state, so we start by clearing any queued-up |
| // dead keys. |
| ClearDeadKeys(layout); |
| |
| // Keyboard layouts have a KLLF_ALTGR flag that indicated whether the right |
| // alt key is AltGr (and thus generates Ctrl+Alt). Unfortunately, there |
| // doesn't seem to be any way to determine whether the current layout sets |
| // this flag using the Windows API. As a hack/workaround, we assume that |
| // right Alt == AltGr if and only if there are keys that generate characters |
| // when both Ctrl and Alt modifiers are set. This is by no means guaranteed, |
| // but is expected to be true for common layouts. |
| bool has_altgr = false; |
| |
| // Keys/shift levels that function as right Alt. These will be updated to |
| // AltGr if the keyboard appears to use it. (Most keyboards will have at |
| // most one of these.) |
| std::vector<std::pair<std::uint32_t, int>> right_alts; |
| |
| for (ui::DomCode key : KeyboardLayoutMonitor::kSupportedKeys) { |
| // These keys cannot be injected properly on Windows with the APIs we use. |
| // |
| // The USB keyboard driver translates NumLock to the scancode 0x45, but it |
| // is delivered to applications as 0xE045. Meanwhile, Pause is translated |
| // to the scancode sequence 0xE1 0x1D 0x45 0xE1 0x9D 0xC5, but is delivered |
| // to applications as a plain 0x45. |
| // |
| // Injecting 0x45 using SendInput does get interpreted by Windows as |
| // VK_NUMLOCK, but is not translated to 0xE045 before being delivered to |
| // applications as it is with a physical keyboard. As a result, Chrome (for |
| // example) reports a key value of "NumLock" but a code value of "Pause". |
| // |
| // Injecting 0xE045, on the otherhand, does not get interpreted as |
| // VK_NUMLOCK, so Chrome sees reports a code value of "NumLock", but a key |
| // value of "Unidentified". |
| // |
| // I have not been able to determine any way to use SendInput to inject an |
| // event that Windows interprets as VK_PAUSE. |
| // |
| // NumpadEqual also behaves inconsistently when injected, but in a |
| // different way: while when input using a physical keyboard, the |
| // corresponding scancode (0x59) is always interpreted as VK_CLEAR |
| // regardless of the num lock state. When injected, however, Windows |
| // translates the key to VK_NUMPAD5 when numlock is enabled. Since the |
| // virtual keyboard considers num lock always to be enabled, this |
| // effectively results in an extra 5 key in the NumpadEqual position, which |
| // is both redundant and confusing. Given that most keyboards lack this key, |
| // and those that do have it label it '=', it seems easiest just to exclude |
| // it for now. In the future, we could consider adding support to it for |
| // keyboard layouts that treat is as something other than VK_CLEAR, if such |
| // layouts turn out to exist. (Why Windows maps the USB 'Keypad =' key to |
| // scancode 0x59 in the first place, even though 0x59 does not generate an |
| // '=' character, is unclear.) |
| if (key == ui::DomCode::NUM_LOCK || key == ui::DomCode::PAUSE || |
| key == ui::DomCode::NUMPAD_EQUAL) { |
| continue; |
| } |
| |
| std::uint32_t usb_code = ui::KeycodeConverter::DomCodeToUsbKeycode(key); |
| int scancode = ui::KeycodeConverter::DomCodeToNativeKeycode(key); |
| UINT virtual_key = MapVirtualKeyEx(scancode, MAPVK_VSC_TO_VK_EX, layout); |
| if (virtual_key == 0) { |
| // This key is not mapped in the current layout. |
| continue; |
| } |
| |
| if (virtual_key == VK_CAPITAL || virtual_key == VK_NUMLOCK) { |
| // Don't send caps or numlock keys until we decide how to handle them. |
| // (We currently skip the key in the NumLock position above due to |
| // difficulties injecting it, but the user still may have mapped a |
| // different key to that function.) |
| continue; |
| } |
| |
| google::protobuf::Map<google::protobuf::uint32, |
| protocol::KeyboardLayout_KeyAction>& key_actions = |
| *(*layout_message.mutable_keys())[usb_code].mutable_actions(); |
| |
| for (int shift_level = 0; shift_level < 4; ++shift_level) { |
| // Mimic Windows's handling of number pad key. |
| UINT translated_key = TranslateVirtualKey( |
| /* numlock_state */ true, shift_level & 1, virtual_key, key); |
| |
| // First check if the key generates a character. |
| BYTE key_state[256] = {0}; |
| // Modifiers set the high-order bit when pressed. |
| key_state[VK_SHIFT] = (shift_level & 1) << 7; |
| key_state[VK_CONTROL] = key_state[VK_MENU] = (shift_level & 2) << 6; |
| // Locks set the low-order bit when toggled on. |
| // For now, generate a layout with numlock always on and caps lock |
| // always off. |
| // TODO(rkjnsn): Update this when we decide how we want to handle locks |
| // for the on-screen keyboard. |
| key_state[VK_NUMLOCK] = 1; |
| key_state[VK_CAPITAL] = 0; |
| WCHAR char_buffer[16]; |
| // According to the documentation, ToUnicodeEx usually does the |
| // translation solely based on the virtual key, but can use bit 15 of the |
| // scancode to distinguish between a keypress and a key release. This |
| // suggests that the expected format for scancode is the upper word of |
| // lParam from a WM_CHAR, where the top bit similarly distinguished press |
| // versus release. In any event, passing |scancode| as the second |
| // parameter here would thus cause extended scancodes (which have the 15th |
| // bit set) to erroneously be interpreted as key-up events and not |
| // generate the appropriate character. Rather than attempting to munge the |
| // scancode into whatever format ToUnicodeEx expects, passing 0 seems to |
| // work just fine. |
| int size = ToUnicodeEx(translated_key, 0, key_state, char_buffer, |
| base::size(char_buffer), 0, layout); |
| if (size < 0) { |
| // We don't handle dead keys specially for the layout, but we do |
| // need to clear them from the system keyboard state. |
| ClearDeadKeys(layout); |
| size = -size; |
| } |
| if (size > 0) { |
| if (size == 1 && char_buffer[0] < 0x20) { |
| // Handle known control characters. |
| protocol::LayoutKeyFunction function = |
| protocol::LayoutKeyFunction::UNKNOWN; |
| switch (char_buffer[0]) { |
| case 0x08: |
| function = protocol::LayoutKeyFunction::BACKSPACE; |
| break; |
| case 0x09: |
| function = protocol::LayoutKeyFunction::TAB; |
| break; |
| case 0x0D: |
| function = protocol::LayoutKeyFunction::ENTER; |
| break; |
| case 0x1B: |
| function = protocol::LayoutKeyFunction::ESCAPE; |
| break; |
| } |
| if (function != protocol::LayoutKeyFunction::UNKNOWN) { |
| key_actions[shift_level].set_function(function); |
| continue; |
| } |
| } |
| // The key generated at least one character. |
| key_actions[shift_level].set_character( |
| base::UTF16ToUTF8(base::StringPiece16(char_buffer, size))); |
| if (shift_level > 2) { |
| has_altgr = true; |
| } |
| continue; |
| } |
| |
| // If the key didn't generate a character, translate it based on the |
| // virtual key value. |
| key_actions[shift_level].set_function(VirtualKeyToLayoutKeyFunction( |
| translated_key, reinterpret_cast<std::uintptr_t>(layout) & 0xFFFF)); |
| if (translated_key == VK_RMENU) { |
| right_alts.emplace_back(usb_code, shift_level); |
| } |
| } |
| } |
| |
| // If any ctrl+alt+key sequence generated a character, assume right Alt is |
| // AltGr. |
| if (has_altgr) { |
| for (std::pair<std::uint32_t, int> right_alt : right_alts) { |
| (*(*layout_message.mutable_keys())[right_alt.first] |
| .mutable_actions())[right_alt.second] |
| .set_function(protocol::LayoutKeyFunction::ALT_GR); |
| } |
| } else { |
| // Remove higher shift levels since there's no way to generate them. |
| for (auto& key : *layout_message.mutable_keys()) { |
| key.second.mutable_actions()->erase(2); |
| key.second.mutable_actions()->erase(3); |
| } |
| } |
| |
| // Some layouts have equivalent keys. Remove the redundant keys to make the |
| // layout cleaner. |
| auto* keys = layout_message.mutable_keys(); |
| for (const std::pair<ui::DomCode, ui::DomCode>& possible_equivalent : |
| POSSIBLE_EQUIVALENTS) { |
| std::uint32_t code1 = |
| ui::KeycodeConverter::DomCodeToUsbKeycode(possible_equivalent.first); |
| std::uint32_t code2 = |
| ui::KeycodeConverter::DomCodeToUsbKeycode(possible_equivalent.second); |
| auto key_behavior1 = keys->find(code1); |
| auto key_behavior2 = keys->find(code2); |
| if (key_behavior1 != keys->end() && key_behavior2 != keys->end() && |
| key_behavior1->second.SerializeAsString() == |
| key_behavior2->second.SerializeAsString()) { |
| keys->erase(key_behavior2); |
| } |
| } |
| |
| // There seem to be a number of keys that are mapped to a virtual key in the |
| // layout but don't do anything useful. E.g., the US QWERTY layout maps |
| // NonConvert, which isn't on a standard US keyboard, to VK_OEM_PA1, which |
| // doesn't appear to be useful. To avoid cluttering the on-screen keyboard |
| // with blank, useless keys, just omit unknown keys for now. We can revisit |
| // this if folks send feedback about useful keys being missing. |
| for (auto it = keys->begin(); it != keys->end();) { |
| bool has_action = false; |
| for (const auto& action : it->second.actions()) { |
| if (action.second.has_character() || |
| (action.second.has_function() && |
| action.second.function() != protocol::LayoutKeyFunction::UNKNOWN)) { |
| has_action = true; |
| } |
| } |
| if (!has_action) { |
| it = keys->erase(it); |
| } else { |
| ++it; |
| } |
| } |
| |
| reply_sequence->PostTask( |
| FROM_HERE, |
| base::BindOnce(&KeyboardLayoutMonitorWin::OnLayoutChanged, |
| std::move(monitor), layout, std::move(layout_message))); |
| } |
| |
| void KeyboardLayoutMonitorWin::OnLayoutChanged( |
| HKL new_layout, |
| protocol::KeyboardLayout layout_details) { |
| previous_layout_ = new_layout; |
| callback_.Run(std::move(layout_details)); |
| } |
| |
| void ClearDeadKeys(HKL layout) { |
| // ToUnicodeEx both is affected by and modifies the current keyboard state, |
| // which includes the list of currently stored dead keys. Pressing space |
| // translates previously pressed dead keys to characters, clearing the dead- |
| // key buffer. |
| BYTE key_state[256] = {0}; |
| WCHAR char_buffer[16]; |
| ToUnicodeEx(VK_SPACE, |
| ui::KeycodeConverter::DomCodeToNativeKeycode(ui::DomCode::SPACE), |
| key_state, char_buffer, base::size(char_buffer), 0, layout); |
| } |
| |
| bool IsNumpadKey(ui::DomCode code) { |
| // Windows keyboard layouts map number pad keys to virtual keys based on |
| // their function when num lock is off. E.g., 4 on the number pad generates |
| // VK_LEFT, the same as the left arrow key. To distinguish them, the layout |
| // sets the KBDNUMPAD flag on the numlock versions, allowing Windows to |
| // translate VK_LEFT to VK_NUMPAD4 when numlock is enabled only for the keys |
| // on the number pad. Unfortunately, the state of the KBDNUMPAD flag for a |
| // given scan code does not appear to be accessible via the Windows API, so |
| // for now just assume that all layouts use the same scan codes for the |
| // number pad. |
| // TODO(rkjnsn): Figure out if there's a better way to determine this. |
| |
| switch (code) { |
| case ui::DomCode::NUMPAD0: |
| case ui::DomCode::NUMPAD1: |
| case ui::DomCode::NUMPAD2: |
| case ui::DomCode::NUMPAD3: |
| case ui::DomCode::NUMPAD4: |
| case ui::DomCode::NUMPAD5: |
| case ui::DomCode::NUMPAD6: |
| case ui::DomCode::NUMPAD7: |
| case ui::DomCode::NUMPAD8: |
| case ui::DomCode::NUMPAD9: |
| case ui::DomCode::NUMPAD_DECIMAL: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| UINT TranslateVirtualKey(bool numlock_state, |
| bool shift_state, |
| UINT virtual_key, |
| ui::DomCode code) { |
| // Windows only translates numpad keys when num lock is on and shift is not |
| // pressed. (Pressing shift when num lock is on will get you navigation, but |
| // pressing shift when num lock is off will not get you numbers.) |
| if (!numlock_state || shift_state || !IsNumpadKey(code)) { |
| return virtual_key; |
| } |
| switch (virtual_key) { |
| case VK_DELETE: |
| return VK_DECIMAL; |
| case VK_INSERT: |
| return VK_NUMPAD0; |
| case VK_END: |
| return VK_NUMPAD1; |
| case VK_DOWN: |
| return VK_NUMPAD2; |
| case VK_NEXT: |
| return VK_NUMPAD3; |
| case VK_LEFT: |
| return VK_NUMPAD4; |
| case VK_CLEAR: |
| return VK_NUMPAD5; |
| case VK_RIGHT: |
| return VK_NUMPAD6; |
| case VK_HOME: |
| return VK_NUMPAD7; |
| case VK_UP: |
| return VK_NUMPAD8; |
| case VK_PRIOR: |
| return VK_NUMPAD9; |
| default: |
| return virtual_key; |
| } |
| } |
| |
| protocol::LayoutKeyFunction VirtualKeyToLayoutKeyFunction(UINT virtual_key, |
| LANGID lang) { |
| switch (virtual_key) { |
| case VK_LCONTROL: |
| case VK_RCONTROL: |
| return protocol::LayoutKeyFunction::CONTROL; |
| case VK_LMENU: |
| case VK_RMENU: |
| return protocol::LayoutKeyFunction::ALT; |
| case VK_LSHIFT: |
| case VK_RSHIFT: |
| return protocol::LayoutKeyFunction::SHIFT; |
| case VK_LWIN: |
| case VK_RWIN: |
| return protocol::LayoutKeyFunction::META; |
| case VK_NUMLOCK: |
| return protocol::LayoutKeyFunction::NUM_LOCK; |
| case VK_CAPITAL: |
| return protocol::LayoutKeyFunction::CAPS_LOCK; |
| case VK_SCROLL: |
| return protocol::LayoutKeyFunction::SCROLL_LOCK; |
| case VK_BACK: |
| return protocol::LayoutKeyFunction::BACKSPACE; |
| case VK_RETURN: |
| return protocol::LayoutKeyFunction::ENTER; |
| case VK_TAB: |
| return protocol::LayoutKeyFunction::TAB; |
| case VK_INSERT: |
| return protocol::LayoutKeyFunction::INSERT; |
| case VK_DELETE: |
| return protocol::LayoutKeyFunction::DELETE_; |
| case VK_HOME: |
| return protocol::LayoutKeyFunction::HOME; |
| case VK_END: |
| return protocol::LayoutKeyFunction::END; |
| case VK_PRIOR: |
| return protocol::LayoutKeyFunction::PAGE_UP; |
| case VK_NEXT: |
| return protocol::LayoutKeyFunction::PAGE_DOWN; |
| case VK_CLEAR: |
| return protocol::LayoutKeyFunction::CLEAR; |
| case VK_UP: |
| return protocol::LayoutKeyFunction::ARROW_UP; |
| case VK_DOWN: |
| return protocol::LayoutKeyFunction::ARROW_DOWN; |
| case VK_LEFT: |
| return protocol::LayoutKeyFunction::ARROW_LEFT; |
| case VK_RIGHT: |
| return protocol::LayoutKeyFunction::ARROW_RIGHT; |
| case VK_F1: |
| return protocol::LayoutKeyFunction::F1; |
| case VK_F2: |
| return protocol::LayoutKeyFunction::F2; |
| case VK_F3: |
| return protocol::LayoutKeyFunction::F3; |
| case VK_F4: |
| return protocol::LayoutKeyFunction::F4; |
| case VK_F5: |
| return protocol::LayoutKeyFunction::F5; |
| case VK_F6: |
| return protocol::LayoutKeyFunction::F6; |
| case VK_F7: |
| return protocol::LayoutKeyFunction::F7; |
| case VK_F8: |
| return protocol::LayoutKeyFunction::F8; |
| case VK_F9: |
| return protocol::LayoutKeyFunction::F9; |
| case VK_F10: |
| return protocol::LayoutKeyFunction::F10; |
| case VK_F11: |
| return protocol::LayoutKeyFunction::F11; |
| case VK_F12: |
| return protocol::LayoutKeyFunction::F12; |
| case VK_F13: |
| return protocol::LayoutKeyFunction::F13; |
| case VK_F14: |
| return protocol::LayoutKeyFunction::F14; |
| case VK_F15: |
| return protocol::LayoutKeyFunction::F15; |
| case VK_F16: |
| return protocol::LayoutKeyFunction::F16; |
| case VK_F17: |
| return protocol::LayoutKeyFunction::F17; |
| case VK_F18: |
| return protocol::LayoutKeyFunction::F18; |
| case VK_F19: |
| return protocol::LayoutKeyFunction::F19; |
| case VK_F20: |
| return protocol::LayoutKeyFunction::F20; |
| case VK_F21: |
| return protocol::LayoutKeyFunction::F21; |
| case VK_F22: |
| return protocol::LayoutKeyFunction::F22; |
| case VK_F23: |
| return protocol::LayoutKeyFunction::F23; |
| case VK_F24: |
| return protocol::LayoutKeyFunction::F24; |
| case VK_ESCAPE: |
| return protocol::LayoutKeyFunction::ESCAPE; |
| case VK_APPS: |
| return protocol::LayoutKeyFunction::CONTEXT_MENU; |
| case VK_PAUSE: |
| return protocol::LayoutKeyFunction::PAUSE; |
| case VK_SNAPSHOT: |
| return protocol::LayoutKeyFunction::PRINT_SCREEN; |
| } |
| |
| // Handle language-specific keys. |
| if (PRIMARYLANGID(lang) == 0x11) { // Japanese |
| switch (virtual_key) { |
| case VK_DBE_SBCSCHAR: |
| return protocol::LayoutKeyFunction::HANKAKU_ZENKAKU_KANJI; |
| case VK_CONVERT: |
| return protocol::LayoutKeyFunction::HENKAN; |
| case VK_NONCONVERT: |
| return protocol::LayoutKeyFunction::MUHENKAN; |
| case VK_DBE_KATAKANA: |
| case VK_DBE_HIRAGANA: |
| // TODO(rkjnsn): Make sure it makes sense to use the same key cap for |
| // both of these. |
| return protocol::LayoutKeyFunction::KATAKANA_HIRAGANA_ROMAJI; |
| case VK_DBE_ALPHANUMERIC: |
| return protocol::LayoutKeyFunction::EISU; |
| } |
| } else if (PRIMARYLANGID(lang) == 0x12) { // Korean |
| switch (virtual_key) { |
| case VK_HANJA: |
| return protocol::LayoutKeyFunction::HANJA; |
| case VK_HANGUL: |
| return protocol::LayoutKeyFunction::HAN_YEONG; |
| } |
| } |
| |
| return protocol::LayoutKeyFunction::UNKNOWN; |
| } |
| |
| } // namespace |
| |
| std::unique_ptr<KeyboardLayoutMonitor> KeyboardLayoutMonitor::Create( |
| base::RepeatingCallback<void(const protocol::KeyboardLayout&)> callback, |
| scoped_refptr<base::SingleThreadTaskRunner> input_task_runner) { |
| return std::make_unique<KeyboardLayoutMonitorWin>( |
| std::move(callback), std::move(input_task_runner)); |
| } |
| |
| } // namespace remoting |