| // 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/host/input_injector.h" |
| |
| #include <stdint.h> |
| #include <windows.h> |
| |
| #include <utility> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/win/windows_version.h" |
| #include "remoting/base/util.h" |
| #include "remoting/host/clipboard.h" |
| #include "remoting/host/touch_injector_win.h" |
| #include "remoting/proto/event.pb.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| |
| namespace remoting { |
| |
| namespace { |
| |
| using protocol::ClipboardEvent; |
| using protocol::KeyEvent; |
| using protocol::TextEvent; |
| using protocol::MouseEvent; |
| using protocol::TouchEvent; |
| |
| // Helper used to call SendInput() API. |
| void SendKeyboardInput(uint32_t flags, uint16_t scancode) { |
| // Populate a Windows INPUT structure for the event. |
| INPUT input; |
| memset(&input, 0, sizeof(input)); |
| input.type = INPUT_KEYBOARD; |
| input.ki.time = 0; |
| input.ki.dwFlags = flags; |
| input.ki.wScan = scancode; |
| |
| if ((flags & KEYEVENTF_UNICODE) == 0) { |
| // Windows scancodes are only 8-bit, so store the low-order byte into the |
| // event and set the extended flag if any high-order bits are set. The only |
| // high-order values we should see are 0xE0 or 0xE1. The extended bit |
| // usually distinguishes keys with the same meaning, e.g. left & right |
| // shift. |
| input.ki.wScan &= 0xFF; |
| if ((scancode & 0xFF00) != 0x0000) |
| input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; |
| } |
| |
| if (SendInput(1, &input, sizeof(INPUT)) == 0) |
| PLOG(ERROR) << "Failed to inject a key event"; |
| } |
| |
| // Parse move related operations from the input MouseEvent, and insert the |
| // result into output. |
| void ParseMouseMoveEvent(const MouseEvent& event, std::vector<INPUT>* output) { |
| INPUT input = {0}; |
| input.type = INPUT_MOUSE; |
| |
| if (event.has_delta_x() && event.has_delta_y()) { |
| input.mi.dx = event.delta_x(); |
| input.mi.dy = event.delta_y(); |
| input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_VIRTUALDESK; |
| } else if (event.has_x() && event.has_y()) { |
| int width = GetSystemMetrics(SM_CXVIRTUALSCREEN); |
| int height = GetSystemMetrics(SM_CYVIRTUALSCREEN); |
| if (width > 1 && height > 1) { |
| int x = std::max(0, std::min(width, event.x())); |
| int y = std::max(0, std::min(height, event.y())); |
| input.mi.dx = static_cast<int>((x * 65535) / (width - 1)); |
| input.mi.dy = static_cast<int>((y * 65535) / (height - 1)); |
| input.mi.dwFlags = |
| MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE | MOUSEEVENTF_VIRTUALDESK; |
| } |
| } else { |
| return; |
| } |
| |
| output->push_back(std::move(input)); |
| } |
| |
| // Parse click related operations from the input MouseEvent, and insert the |
| // result into output. |
| void ParseMouseClickEvent(const MouseEvent& event, std::vector<INPUT>* output) { |
| if (event.has_button() && event.has_button_down()) { |
| INPUT input = {0}; |
| input.type = INPUT_MOUSE; |
| |
| MouseEvent::MouseButton button = event.button(); |
| bool down = event.button_down(); |
| |
| // If the host is configured to swap left & right buttons, inject swapped |
| // events to un-do that re-mapping. |
| if (GetSystemMetrics(SM_SWAPBUTTON)) { |
| if (button == MouseEvent::BUTTON_LEFT) { |
| button = MouseEvent::BUTTON_RIGHT; |
| } else if (button == MouseEvent::BUTTON_RIGHT) { |
| button = MouseEvent::BUTTON_LEFT; |
| } |
| } |
| |
| if (button == MouseEvent::BUTTON_MIDDLE) { |
| input.mi.dwFlags = down ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; |
| } else if (button == MouseEvent::BUTTON_RIGHT) { |
| input.mi.dwFlags = down ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; |
| } else { |
| input.mi.dwFlags = down ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; |
| } |
| |
| output->push_back(std::move(input)); |
| } |
| } |
| |
| // Parse wheel related operations from the input MouseEvent, and insert the |
| // result into output. |
| void ParseMouseWheelEvent(const MouseEvent& event, std::vector<INPUT>* output) { |
| if (event.has_wheel_delta_x()) { |
| int delta = static_cast<int>(event.wheel_delta_x()); |
| if (delta != 0) { |
| INPUT input = {0}; |
| input.type = INPUT_MOUSE; |
| input.mi.mouseData = delta; |
| // According to MSDN, MOUSEEVENTF_HWHELL and MOUSEEVENTF_WHEEL are both |
| // required for a horizontal wheel event. |
| input.mi.dwFlags = MOUSEEVENTF_HWHEEL | MOUSEEVENTF_WHEEL; |
| output->push_back(std::move(input)); |
| } |
| } |
| |
| if (event.has_wheel_delta_y()) { |
| int delta = static_cast<int>(event.wheel_delta_y()); |
| if (delta != 0) { |
| INPUT input = {0}; |
| input.type = INPUT_MOUSE; |
| input.mi.mouseData = delta; |
| input.mi.dwFlags = MOUSEEVENTF_WHEEL; |
| output->push_back(std::move(input)); |
| } |
| } |
| } |
| |
| // A class to generate events on Windows. |
| class InputInjectorWin : public InputInjector { |
| public: |
| InputInjectorWin(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner); |
| ~InputInjectorWin() override; |
| |
| // ClipboardStub interface. |
| void InjectClipboardEvent(const ClipboardEvent& event) override; |
| |
| // InputStub interface. |
| void InjectKeyEvent(const KeyEvent& event) override; |
| void InjectTextEvent(const TextEvent& event) override; |
| void InjectMouseEvent(const MouseEvent& event) override; |
| void InjectTouchEvent(const TouchEvent& event) override; |
| |
| // InputInjector interface. |
| void Start( |
| std::unique_ptr<protocol::ClipboardStub> client_clipboard) override; |
| |
| private: |
| // The actual implementation resides in InputInjectorWin::Core class. |
| class Core : public base::RefCountedThreadSafe<Core> { |
| public: |
| Core(scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner); |
| |
| // Mirrors the ClipboardStub interface. |
| void InjectClipboardEvent(const ClipboardEvent& event); |
| |
| // Mirrors the InputStub interface. |
| void InjectKeyEvent(const KeyEvent& event); |
| void InjectTextEvent(const TextEvent& event); |
| void InjectMouseEvent(const MouseEvent& event); |
| void InjectTouchEvent(const TouchEvent& event); |
| |
| // Mirrors the InputInjector interface. |
| void Start(std::unique_ptr<protocol::ClipboardStub> client_clipboard); |
| |
| void Stop(); |
| |
| private: |
| friend class base::RefCountedThreadSafe<Core>; |
| virtual ~Core(); |
| |
| void HandleKey(const KeyEvent& event); |
| void HandleText(const TextEvent& event); |
| void HandleMouse(const MouseEvent& event); |
| void HandleTouch(const TouchEvent& event); |
| |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_; |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; |
| std::unique_ptr<Clipboard> clipboard_; |
| std::unique_ptr<TouchInjectorWin> touch_injector_; |
| |
| DISALLOW_COPY_AND_ASSIGN(Core); |
| }; |
| |
| scoped_refptr<Core> core_; |
| |
| DISALLOW_COPY_AND_ASSIGN(InputInjectorWin); |
| }; |
| |
| InputInjectorWin::InputInjectorWin( |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { |
| core_ = new Core(main_task_runner, ui_task_runner); |
| } |
| |
| InputInjectorWin::~InputInjectorWin() { |
| core_->Stop(); |
| } |
| |
| void InputInjectorWin::InjectClipboardEvent(const ClipboardEvent& event) { |
| core_->InjectClipboardEvent(event); |
| } |
| |
| void InputInjectorWin::InjectKeyEvent(const KeyEvent& event) { |
| core_->InjectKeyEvent(event); |
| } |
| |
| void InputInjectorWin::InjectTextEvent(const TextEvent& event) { |
| core_->InjectTextEvent(event); |
| } |
| |
| void InputInjectorWin::InjectMouseEvent(const MouseEvent& event) { |
| core_->InjectMouseEvent(event); |
| } |
| |
| void InputInjectorWin::InjectTouchEvent(const TouchEvent& event) { |
| core_->InjectTouchEvent(event); |
| } |
| |
| void InputInjectorWin::Start( |
| std::unique_ptr<protocol::ClipboardStub> client_clipboard) { |
| core_->Start(std::move(client_clipboard)); |
| } |
| |
| InputInjectorWin::Core::Core( |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) |
| : main_task_runner_(main_task_runner), |
| ui_task_runner_(ui_task_runner), |
| clipboard_(Clipboard::Create()) { |
| } |
| |
| void InputInjectorWin::Core::InjectClipboardEvent(const ClipboardEvent& event) { |
| if (!ui_task_runner_->BelongsToCurrentThread()) { |
| ui_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&Core::InjectClipboardEvent, this, event)); |
| return; |
| } |
| |
| // |clipboard_| will ignore unknown MIME-types, and verify the data's format. |
| clipboard_->InjectClipboardEvent(event); |
| } |
| |
| void InputInjectorWin::Core::InjectKeyEvent(const KeyEvent& event) { |
| if (!main_task_runner_->BelongsToCurrentThread()) { |
| main_task_runner_->PostTask(FROM_HERE, |
| base::Bind(&Core::InjectKeyEvent, this, event)); |
| return; |
| } |
| |
| HandleKey(event); |
| } |
| |
| void InputInjectorWin::Core::InjectTextEvent(const TextEvent& event) { |
| if (!main_task_runner_->BelongsToCurrentThread()) { |
| main_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&Core::InjectTextEvent, this, event)); |
| return; |
| } |
| |
| HandleText(event); |
| } |
| |
| void InputInjectorWin::Core::InjectMouseEvent(const MouseEvent& event) { |
| if (!main_task_runner_->BelongsToCurrentThread()) { |
| main_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&Core::InjectMouseEvent, this, event)); |
| return; |
| } |
| |
| HandleMouse(event); |
| } |
| |
| void InputInjectorWin::Core::InjectTouchEvent(const TouchEvent& event) { |
| if (!main_task_runner_->BelongsToCurrentThread()) { |
| main_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&Core::InjectTouchEvent, this, event)); |
| return; |
| } |
| |
| HandleTouch(event); |
| } |
| |
| void InputInjectorWin::Core::Start( |
| std::unique_ptr<protocol::ClipboardStub> client_clipboard) { |
| if (!ui_task_runner_->BelongsToCurrentThread()) { |
| ui_task_runner_->PostTask( |
| FROM_HERE, |
| base::Bind(&Core::Start, this, base::Passed(&client_clipboard))); |
| return; |
| } |
| |
| clipboard_->Start(std::move(client_clipboard)); |
| touch_injector_.reset(new TouchInjectorWin()); |
| touch_injector_->Init(); |
| } |
| |
| void InputInjectorWin::Core::Stop() { |
| if (!ui_task_runner_->BelongsToCurrentThread()) { |
| ui_task_runner_->PostTask(FROM_HERE, base::Bind(&Core::Stop, this)); |
| return; |
| } |
| |
| clipboard_.reset(); |
| touch_injector_->Deinitialize(); |
| } |
| |
| InputInjectorWin::Core::~Core() {} |
| |
| void InputInjectorWin::Core::HandleKey(const KeyEvent& event) { |
| // HostEventDispatcher should filter events missing the pressed field. |
| DCHECK(event.has_pressed() && event.has_usb_keycode()); |
| |
| // Reset the system idle suspend timeout. |
| SetThreadExecutionState(ES_SYSTEM_REQUIRED); |
| |
| int scancode = |
| ui::KeycodeConverter::UsbKeycodeToNativeKeycode(event.usb_keycode()); |
| VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode() |
| << " to scancode: " << scancode << std::dec; |
| |
| // Ignore events which can't be mapped. |
| if (scancode == ui::KeycodeConverter::InvalidNativeKeycode()) |
| return; |
| |
| uint32_t flags = KEYEVENTF_SCANCODE | (event.pressed() ? 0 : KEYEVENTF_KEYUP); |
| SendKeyboardInput(flags, scancode); |
| } |
| |
| void InputInjectorWin::Core::HandleText(const TextEvent& event) { |
| // HostEventDispatcher should filter events missing the pressed field. |
| DCHECK(event.has_text()); |
| |
| base::string16 text = base::UTF8ToUTF16(event.text()); |
| for (base::string16::const_iterator it = text.begin(); |
| it != text.end(); ++it) { |
| SendKeyboardInput(KEYEVENTF_UNICODE, *it); |
| SendKeyboardInput(KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, *it); |
| } |
| } |
| |
| void InputInjectorWin::Core::HandleMouse(const MouseEvent& event) { |
| // Reset the system idle suspend timeout. |
| SetThreadExecutionState(ES_SYSTEM_REQUIRED); |
| |
| std::vector<INPUT> inputs; |
| ParseMouseMoveEvent(event, &inputs); |
| ParseMouseClickEvent(event, &inputs); |
| ParseMouseWheelEvent(event, &inputs); |
| |
| if (!inputs.empty()) { |
| if (SendInput(inputs.size(), inputs.data(), sizeof(INPUT)) != inputs.size()) |
| PLOG(ERROR) << "Failed to inject a mouse event"; |
| } |
| } |
| |
| void InputInjectorWin::Core::HandleTouch(const TouchEvent& event) { |
| DCHECK(touch_injector_); |
| touch_injector_->InjectTouchEvent(event); |
| } |
| |
| } // namespace |
| |
| // static |
| std::unique_ptr<InputInjector> InputInjector::Create( |
| scoped_refptr<base::SingleThreadTaskRunner> main_task_runner, |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { |
| return base::WrapUnique( |
| new InputInjectorWin(main_task_runner, ui_task_runner)); |
| } |
| |
| // static |
| bool InputInjector::SupportsTouchEvents() { |
| return base::win::GetVersion() >= base::win::VERSION_WIN8; |
| } |
| |
| } // namespace remoting |