// 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/event_executor.h" #include #include "base/bind.h" #include "base/compiler_specific.h" #include "base/location.h" #include "base/single_thread_task_runner.h" #include "remoting/host/capturer.h" #include "remoting/host/clipboard.h" #include "remoting/proto/event.pb.h" namespace remoting { namespace { using protocol::ClipboardEvent; using protocol::KeyEvent; using protocol::MouseEvent; // USB to XKB keycode map table. #define USB_KEYMAP(usb, xkb, win, mac) {usb, win} #include "ui/base/keycodes/usb_keycode_map.h" #undef USB_KEYMAP // A class to generate events on Windows. class EventExecutorWin : public EventExecutor { public: EventExecutorWin(scoped_refptr main_task_runner, scoped_refptr ui_task_runner, Capturer* capturer); virtual ~EventExecutorWin() {} // ClipboardStub interface. virtual void InjectClipboardEvent(const ClipboardEvent& event) OVERRIDE; // InputStub interface. virtual void InjectKeyEvent(const KeyEvent& event) OVERRIDE; virtual void InjectMouseEvent(const MouseEvent& event) OVERRIDE; // EventExecutor interface. virtual void OnSessionStarted( scoped_ptr client_clipboard) OVERRIDE; virtual void OnSessionFinished() OVERRIDE; private: HKL GetForegroundKeyboardLayout(); void HandleKey(const KeyEvent& event); void HandleMouse(const MouseEvent& event); scoped_refptr main_task_runner_; scoped_refptr ui_task_runner_; Capturer* capturer_; scoped_ptr clipboard_; DISALLOW_COPY_AND_ASSIGN(EventExecutorWin); }; EventExecutorWin::EventExecutorWin( scoped_refptr main_task_runner, scoped_refptr ui_task_runner, Capturer* capturer) : main_task_runner_(main_task_runner), ui_task_runner_(ui_task_runner), capturer_(capturer), clipboard_(Clipboard::Create()) { } void EventExecutorWin::InjectClipboardEvent(const ClipboardEvent& event) { if (!ui_task_runner_->BelongsToCurrentThread()) { ui_task_runner_->PostTask( FROM_HERE, base::Bind(&EventExecutorWin::InjectClipboardEvent, base::Unretained(this), event)); return; } clipboard_->InjectClipboardEvent(event); } void EventExecutorWin::InjectKeyEvent(const KeyEvent& event) { if (!main_task_runner_->BelongsToCurrentThread()) { main_task_runner_->PostTask( FROM_HERE, base::Bind(&EventExecutorWin::InjectKeyEvent, base::Unretained(this), event)); return; } HandleKey(event); } void EventExecutorWin::InjectMouseEvent(const MouseEvent& event) { if (!main_task_runner_->BelongsToCurrentThread()) { main_task_runner_->PostTask( FROM_HERE, base::Bind(&EventExecutorWin::InjectMouseEvent, base::Unretained(this), event)); return; } HandleMouse(event); } void EventExecutorWin::OnSessionStarted( scoped_ptr client_clipboard) { if (!ui_task_runner_->BelongsToCurrentThread()) { ui_task_runner_->PostTask( FROM_HERE, base::Bind(&EventExecutorWin::OnSessionStarted, base::Unretained(this), base::Passed(&client_clipboard))); return; } clipboard_->Start(client_clipboard.Pass()); } void EventExecutorWin::OnSessionFinished() { if (!ui_task_runner_->BelongsToCurrentThread()) { ui_task_runner_->PostTask( FROM_HERE, base::Bind(&EventExecutorWin::OnSessionFinished, base::Unretained(this))); return; } clipboard_->Stop(); } HKL EventExecutorWin::GetForegroundKeyboardLayout() { HKL layout = 0; // Can return NULL if a window is losing focus. HWND foreground = GetForegroundWindow(); if (foreground) { // Can return 0 if the window no longer exists. DWORD thread_id = GetWindowThreadProcessId(foreground, 0); if (thread_id) { // Can return 0 if the thread no longer exists, or if we're // running on Windows Vista and the window is a command-prompt. layout = GetKeyboardLayout(thread_id); } } // If we couldn't determine a layout then use the system default. if (!layout) { SystemParametersInfo(SPI_GETDEFAULTINPUTLANG, 0, &layout, 0); } return layout; } void EventExecutorWin::HandleKey(const KeyEvent& event) { // HostEventDispatcher should filter events missing the pressed field. DCHECK(event.has_pressed()); // Reset the system idle suspend timeout. SetThreadExecutionState(ES_SYSTEM_REQUIRED); // The mapping between scancodes and VKEY values depends on the foreground // window's current keyboard layout. HKL layout = GetForegroundKeyboardLayout(); // Populate the 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 = event.pressed() ? 0 : KEYEVENTF_KEYUP; int scancode = kInvalidKeycode; if (event.has_usb_keycode()) { // If the event contains a USB-style code, map to a Windows scancode, and // set a flag to have Windows look up the corresponding VK code. input.ki.dwFlags |= KEYEVENTF_SCANCODE; scancode = UsbKeycodeToNativeKeycode(event.usb_keycode()); VLOG(3) << "Converting USB keycode: " << std::hex << event.usb_keycode() << " to scancode: " << scancode << std::dec; } else { // If the event provides only a VKEY then use it, and map to the scancode. input.ki.wVk = event.keycode(); scancode = MapVirtualKeyEx(event.keycode(), MAPVK_VK_TO_VSC_EX, layout); VLOG(3) << "Converting VKEY: " << std::hex << event.keycode() << " to scancode: " << scancode << std::dec; } // Ignore events with no VK- or USB-keycode, or which can't be mapped. if (scancode == kInvalidKeycode) return; // 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 = scancode & 0xFF; if ((scancode & 0xFF00) != 0x0000) { input.ki.dwFlags |= KEYEVENTF_EXTENDEDKEY; } if (SendInput(1, &input, sizeof(INPUT)) == 0) { LOG_GETLASTERROR(ERROR) << "Failed to inject a key event"; } } void EventExecutorWin::HandleMouse(const MouseEvent& event) { // Reset the system idle suspend timeout. SetThreadExecutionState(ES_SYSTEM_REQUIRED); // TODO(garykac) Collapse mouse (x,y) and button events into a single // input event when possible. if (event.has_x() && event.has_y()) { int x = event.x(); int y = event.y(); INPUT input; input.type = INPUT_MOUSE; input.mi.time = 0; SkISize screen_size = capturer_->size_most_recent(); if ((screen_size.width() > 1) && (screen_size.height() > 1)) { input.mi.dx = static_cast((x * 65535) / (screen_size.width() - 1)); input.mi.dy = static_cast((y * 65535) / (screen_size.height() - 1)); input.mi.dwFlags = MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE; if (SendInput(1, &input, sizeof(INPUT)) == 0) { LOG_GETLASTERROR(ERROR) << "Failed to inject a mouse move event"; } } } if (event.has_wheel_offset_x() && event.has_wheel_offset_y()) { INPUT wheel; wheel.type = INPUT_MOUSE; wheel.mi.time = 0; int dx = event.wheel_offset_x(); int dy = event.wheel_offset_y(); if (dx != 0) { wheel.mi.mouseData = dx * WHEEL_DELTA; wheel.mi.dwFlags = MOUSEEVENTF_HWHEEL; if (SendInput(1, &wheel, sizeof(INPUT)) == 0) { LOG_GETLASTERROR(ERROR) << "Failed to inject a mouse wheel(x) event"; } } if (dy != 0) { wheel.mi.mouseData = dy * WHEEL_DELTA; wheel.mi.dwFlags = MOUSEEVENTF_WHEEL; if (SendInput(1, &wheel, sizeof(INPUT)) == 0) { LOG_GETLASTERROR(ERROR) << "Failed to inject a mouse wheel(y) event"; } } } if (event.has_button() && event.has_button_down()) { INPUT button_event; button_event.type = INPUT_MOUSE; button_event.mi.time = 0; button_event.mi.dx = 0; button_event.mi.dy = 0; MouseEvent::MouseButton button = event.button(); bool down = event.button_down(); if (button == MouseEvent::BUTTON_LEFT) { button_event.mi.dwFlags = down ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; } else if (button == MouseEvent::BUTTON_MIDDLE) { button_event.mi.dwFlags = down ? MOUSEEVENTF_MIDDLEDOWN : MOUSEEVENTF_MIDDLEUP; } else if (button == MouseEvent::BUTTON_RIGHT) { button_event.mi.dwFlags = down ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP; } else { button_event.mi.dwFlags = down ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP; } if (SendInput(1, &button_event, sizeof(INPUT)) == 0) { LOG_GETLASTERROR(ERROR) << "Failed to inject a mouse button event"; } } } } // namespace scoped_ptr EventExecutor::Create( scoped_refptr main_task_runner, scoped_refptr ui_task_runner, Capturer* capturer) { return scoped_ptr( new EventExecutorWin(main_task_runner, ui_task_runner, capturer)); } } // namespace remoting