Index: media/base/user_input_monitor_linux.cc |
diff --git a/media/base/user_input_monitor_linux.cc b/media/base/user_input_monitor_linux.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..03a797fc41190b7b58a79ed23349b3b9d8bd392c |
--- /dev/null |
+++ b/media/base/user_input_monitor_linux.cc |
@@ -0,0 +1,338 @@ |
+// Copyright 2013 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 "media/base/user_input_monitor.h" |
+ |
+#include <sys/select.h> |
+#include <unistd.h> |
+#define XK_MISCELLANY |
+#include <X11/keysymdef.h> |
+ |
+#include "base/basictypes.h" |
+#include "base/bind.h" |
+#include "base/callback.h" |
+#include "base/compiler_specific.h" |
+#include "base/location.h" |
+#include "base/logging.h" |
+#include "base/memory/weak_ptr.h" |
+#include "base/message_loop/message_loop.h" |
+#include "base/message_loop/message_pump_libevent.h" |
+#include "base/posix/eintr_wrapper.h" |
+#include "base/single_thread_task_runner.h" |
+#include "base/threading/non_thread_safe.h" |
+#include "third_party/skia/include/core/SkPoint.h" |
+#include "ui/base/keycodes/keyboard_code_conversion_x.h" |
+ |
+// These includes need to be later than dictated by the style guide due to |
+// Xlib header pollution, specifically the min, max, and Status macros. |
+#include <X11/XKBlib.h> |
+#include <X11/Xlibint.h> |
+#include <X11/extensions/record.h> |
+ |
+namespace media { |
+ |
+namespace { |
+ |
+class UserInputMonitorLinux : public base::NonThreadSafe, |
+ public UserInputMonitor { |
+ public: |
+ UserInputMonitorLinux( |
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner); |
+ virtual ~UserInputMonitorLinux(); |
+ |
+ private: |
+ enum EventType { |
+ MOUSE_EVENT = 0, |
Sergey Ulanov
2013/08/13 20:12:51
nit: no need to specify values here - 0 and 1 must
|
+ KEYBOARD_EVENT = 1 |
+ }; |
+ |
+ // The actual implementation resides in UserInputMonitorLinux::Core class. |
+ // Must be called on the io_task_runner thread. |
+ class Core : public base::RefCountedThreadSafe<Core>, |
+ public base::MessagePumpLibevent::Watcher { |
+ public: |
+ typedef const base::Callback<void(const SkIPoint&)> MouseCallback; |
+ typedef base::Callback<void(ui::EventType event, ui::KeyboardCode key_code)> |
+ KeyboardCallback; |
+ Core(const MouseCallback& mouse_callback, |
+ const KeyboardCallback& keyboard_callback); |
+ |
+ void StartMonitor(EventType type); |
+ void StopMonitor(EventType type); |
+ |
+ private: |
+ friend class base::RefCountedThreadSafe<Core>; |
+ virtual ~Core(); |
+ |
+ // base::MessagePumpLibevent::Watcher interface. |
+ virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE; |
+ virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE; |
+ |
+ // Processes key and mouse events. |
+ void ProcessXEvent(xEvent* event); |
+ static void ProcessReply(XPointer self, XRecordInterceptData* data); |
+ |
+ // Used to receive base::MessagePumpLibevent::Watcher events. |
+ base::MessagePumpLibevent::FileDescriptorWatcher controller_; |
+ |
+ Display* display_; |
+ Display* x_record_display_; |
+ XRecordRange* x_record_range_[2]; |
+ XRecordContext x_record_context_; |
+ base::Callback<void(const SkIPoint&)> mouse_callback_; |
+ base::Callback<void(ui::EventType event, ui::KeyboardCode key_code)> |
+ keyboard_callback_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(Core); |
+ }; |
+ |
+ virtual void StartMouseMonitoring() OVERRIDE; |
+ virtual void StopMouseMonitoring() OVERRIDE; |
+ virtual void StartKeyboardMonitoring() OVERRIDE; |
+ virtual void StopKeyboardMonitoring() OVERRIDE; |
+ |
+ void OnMouseEvent(const SkIPoint& position); |
+ void OnKeyboardEvent(ui::EventType event, ui::KeyboardCode key_code); |
+ |
+ // Task runner on which X Window events are received. |
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; |
+ scoped_refptr<Core> core_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(UserInputMonitorLinux); |
+}; |
+ |
+UserInputMonitorLinux::UserInputMonitorLinux( |
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner) |
+ : io_task_runner_(io_task_runner), |
+ core_(new Core(base::Bind(&UserInputMonitorLinux::OnMouseEvent, |
+ base::Unretained(this)), |
+ base::Bind(&UserInputMonitorLinux::OnKeyboardEvent, |
+ base::Unretained(this)))) {} |
+ |
+UserInputMonitorLinux::~UserInputMonitorLinux() {} |
+ |
+void UserInputMonitorLinux::StartMouseMonitoring() { |
+ io_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&Core::StartMonitor, core_.get(), MOUSE_EVENT)); |
+} |
+ |
+void UserInputMonitorLinux::StopMouseMonitoring() { |
+ io_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&Core::StopMonitor, core_.get(), MOUSE_EVENT)); |
+} |
+ |
+void UserInputMonitorLinux::StartKeyboardMonitoring() { |
+ io_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&Core::StartMonitor, core_.get(), KEYBOARD_EVENT)); |
+} |
+ |
+void UserInputMonitorLinux::StopKeyboardMonitoring() { |
+ io_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&Core::StopMonitor, core_.get(), KEYBOARD_EVENT)); |
+} |
+ |
+void UserInputMonitorLinux::OnMouseEvent(const SkIPoint& position) { |
+ UserInputMonitor::OnMouseEvent(position); |
+} |
+ |
+void UserInputMonitorLinux::OnKeyboardEvent(ui::EventType event, |
+ ui::KeyboardCode key_code) { |
+ UserInputMonitor::OnKeyboardEvent(event, key_code); |
+} |
+ |
+UserInputMonitorLinux::Core::Core(const MouseCallback& mouse_callback, |
+ const KeyboardCallback& keyboard_callback) |
+ : display_(NULL), |
+ x_record_display_(NULL), |
+ x_record_context_(0), |
+ mouse_callback_(mouse_callback), |
+ keyboard_callback_(keyboard_callback) { |
+ x_record_range_[0] = NULL; |
+ x_record_range_[1] = NULL; |
+} |
+ |
+UserInputMonitorLinux::Core::~Core() { |
+ DCHECK(!display_); |
+ DCHECK(!x_record_display_); |
+ DCHECK(!x_record_range_[0]); |
+ DCHECK(!x_record_range_[1]); |
+ DCHECK(!x_record_context_); |
+} |
+ |
+void UserInputMonitorLinux::Core::StartMonitor(EventType type) { |
+ DCHECK(base::MessageLoopForIO::current()); |
+ // TODO(jamiewalch): We should pass the display in. At that point, since |
+ // XRecord needs a private connection to the X Server for its data channel |
+ // and both channels are used from a separate thread, we'll need to duplicate |
+ // them with something like the following: |
+ // XOpenDisplay(DisplayString(display)); |
+ if (!display_) |
+ display_ = XOpenDisplay(NULL); |
+ |
+ if (!x_record_display_) |
+ x_record_display_ = XOpenDisplay(NULL); |
+ |
+ if (!display_ || !x_record_display_) { |
+ LOG(ERROR) << "Couldn't open X display"; |
+ return; |
+ } |
+ |
+ int xr_opcode, xr_event, xr_error; |
+ if (!XQueryExtension(display_, "RECORD", &xr_opcode, &xr_event, &xr_error)) { |
+ LOG(ERROR) << "X Record extension not available."; |
+ return; |
+ } |
+ |
+ if (!x_record_range_[type]) |
+ x_record_range_[type] = XRecordAllocRange(); |
+ |
+ if (!x_record_range_[type]) { |
+ LOG(ERROR) << "XRecordAllocRange failed."; |
+ return; |
+ } |
+ |
+ if (type == MOUSE_EVENT) { |
+ x_record_range_[type]->device_events.first = MotionNotify; |
+ x_record_range_[type]->device_events.last = MotionNotify; |
+ } else { |
+ DCHECK_EQ(KEYBOARD_EVENT, type); |
+ x_record_range_[type]->device_events.first = KeyPress; |
+ x_record_range_[type]->device_events.last = KeyRelease; |
+ } |
+ |
+ if (x_record_context_) { |
+ XRecordDisableContext(display_, x_record_context_); |
+ XFlush(display_); |
+ XRecordFreeContext(x_record_display_, x_record_context_); |
+ x_record_context_ = 0; |
+ } |
+ XRecordRange** record_range_to_use = |
+ (x_record_range_[0] && x_record_range_[1]) ? x_record_range_ |
+ : &x_record_range_[type]; |
+ int number_of_ranges = (x_record_range_[0] && x_record_range_[1]) ? 2 : 1; |
+ |
+ XRecordClientSpec client_spec = XRecordAllClients; |
+ x_record_context_ = XRecordCreateContext(x_record_display_, |
+ 0, |
+ &client_spec, |
+ 1, |
+ record_range_to_use, |
+ number_of_ranges); |
+ if (!x_record_context_) { |
+ LOG(ERROR) << "XRecordCreateContext failed."; |
+ return; |
+ } |
+ |
+ if (!XRecordEnableContextAsync(x_record_display_, |
+ x_record_context_, |
+ &Core::ProcessReply, |
+ reinterpret_cast<XPointer>(this))) { |
+ LOG(ERROR) << "XRecordEnableContextAsync failed."; |
+ return; |
+ } |
+ |
+ if (!x_record_range_[0] || !x_record_range_[1]) { |
+ // Register OnFileCanReadWithoutBlocking() to be called every time there is |
+ // something to read from |x_record_display_|. |
+ base::MessageLoopForIO* message_loop = base::MessageLoopForIO::current(); |
+ int result = |
+ message_loop->WatchFileDescriptor(ConnectionNumber(x_record_display_), |
+ true, |
+ base::MessageLoopForIO::WATCH_READ, |
+ &controller_, |
+ this); |
+ if (!result) { |
+ LOG(ERROR) << "Failed to create X record task."; |
+ return; |
+ } |
+ } |
+ |
+ // Fetch pending events if any. |
+ OnFileCanReadWithoutBlocking(ConnectionNumber(x_record_display_)); |
+} |
+ |
+void UserInputMonitorLinux::Core::StopMonitor(EventType type) { |
+ DCHECK(base::MessageLoopForIO::current()); |
+ |
+ if (x_record_range_[type]) { |
+ XFree(x_record_range_[type]); |
+ x_record_range_[type] = NULL; |
+ } |
+ if (x_record_range_[0] || x_record_range_[1]) |
+ return; |
+ |
+ // Context must be disabled via the control channel because we can't send |
+ // any X protocol traffic over the data channel while it's recording. |
+ if (x_record_context_) { |
+ XRecordDisableContext(display_, x_record_context_); |
+ XFlush(display_); |
+ XRecordFreeContext(x_record_display_, x_record_context_); |
+ x_record_context_ = 0; |
+ |
+ controller_.StopWatchingFileDescriptor(); |
+ if (x_record_display_) { |
+ XCloseDisplay(x_record_display_); |
+ x_record_display_ = NULL; |
+ } |
+ if (display_) { |
+ XCloseDisplay(display_); |
+ display_ = NULL; |
+ } |
+ } |
+} |
+ |
+void UserInputMonitorLinux::Core::OnFileCanReadWithoutBlocking(int fd) { |
+ DCHECK(base::MessageLoopForIO::current()); |
+ XEvent event; |
+ // Fetch pending events if any. |
+ while (XPending(x_record_display_)) { |
+ XNextEvent(x_record_display_, &event); |
+ } |
+} |
+ |
+void UserInputMonitorLinux::Core::OnFileCanWriteWithoutBlocking(int fd) { |
+ NOTREACHED(); |
+} |
+ |
+void UserInputMonitorLinux::Core::ProcessXEvent(xEvent* event) { |
+ if (event->u.u.type == MotionNotify) { |
+ SkIPoint position(SkIPoint::Make(event->u.keyButtonPointer.rootX, |
+ event->u.keyButtonPointer.rootY)); |
+ mouse_callback_.Run(position); |
+ } else { |
+ ui::EventType type; |
+ if (event->u.u.type == KeyPress) { |
+ type = ui::ET_KEY_PRESSED; |
+ } else if (event->u.u.type == KeyRelease) { |
+ type = ui::ET_KEY_RELEASED; |
+ } else { |
+ NOTREACHED(); |
+ } |
+ |
+ KeySym key_sym = XkbKeycodeToKeysym(display_, event->u.u.detail, 0, 0); |
+ ui::KeyboardCode key_code = ui::KeyboardCodeFromXKeysym(key_sym); |
+ keyboard_callback_.Run(type, key_code); |
+ } |
+} |
+ |
+// static |
+void UserInputMonitorLinux::Core::ProcessReply(XPointer self, |
+ XRecordInterceptData* data) { |
+ if (data->category == XRecordFromServer) { |
+ xEvent* event = reinterpret_cast<xEvent*>(data->data); |
+ reinterpret_cast<Core*>(self)->ProcessXEvent(event); |
+ } |
+ XRecordFreeData(data); |
+} |
+ |
+} // namespace |
+ |
+scoped_ptr<UserInputMonitor> UserInputMonitor::Create( |
+ const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, |
+ const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner) { |
+ return scoped_ptr<UserInputMonitor>( |
+ new UserInputMonitorLinux(io_task_runner)); |
+} |
+ |
+} // namespace media |