diff options
author | alexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-07 08:42:20 +0000 |
---|---|---|
committer | alexeypa@chromium.org <alexeypa@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-12-07 08:42:20 +0000 |
commit | d1d9bc5bb453c6859c63b3951d7689142a620e3b (patch) | |
tree | 8a8b430a3e737dd7b7bf37675fb89b2a0094eaed /remoting | |
parent | 519d834b7fc7ce364e4b611d5f24ed3a2e44e845 (diff) | |
download | chromium_src-d1d9bc5bb453c6859c63b3951d7689142a620e3b.zip chromium_src-d1d9bc5bb453c6859c63b3951d7689142a620e3b.tar.gz chromium_src-d1d9bc5bb453c6859c63b3951d7689142a620e3b.tar.bz2 |
Use raw input to detect local mouse events.
This CL switches the local input monitor to use raw input instead of hooking low-level mouse input events (WH_MOUSE_LL). The OS can silently remove WH_MOUSE_LL hooks if it is running longer than the timeout specified in [HKCU\Control Panel\Desktop]\LowLevelHooksTimeout. This can result in the local user not being able to take control of the mouse.
Other advantades are:
- raw input is considered more efficient than low-level hooks;
- raw input is received on the UI thread meaning that the host creates one less thread.
BUG=148690
Review URL: https://chromiumcodereview.appspot.com/11419216
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@171732 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'remoting')
-rw-r--r-- | remoting/host/host_mock_objects.cc | 4 | ||||
-rw-r--r-- | remoting/host/local_input_monitor_thread_win.cc | 122 | ||||
-rw-r--r-- | remoting/host/local_input_monitor_thread_win.h | 52 | ||||
-rw-r--r-- | remoting/host/local_input_monitor_unittest.cc | 82 | ||||
-rw-r--r-- | remoting/host/local_input_monitor_win.cc | 187 | ||||
-rw-r--r-- | remoting/remoting.gyp | 3 |
6 files changed, 257 insertions, 193 deletions
diff --git a/remoting/host/host_mock_objects.cc b/remoting/host/host_mock_objects.cc index 0c441c5..8b40c6d 100644 --- a/remoting/host/host_mock_objects.cc +++ b/remoting/host/host_mock_objects.cc @@ -69,10 +69,6 @@ MockLocalInputMonitor::MockLocalInputMonitor() {} MockLocalInputMonitor::~MockLocalInputMonitor() {} -scoped_ptr<LocalInputMonitor> LocalInputMonitor::Create() { - return scoped_ptr<LocalInputMonitor>(new MockLocalInputMonitor()); -} - MockClientSessionEventHandler::MockClientSessionEventHandler() {} MockClientSessionEventHandler::~MockClientSessionEventHandler() {} diff --git a/remoting/host/local_input_monitor_thread_win.cc b/remoting/host/local_input_monitor_thread_win.cc deleted file mode 100644 index 8e08a2b9..0000000 --- a/remoting/host/local_input_monitor_thread_win.cc +++ /dev/null @@ -1,122 +0,0 @@ -// 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/local_input_monitor_thread_win.h" - -#include "base/lazy_instance.h" -#include "base/logging.h" -#include "base/synchronization/waitable_event.h" -#include "remoting/host/mouse_move_observer.h" -#include "third_party/skia/include/core/SkPoint.h" - -namespace remoting { - -namespace { -LocalInputMonitorThread* g_local_input_monitor_thread = NULL; -base::LazyInstance<base::Lock>::Leaky g_thread_lock = LAZY_INSTANCE_INITIALIZER; -} // namespace - -LocalInputMonitorThread::LocalInputMonitorThread() - : base::SimpleThread("LocalInputMonitor") { -} - -LocalInputMonitorThread::~LocalInputMonitorThread() { - DCHECK(observers_.empty()); -} - -void LocalInputMonitorThread::AddObserver( - MouseMoveObserver* mouse_move_observer) { - base::AutoLock lock(lock_); - observers_.insert(mouse_move_observer); -} - -bool LocalInputMonitorThread::RemoveObserver( - MouseMoveObserver* mouse_move_observer) { - base::AutoLock lock(lock_); - observers_.erase(mouse_move_observer); - return observers_.empty(); -} - -void LocalInputMonitorThread::Stop() { - CHECK(PostThreadMessage(tid(), WM_QUIT, 0, 0)); - Join(); -} - -void LocalInputMonitorThread::Run() { - extern HMODULE g_hModule; - HHOOK win_event_hook = SetWindowsHookEx(WH_MOUSE_LL, HandleLowLevelMouseEvent, - g_hModule, 0); - if (!win_event_hook) { - DWORD err = GetLastError(); - LOG(ERROR) << "SetWindowHookEx failed: " << err; - return; - } - - MSG msg; - BOOL result; - while ((result = GetMessage(&msg, NULL, 0, 0)) != 0) { - if (result == -1) { - DWORD err = GetLastError(); - LOG(ERROR) << "GetMessage failed: " << err; - break; - } else { - DispatchMessage(&msg); - } - } - - if (win_event_hook) { - CHECK(UnhookWindowsHookEx(win_event_hook)); - } -} - -void LocalInputMonitorThread::LocalMouseMoved(const SkIPoint& mouse_position) { - base::AutoLock lock(lock_); - for (MouseMoveObservers::const_iterator i = observers_.begin(); - i != observers_.end(); ++i) { - (*i)->OnLocalMouseMoved(mouse_position); - } -} - -LRESULT WINAPI LocalInputMonitorThread::HandleLowLevelMouseEvent( - int code, WPARAM event_type, LPARAM event_data) { - if (code == HC_ACTION) { - if (event_type == WM_MOUSEMOVE) { - PMSLLHOOKSTRUCT data = reinterpret_cast<PMSLLHOOKSTRUCT>(event_data); - if ((data->flags & LLMHF_INJECTED) == 0) { - SkIPoint mouse_position = SkIPoint::Make(data->pt.x, data->pt.y); - // |g_local_input_monitor_thread| cannot be NULL because this function - // is called from within the GetMessage call running on that thread. - DCHECK(g_local_input_monitor_thread); - g_local_input_monitor_thread->LocalMouseMoved(mouse_position); - } - } - } - return CallNextHookEx(NULL, code, event_type, event_data); -} - - -// static -void LocalInputMonitorThread::AddMouseMoveObserver( - MouseMoveObserver* mouse_move_observer) { - base::AutoLock lock(g_thread_lock.Get()); - if (!g_local_input_monitor_thread) { - g_local_input_monitor_thread = new LocalInputMonitorThread; - g_local_input_monitor_thread->Start(); - } - g_local_input_monitor_thread->AddObserver(mouse_move_observer); -} - -// static -void LocalInputMonitorThread::RemoveMouseMoveObserver( - MouseMoveObserver* mouse_move_observer) { - DCHECK(g_local_input_monitor_thread); - base::AutoLock lock(g_thread_lock.Get()); - if (g_local_input_monitor_thread->RemoveObserver(mouse_move_observer)) { - g_local_input_monitor_thread->Stop(); - delete g_local_input_monitor_thread; - g_local_input_monitor_thread = NULL; - } -} - -} // namespace remoting diff --git a/remoting/host/local_input_monitor_thread_win.h b/remoting/host/local_input_monitor_thread_win.h deleted file mode 100644 index 92687b2..0000000 --- a/remoting/host/local_input_monitor_thread_win.h +++ /dev/null @@ -1,52 +0,0 @@ -// 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_HOST_LOCAL_INPUT_MONITOR_THREAD_WIN_H_ -#define REMOTING_HOST_LOCAL_INPUT_MONITOR_THREAD_WIN_H_ - -#include <set> - -#include "base/compiler_specific.h" -#include "base/memory/ref_counted.h" -#include "base/synchronization/lock.h" -#include "base/threading/simple_thread.h" - -struct SkIPoint; - -namespace remoting { - -class MouseMoveObserver; - -class LocalInputMonitorThread : public base::SimpleThread { - public: - static void AddMouseMoveObserver(MouseMoveObserver* mouse_move_observer); - static void RemoveMouseMoveObserver(MouseMoveObserver* mouse_move_observer); - - private: - LocalInputMonitorThread(); - virtual ~LocalInputMonitorThread(); - - void AddObserver(MouseMoveObserver* mouse_move_observer); - bool RemoveObserver(MouseMoveObserver* mouse_move_observer); - - void Stop(); - - // Overridden from base::SimpleThread: - virtual void Run() OVERRIDE; - - void LocalMouseMoved(const SkIPoint& mouse_position); - static LRESULT WINAPI HandleLowLevelMouseEvent(int code, - WPARAM event_type, - LPARAM event_data); - - base::Lock lock_; - typedef std::set<MouseMoveObserver*> MouseMoveObservers; - MouseMoveObservers observers_; - - DISALLOW_COPY_AND_ASSIGN(LocalInputMonitorThread); -}; - -} // namespace remoting - -#endif // REMOTING_HOST_LOCAL_INPUT_MONITOR_THREAD_WIN_H_ diff --git a/remoting/host/local_input_monitor_unittest.cc b/remoting/host/local_input_monitor_unittest.cc new file mode 100644 index 0000000..590765e --- /dev/null +++ b/remoting/host/local_input_monitor_unittest.cc @@ -0,0 +1,82 @@ +// 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 "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/run_loop.h" +#include "remoting/base/auto_thread_task_runner.h" +#include "remoting/host/local_input_monitor.h" +#include "remoting/host/mouse_move_observer.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/skia/include/core/SkPoint.h" + +namespace remoting { + +using testing::_; +using testing::AnyNumber; + +namespace { + +class FakeMouseMoveObserver : public MouseMoveObserver { + public: + FakeMouseMoveObserver() {} + virtual ~FakeMouseMoveObserver() {} + + // Ignore mouse events to avoid breaking the test of someone moves the mouse. + void OnLocalMouseMoved(const SkIPoint&) {} + void OnDisconnectCallback() {} +}; + +} // namespace + +class LocalInputMonitorTest : public testing::Test { + public: + LocalInputMonitorTest() {} + + virtual void SetUp() OVERRIDE { + task_runner_ = new AutoThreadTaskRunner( + message_loop_.message_loop_proxy(), + base::Bind(&LocalInputMonitorTest::OnStopTaskRunner, + base::Unretained(this))); + } + + void OnStopTaskRunner() { + message_loop_.PostTask( + FROM_HERE, base::Bind(&LocalInputMonitorTest::DestroyLocalInputMonitor, + base::Unretained(this))); + } + + void DestroyLocalInputMonitor() { + if (local_input_monitor_) { + local_input_monitor_->Stop(); + local_input_monitor_.reset(); + } + + message_loop_.PostTask(FROM_HERE, run_loop_.QuitClosure()); + } + + MessageLoop message_loop_; + base::RunLoop run_loop_; + scoped_refptr<AutoThreadTaskRunner> task_runner_; + + FakeMouseMoveObserver mouse_move_observer_; + scoped_ptr<LocalInputMonitor> local_input_monitor_; +}; + +// This test is really to exercise only the creation and destruction code in +// LocalInputMonitor. +TEST_F(LocalInputMonitorTest, Basic) { + local_input_monitor_ = LocalInputMonitor::Create(); + local_input_monitor_->Start( + &mouse_move_observer_, + base::Bind(&FakeMouseMoveObserver::OnDisconnectCallback, + base::Unretained(&mouse_move_observer_))); + + task_runner_ = NULL; + run_loop_.Run(); +} + +} // namespace remoting diff --git a/remoting/host/local_input_monitor_win.cc b/remoting/host/local_input_monitor_win.cc index 56c7318..7afb392 100644 --- a/remoting/host/local_input_monitor_win.cc +++ b/remoting/host/local_input_monitor_win.cc @@ -6,14 +6,24 @@ #include "base/compiler_specific.h" #include "base/logging.h" -#include "remoting/host/chromoting_host.h" -#include "remoting/host/local_input_monitor_thread_win.h" +#include "base/stringprintf.h" +#include "base/threading/non_thread_safe.h" +#include "base/win/wrapped_window_proc.h" +#include "remoting/host/mouse_move_observer.h" +#include "third_party/skia/include/core/SkPoint.h" namespace remoting { namespace { -class LocalInputMonitorWin : public LocalInputMonitor { +const wchar_t kWindowClassFormat[] = L"Chromoting_LocalInputMonitorWin_%p"; + +// From the HID Usage Tables specification. +const USHORT kGenericDesktopPage = 1; +const USHORT kMouseUsage = 2; + +class LocalInputMonitorWin : public base::NonThreadSafe, + public LocalInputMonitor { public: LocalInputMonitorWin(); ~LocalInputMonitorWin(); @@ -23,30 +33,181 @@ class LocalInputMonitorWin : public LocalInputMonitor { virtual void Stop() OVERRIDE; private: - DISALLOW_COPY_AND_ASSIGN(LocalInputMonitorWin); + // Handles WM_CREATE messages. + LRESULT OnCreate(HWND hwnd); + + // Handles WM_INPUT messages. + LRESULT OnInput(HRAWINPUT input_handle); + + // Window procedure invoked by the OS to process window messages. + static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, + WPARAM wparam, LPARAM lparam); + + // Atom representing the input window class. + ATOM atom_; + + // Instance of the module containing the window procedure. + HINSTANCE instance_; + + // Handle of the input window. + HWND window_; - MouseMoveObserver* mouse_move_observer_; + // Observer to dispatch mouse event notifications to. + MouseMoveObserver* observer_; + + DISALLOW_COPY_AND_ASSIGN(LocalInputMonitorWin); }; LocalInputMonitorWin::LocalInputMonitorWin() - : mouse_move_observer_(NULL) { + : atom_(0), + instance_(NULL), + window_(NULL), + observer_(NULL) { } LocalInputMonitorWin::~LocalInputMonitorWin() { - DCHECK(!mouse_move_observer_); + DCHECK(CalledOnValidThread()); + DCHECK(!atom_); + DCHECK(!instance_); + DCHECK(!window_); + DCHECK(!observer_); } void LocalInputMonitorWin::Start(MouseMoveObserver* mouse_move_observer, const base::Closure& disconnect_callback) { - DCHECK(!mouse_move_observer_); - mouse_move_observer_ = mouse_move_observer; - LocalInputMonitorThread::AddMouseMoveObserver(mouse_move_observer_); + DCHECK(CalledOnValidThread()); + DCHECK(!observer_); + DCHECK(mouse_move_observer); + + observer_ = mouse_move_observer; + + // Create a window for receiving raw input. + string16 window_class_name = base::StringPrintf(kWindowClassFormat, this); + WNDCLASSEX window_class; + base::win::InitializeWindowClass( + window_class_name.c_str(), + &base::win::WrappedWindowProc<WindowProc>, + 0, 0, 0, NULL, NULL, NULL, NULL, NULL, + &window_class); + instance_ = window_class.hInstance; + atom_ = RegisterClassEx(&window_class); + if (atom_ == 0) { + LOG_GETLASTERROR(ERROR) + << "Failed to register the window class '" << window_class_name << "'"; + return; + } + + window_ = CreateWindow(MAKEINTATOM(atom_), 0, 0, 0, 0, 0, 0, HWND_MESSAGE, 0, + instance_, this); + if (window_ == NULL) { + LOG_GETLASTERROR(ERROR) << "Failed to create the raw input window"; + return; + } } void LocalInputMonitorWin::Stop() { - DCHECK(mouse_move_observer_); - LocalInputMonitorThread::RemoveMouseMoveObserver(mouse_move_observer_); - mouse_move_observer_ = NULL; + DCHECK(CalledOnValidThread()); + DCHECK(observer_); + + if (window_ != NULL) { + DestroyWindow(window_); + window_ = NULL; + } + + if (atom_ != 0) { + UnregisterClass(MAKEINTATOM(atom_), instance_); + atom_ = 0; + instance_ = NULL; + } + + observer_ = NULL; +} + +LRESULT LocalInputMonitorWin::OnCreate(HWND hwnd) { + DCHECK(CalledOnValidThread()); + + // Register to receive raw mouse input. + RAWINPUTDEVICE device = {0}; + device.dwFlags = RIDEV_INPUTSINK; + device.usUsagePage = kGenericDesktopPage; + device.usUsage = kMouseUsage; + device.hwndTarget = hwnd; + if (!RegisterRawInputDevices(&device, 1, sizeof(device))) { + LOG_GETLASTERROR(ERROR) << "RegisterRawInputDevices() failed"; + return -1; + } + + return 0; +} + +LRESULT LocalInputMonitorWin::OnInput(HRAWINPUT input_handle) { + DCHECK(CalledOnValidThread()); + + // Get the size of the input record. + UINT size = 0; + UINT result = GetRawInputData(input_handle, + RID_INPUT, + NULL, + &size, + sizeof(RAWINPUTHEADER)); + if (result == -1) { + LOG_GETLASTERROR(ERROR) << "GetRawInputData() failed"; + return 0; + } + + // Retrieve the input record itself. + scoped_array<uint8> buffer(new uint8[size]); + RAWINPUT* input = reinterpret_cast<RAWINPUT*>(buffer.get()); + result = GetRawInputData(input_handle, + RID_INPUT, + buffer.get(), + &size, + sizeof(RAWINPUTHEADER)); + if (result == -1) { + LOG_GETLASTERROR(ERROR) << "GetRawInputData() failed"; + return 0; + } + + // Notify the observer about mouse events generated locally. Remote (injected) + // mouse events do not specify a device handle (based on observed behavior). + if (input->header.dwType == RIM_TYPEMOUSE && + input->header.hDevice != NULL) { + POINT position; + if (!GetCursorPos(&position)) { + position.x = 0; + position.y = 0; + } + + observer_->OnLocalMouseMoved(SkIPoint::Make(position.x, position.y)); + } + + return DefRawInputProc(&input, 1, sizeof(RAWINPUTHEADER)); +} + +// static +LRESULT CALLBACK LocalInputMonitorWin::WindowProc( + HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) { + // Store |this| to the window's user data. + if (message == WM_CREATE) { + CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lparam); + SetLastError(ERROR_SUCCESS); + LONG_PTR result = SetWindowLongPtr( + hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(cs->lpCreateParams)); + CHECK(result != 0 || GetLastError() == ERROR_SUCCESS); + } + + LocalInputMonitorWin* self = reinterpret_cast<LocalInputMonitorWin*>( + GetWindowLongPtr(hwnd, GWLP_USERDATA)); + switch (message) { + case WM_CREATE: + return self->OnCreate(hwnd); + + case WM_INPUT: + return self->OnInput(reinterpret_cast<HRAWINPUT>(lparam)); + + default: + return DefWindowProc(hwnd, message, wparam, lparam); + } } } // namespace diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index ac64826..216a855 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -379,8 +379,6 @@ 'host/local_input_monitor_mac.mm', 'host/local_input_monitor_thread_linux.cc', 'host/local_input_monitor_thread_linux.h', - 'host/local_input_monitor_thread_win.cc', - 'host/local_input_monitor_thread_win.h', 'host/local_input_monitor_win.cc', 'host/log_to_server.cc', 'host/log_to_server.h', @@ -2201,6 +2199,7 @@ 'host/host_mock_objects.h', 'host/json_host_config_unittest.cc', 'host/linux/x_server_clipboard_unittest.cc', + 'host/local_input_monitor_unittest.cc', 'host/log_to_server_unittest.cc', 'host/pin_hash_unittest.cc', 'host/policy_hack/fake_policy_watcher.cc', |