Index: media/base/user_input_monitor_win.cc |
diff --git a/media/base/user_input_monitor_win.cc b/media/base/user_input_monitor_win.cc |
index 4ffad42a72d9b479d1611e4a10fea96ae41e0e5d..be3a92394525d95da3e9b39270cfc26d10ce27e5 100644 |
--- a/media/base/user_input_monitor_win.cc |
+++ b/media/base/user_input_monitor_win.cc |
@@ -4,13 +4,278 @@ |
#include "media/base/user_input_monitor.h" |
+#include "base/bind.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+#include "base/single_thread_task_runner.h" |
+#include "base/strings/stringprintf.h" |
+#include "base/win/message_window.h" |
+#include "media/base/keyboard_event_counter.h" |
+#include "third_party/skia/include/core/SkPoint.h" |
+#include "ui/base/keycodes/keyboard_code_conversion_win.h" |
+ |
namespace media { |
+namespace { |
+ |
+// From the HID Usage Tables specification. |
+const USHORT kGenericDesktopPage = 1; |
+const USHORT kMouseUsage = 2; |
+const USHORT kKeyboardUsage = 6; |
+ |
+class UserInputMonitorWin : public UserInputMonitor { |
+ public: |
+ explicit UserInputMonitorWin( |
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner); |
+ ~UserInputMonitorWin(); |
+ |
+ virtual size_t GetKeyPressCount() const OVERRIDE; |
Wez
2013/09/03 20:07:04
nit: Suggest comment "UserInputMonitor public inte
jiayl
2013/09/03 23:44:12
Done.
|
+ |
+ private: |
+ enum EventBitMask { |
+ MOUSE_EVENT_MASK = 1, |
+ KEYBOARD_EVENT_MASK = 2, |
+ }; |
+ static int NumberOfRawInputDevices(uint8 target_events); |
Wez
2013/09/03 20:07:04
nit: |target_events| -> |event_mask| to make it cl
jiayl
2013/09/03 23:44:12
Done.
|
+ |
+ virtual void StartMouseMonitoring() OVERRIDE; |
Wez
2013/09/03 20:07:04
nit: Suggest comment "UserInputMonitor private int
jiayl
2013/09/03 23:44:12
Done.
jiayl
2013/09/03 23:44:12
Done.
|
+ virtual void StopMouseMonitoring() OVERRIDE; |
+ virtual void StartKeyboardMonitoring() OVERRIDE; |
+ virtual void StopKeyboardMonitoring() OVERRIDE; |
+ |
+ // |
+ // The following methods are only called on the UI message loop. |
+ // |
+ |
+ void StartMonitor(EventBitMask type); |
+ void StopMonitor(EventBitMask type); |
+ |
+ // Handles WM_INPUT messages. |
+ LRESULT OnInput(HRAWINPUT input_handle); |
+ // Handles messages received by |window_|. |
+ bool HandleMessage(UINT message, |
+ WPARAM wparam, |
+ LPARAM lparam, |
+ LRESULT* result); |
+ RAWINPUTDEVICE* GetRawInputDevices(uint8 target_events, DWORD flags); |
Wez
2013/09/03 20:07:04
If you make this return std::vector<RAWINPUTDEVICE
jiayl
2013/09/03 23:44:12
Done.
|
+ |
+ // Task runner on which |window_| is created. |
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; |
+ |
+ // |
+ // Members only accessed on the UI thread. |
Wez
2013/09/03 20:07:04
If all the methods above are called only on the UI
jiayl
2013/09/03 23:44:12
Done.
|
+ // |
+ // Used to receive raw input. |
+ scoped_ptr<base::win::MessageWindow> window_; |
+ uint8 events_monitored_; |
+ KeyboardEventCounter counter_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(UserInputMonitorWin); |
+}; |
+ |
+UserInputMonitorWin::UserInputMonitorWin( |
+ scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) |
+ : ui_task_runner_(ui_task_runner), events_monitored_(0) {} |
+ |
+UserInputMonitorWin::~UserInputMonitorWin() { |
+ DCHECK(!window_); |
+ DCHECK(!events_monitored_); |
+} |
+ |
+size_t UserInputMonitorWin::GetKeyPressCount() const { |
+ return counter_.GetKeyPressCount(); |
+} |
+ |
+int UserInputMonitorWin::NumberOfRawInputDevices(uint8 target_events) { |
+ int number = 0; |
+ if (target_events & MOUSE_EVENT_MASK) |
+ number++; |
+ if (target_events & KEYBOARD_EVENT_MASK) |
+ number++; |
+ return number; |
+} |
+ |
+void UserInputMonitorWin::StartMouseMonitoring() { |
+ if (!ui_task_runner_->BelongsToCurrentThread()) { |
+ ui_task_runner_->PostTask(FROM_HERE, |
+ base::Bind(&UserInputMonitorWin::StartMonitor, |
+ base::Unretained(this), |
Wez
2013/09/03 20:07:04
This will use-after-free if the StartMouseMonitori
jiayl
2013/09/03 21:41:28
You are right. I was assuming the caller should ma
DaleCurtis
2013/09/03 22:59:32
Isn't it invalid for a client to call StartMouseMo
jiayl
2013/09/03 23:44:12
Done.
|
+ MOUSE_EVENT_MASK)); |
+ return; |
+ } |
+ StartMonitor(MOUSE_EVENT_MASK); |
Wez
2013/09/03 20:07:04
This path will never be hit unless the caller is a
jiayl
2013/09/03 23:44:12
Done.
|
+} |
+ |
+void UserInputMonitorWin::StopMouseMonitoring() { |
+ if (!ui_task_runner_->BelongsToCurrentThread()) { |
+ ui_task_runner_->PostTask(FROM_HERE, |
+ base::Bind(&UserInputMonitorWin::StopMonitor, |
+ base::Unretained(this), |
+ MOUSE_EVENT_MASK)); |
+ return; |
+ } |
+ StopMonitor(MOUSE_EVENT_MASK); |
+} |
+ |
+void UserInputMonitorWin::StartKeyboardMonitoring() { |
+ if (!ui_task_runner_->BelongsToCurrentThread()) { |
+ ui_task_runner_->PostTask(FROM_HERE, |
+ base::Bind(&UserInputMonitorWin::StartMonitor, |
+ base::Unretained(this), |
+ KEYBOARD_EVENT_MASK)); |
+ return; |
+ } |
+ StartMonitor(KEYBOARD_EVENT_MASK); |
+} |
+ |
+void UserInputMonitorWin::StopKeyboardMonitoring() { |
+ if (!ui_task_runner_->BelongsToCurrentThread()) { |
+ ui_task_runner_->PostTask(FROM_HERE, |
+ base::Bind(&UserInputMonitorWin::StopMonitor, |
+ base::Unretained(this), |
+ KEYBOARD_EVENT_MASK)); |
+ return; |
+ } |
+ StopMonitor(KEYBOARD_EVENT_MASK); |
+} |
+ |
+void UserInputMonitorWin::StartMonitor(EventBitMask type) { |
+ DCHECK(ui_task_runner_->BelongsToCurrentThread()); |
Wez
2013/09/03 20:07:04
nit: I prefer pre-condition DCHECKs to be followed
jiayl
2013/09/03 23:44:12
Done.
|
+ if (events_monitored_ & type) |
+ return; |
+ |
+ if (type == KEYBOARD_EVENT_MASK) |
+ counter_.Reset(); |
+ |
+ events_monitored_ |= type; |
+ |
+ window_.reset(new base::win::MessageWindow()); |
Wez
2013/09/03 20:07:04
Why re-create the window every time a new event is
jiayl
2013/09/03 23:44:12
Done.
|
+ if (!window_->Create(base::Bind(&UserInputMonitorWin::HandleMessage, |
+ base::Unretained(this)))) { |
+ LOG_GETLASTERROR(ERROR) << "Failed to create the raw input window"; |
+ window_.reset(); |
+ } |
+} |
+ |
+void UserInputMonitorWin::StopMonitor(EventBitMask type) { |
+ DCHECK(ui_task_runner_->BelongsToCurrentThread()); |
+ if (!(events_monitored_ & type)) |
+ return; |
+ |
+ // Stop receiving raw input. |
+ if (window_) { |
Wez
2013/09/03 20:07:04
Clarify that |window_| may be NULL if we failed to
jiayl
2013/09/03 23:44:12
Done.
|
+ scoped_ptr<RAWINPUTDEVICE[]> devices( |
+ GetRawInputDevices(type, RIDEV_REMOVE)); |
+ |
+ // The error is harmless, ignore it. |
Wez
2013/09/03 20:07:04
Which error? Why not trap & at least LOG_SYSERR(WA
jiayl
2013/09/03 23:44:12
Done.
|
+ RegisterRawInputDevices(devices.get(), 1, sizeof(devices[0])); |
+ |
+ events_monitored_ &= ~type; |
+ if (events_monitored_ == 0) |
+ window_.reset(); |
+ } |
+} |
+ |
+LRESULT UserInputMonitorWin::OnInput(HRAWINPUT input_handle) { |
+ DCHECK(ui_task_runner_->BelongsToCurrentThread()); |
+ // Get the size of the input record. |
+ UINT size = 0; |
+ UINT result = GetRawInputData( |
+ input_handle, RID_INPUT, NULL, &size, sizeof(RAWINPUTHEADER)); |
Wez
2013/09/03 20:07:04
nit: Verify that |result| is not greater than zero
jiayl
2013/09/03 23:44:12
Done.
|
+ if (result == -1) { |
+ LOG_GETLASTERROR(ERROR) << "GetRawInputData() failed"; |
+ return 0; |
+ } |
+ |
+ // Retrieve the input record itself. |
+ scoped_ptr<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) { |
Wez
2013/09/03 20:07:04
nit: See above re verifying |result|
jiayl
2013/09/03 23:44:12
Done.
|
+ LOG_GETLASTERROR(ERROR) << "GetRawInputData() failed"; |
+ return 0; |
+ } |
+ |
+ // Notify the observer about events generated locally. |
+ if (input->header.dwType == RIM_TYPEMOUSE && input->header.hDevice != NULL) { |
+ POINT position; |
+ if (!GetCursorPos(&position)) { |
+ position.x = 0; |
+ position.y = 0; |
+ } |
+ OnMouseEvent(SkIPoint::Make(position.x, position.y)); |
+ } else if (input->header.dwType == RIM_TYPEKEYBOARD && |
+ input->header.hDevice != NULL) { |
+ ui::EventType event = (input->data.keyboard.Flags & RI_KEY_BREAK) |
+ ? ui::ET_KEY_RELEASED |
+ : ui::ET_KEY_PRESSED; |
+ ui::KeyboardCode key_code = |
+ ui::KeyboardCodeForWindowsKeyCode(input->data.keyboard.VKey); |
+ counter_.OnKeyboardEvent(event, key_code); |
+ } |
+ |
+ return DefRawInputProc(&input, 1, sizeof(RAWINPUTHEADER)); |
+} |
+ |
+bool UserInputMonitorWin::HandleMessage(UINT message, |
+ WPARAM wparam, |
+ LPARAM lparam, |
+ LRESULT* result) { |
+ DCHECK(ui_task_runner_->BelongsToCurrentThread()); |
+ switch (message) { |
+ case WM_CREATE: { |
+ // Register to receive raw mouse and/or keyboard input. |
+ scoped_ptr<RAWINPUTDEVICE[]> devices( |
+ GetRawInputDevices(events_monitored_, RIDEV_INPUTSINK)); |
+ if (RegisterRawInputDevices(devices.get(), |
Wez
2013/09/03 20:07:04
Have you checked the semantics if e.g. two separat
jiayl
2013/09/03 23:44:12
Hmm...I verified that only one window will receive
|
+ NumberOfRawInputDevices(events_monitored_), |
+ sizeof(devices[0]))) { |
+ *result = 0; |
+ } else { |
+ LOG_GETLASTERROR(ERROR) << "RegisterRawInputDevices() failed"; |
+ *result = -1; |
+ } |
+ return true; |
+ } |
+ |
+ case WM_INPUT: |
+ *result = OnInput(reinterpret_cast<HRAWINPUT>(lparam)); |
+ return true; |
+ |
+ default: |
+ return false; |
+ } |
+} |
+ |
+RAWINPUTDEVICE* UserInputMonitorWin::GetRawInputDevices(uint8 target_events, |
+ DWORD flags) { |
+ DCHECK(ui_task_runner_->BelongsToCurrentThread()); |
+ RAWINPUTDEVICE* devices = |
+ new RAWINPUTDEVICE[NumberOfRawInputDevices(target_events)]; |
+ int index = 0; |
+ if (target_events & MOUSE_EVENT_MASK) { |
+ devices[index].dwFlags = flags; |
+ devices[index].usUsagePage = kGenericDesktopPage; |
+ devices[index].usUsage = kMouseUsage; |
+ devices[index].hwndTarget = window_->hwnd(); |
+ index++; |
+ } |
+ if (target_events & KEYBOARD_EVENT_MASK) { |
+ devices[index].dwFlags = flags; |
+ devices[index].usUsagePage = kGenericDesktopPage; |
+ devices[index].usUsage = kKeyboardUsage; |
+ devices[index].hwndTarget = window_->hwnd(); |
+ index++; |
+ } |
+ return devices; |
+} |
+ |
+} // namespace |
-// TODO(jiayl): add the implementation. |
scoped_ptr<UserInputMonitor> UserInputMonitor::Create( |
- const scoped_refptr<base::SingleThreadTaskRunner>& input_task_runner, |
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, |
const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner) { |
- return scoped_ptr<UserInputMonitor>(); |
+ return scoped_ptr<UserInputMonitor>(new UserInputMonitorWin(ui_task_runner)); |
} |
} // namespace media |