Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / media / base / user_input_monitor_linux.cc
blob55675ecb8c93c42adee35398fd4854499853c46d
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "media/base/user_input_monitor.h"
7 #include <sys/select.h>
8 #include <unistd.h>
9 #define XK_MISCELLANY
10 #include <X11/keysymdef.h>
12 #include "base/basictypes.h"
13 #include "base/bind.h"
14 #include "base/callback.h"
15 #include "base/compiler_specific.h"
16 #include "base/location.h"
17 #include "base/logging.h"
18 #include "base/message_loop/message_loop.h"
19 #include "base/message_loop/message_pump_libevent.h"
20 #include "base/single_thread_task_runner.h"
21 #include "base/synchronization/lock.h"
22 #include "media/base/keyboard_event_counter.h"
23 #include "third_party/skia/include/core/SkPoint.h"
24 #include "ui/events/keycodes/keyboard_code_conversion_x.h"
25 #include "ui/gfx/x/x11_types.h"
27 // These includes need to be later than dictated by the style guide due to
28 // Xlib header pollution, specifically the min, max, and Status macros.
29 #include <X11/XKBlib.h>
30 #include <X11/Xlibint.h>
31 #include <X11/extensions/record.h>
33 namespace media {
34 namespace {
36 // This is the actual implementation of event monitoring. It's separated from
37 // UserInputMonitorLinux since it needs to be deleted on the IO thread.
38 class UserInputMonitorLinuxCore
39 : public base::MessagePumpLibevent::Watcher,
40 public base::SupportsWeakPtr<UserInputMonitorLinuxCore>,
41 public base::MessageLoop::DestructionObserver {
42 public:
43 enum EventType {
44 MOUSE_EVENT,
45 KEYBOARD_EVENT
48 explicit UserInputMonitorLinuxCore(
49 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
50 const scoped_refptr<UserInputMonitor::MouseListenerList>&
51 mouse_listeners);
52 ~UserInputMonitorLinuxCore() override;
54 // DestructionObserver overrides.
55 void WillDestroyCurrentMessageLoop() override;
57 size_t GetKeyPressCount() const;
58 void StartMonitor(EventType type);
59 void StopMonitor(EventType type);
61 private:
62 // base::MessagePumpLibevent::Watcher interface.
63 void OnFileCanReadWithoutBlocking(int fd) override;
64 void OnFileCanWriteWithoutBlocking(int fd) override;
66 // Processes key and mouse events.
67 void ProcessXEvent(xEvent* event);
68 static void ProcessReply(XPointer self, XRecordInterceptData* data);
70 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
71 scoped_refptr<ObserverListThreadSafe<UserInputMonitor::MouseEventListener> >
72 mouse_listeners_;
75 // The following members should only be accessed on the IO thread.
77 base::MessagePumpLibevent::FileDescriptorWatcher controller_;
78 Display* x_control_display_;
79 Display* x_record_display_;
80 XRecordRange* x_record_range_[2];
81 XRecordContext x_record_context_;
82 KeyboardEventCounter counter_;
84 DISALLOW_COPY_AND_ASSIGN(UserInputMonitorLinuxCore);
87 class UserInputMonitorLinux : public UserInputMonitor {
88 public:
89 explicit UserInputMonitorLinux(
90 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner);
91 ~UserInputMonitorLinux() override;
93 // Public UserInputMonitor overrides.
94 size_t GetKeyPressCount() const override;
96 private:
97 // Private UserInputMonitor overrides.
98 void StartKeyboardMonitoring() override;
99 void StopKeyboardMonitoring() override;
100 void StartMouseMonitoring() override;
101 void StopMouseMonitoring() override;
103 scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
104 UserInputMonitorLinuxCore* core_;
106 DISALLOW_COPY_AND_ASSIGN(UserInputMonitorLinux);
109 UserInputMonitorLinuxCore::UserInputMonitorLinuxCore(
110 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
111 const scoped_refptr<UserInputMonitor::MouseListenerList>& mouse_listeners)
112 : io_task_runner_(io_task_runner),
113 mouse_listeners_(mouse_listeners),
114 x_control_display_(NULL),
115 x_record_display_(NULL),
116 x_record_context_(0) {
117 x_record_range_[0] = NULL;
118 x_record_range_[1] = NULL;
121 UserInputMonitorLinuxCore::~UserInputMonitorLinuxCore() {
122 DCHECK(!x_control_display_);
123 DCHECK(!x_record_display_);
124 DCHECK(!x_record_range_[0]);
125 DCHECK(!x_record_range_[1]);
126 DCHECK(!x_record_context_);
129 void UserInputMonitorLinuxCore::WillDestroyCurrentMessageLoop() {
130 DCHECK(io_task_runner_->BelongsToCurrentThread());
131 StopMonitor(MOUSE_EVENT);
132 StopMonitor(KEYBOARD_EVENT);
135 size_t UserInputMonitorLinuxCore::GetKeyPressCount() const {
136 return counter_.GetKeyPressCount();
139 void UserInputMonitorLinuxCore::StartMonitor(EventType type) {
140 DCHECK(io_task_runner_->BelongsToCurrentThread());
142 if (type == KEYBOARD_EVENT)
143 counter_.Reset();
145 // TODO(jamiewalch): We should pass the display in. At that point, since
146 // XRecord needs a private connection to the X Server for its data channel
147 // and both channels are used from a separate thread, we'll need to duplicate
148 // them with something like the following:
149 // XOpenDisplay(DisplayString(display));
150 if (!x_control_display_)
151 x_control_display_ = gfx::OpenNewXDisplay();
153 if (!x_record_display_)
154 x_record_display_ = gfx::OpenNewXDisplay();
156 if (!x_control_display_ || !x_record_display_) {
157 LOG(ERROR) << "Couldn't open X display";
158 StopMonitor(type);
159 return;
162 int xr_opcode, xr_event, xr_error;
163 if (!XQueryExtension(
164 x_control_display_, "RECORD", &xr_opcode, &xr_event, &xr_error)) {
165 LOG(ERROR) << "X Record extension not available.";
166 StopMonitor(type);
167 return;
170 if (!x_record_range_[type])
171 x_record_range_[type] = XRecordAllocRange();
173 if (!x_record_range_[type]) {
174 LOG(ERROR) << "XRecordAllocRange failed.";
175 StopMonitor(type);
176 return;
179 if (type == MOUSE_EVENT) {
180 x_record_range_[type]->device_events.first = MotionNotify;
181 x_record_range_[type]->device_events.last = MotionNotify;
182 } else {
183 DCHECK_EQ(KEYBOARD_EVENT, type);
184 x_record_range_[type]->device_events.first = KeyPress;
185 x_record_range_[type]->device_events.last = KeyRelease;
188 if (x_record_context_) {
189 XRecordDisableContext(x_control_display_, x_record_context_);
190 XFlush(x_control_display_);
191 XRecordFreeContext(x_record_display_, x_record_context_);
192 x_record_context_ = 0;
194 XRecordRange** record_range_to_use =
195 (x_record_range_[0] && x_record_range_[1]) ? x_record_range_
196 : &x_record_range_[type];
197 int number_of_ranges = (x_record_range_[0] && x_record_range_[1]) ? 2 : 1;
199 XRecordClientSpec client_spec = XRecordAllClients;
200 x_record_context_ = XRecordCreateContext(x_record_display_,
202 &client_spec,
204 record_range_to_use,
205 number_of_ranges);
206 if (!x_record_context_) {
207 LOG(ERROR) << "XRecordCreateContext failed.";
208 StopMonitor(type);
209 return;
212 if (!XRecordEnableContextAsync(x_record_display_,
213 x_record_context_,
214 &UserInputMonitorLinuxCore::ProcessReply,
215 reinterpret_cast<XPointer>(this))) {
216 LOG(ERROR) << "XRecordEnableContextAsync failed.";
217 StopMonitor(type);
218 return;
221 if (!x_record_range_[0] || !x_record_range_[1]) {
222 // Register OnFileCanReadWithoutBlocking() to be called every time there is
223 // something to read from |x_record_display_|.
224 base::MessageLoopForIO* message_loop = base::MessageLoopForIO::current();
225 int result =
226 message_loop->WatchFileDescriptor(ConnectionNumber(x_record_display_),
227 true,
228 base::MessageLoopForIO::WATCH_READ,
229 &controller_,
230 this);
231 if (!result) {
232 LOG(ERROR) << "Failed to create X record task.";
233 StopMonitor(type);
234 return;
237 // Start observing message loop destruction if we start monitoring the first
238 // event.
239 base::MessageLoop::current()->AddDestructionObserver(this);
242 // Fetch pending events if any.
243 OnFileCanReadWithoutBlocking(ConnectionNumber(x_record_display_));
246 void UserInputMonitorLinuxCore::StopMonitor(EventType type) {
247 DCHECK(io_task_runner_->BelongsToCurrentThread());
249 if (x_record_range_[type]) {
250 XFree(x_record_range_[type]);
251 x_record_range_[type] = NULL;
253 if (x_record_range_[0] || x_record_range_[1])
254 return;
256 // Context must be disabled via the control channel because we can't send
257 // any X protocol traffic over the data channel while it's recording.
258 if (x_record_context_) {
259 XRecordDisableContext(x_control_display_, x_record_context_);
260 XFlush(x_control_display_);
261 XRecordFreeContext(x_record_display_, x_record_context_);
262 x_record_context_ = 0;
264 controller_.StopWatchingFileDescriptor();
266 if (x_record_display_) {
267 XCloseDisplay(x_record_display_);
268 x_record_display_ = NULL;
270 if (x_control_display_) {
271 XCloseDisplay(x_control_display_);
272 x_control_display_ = NULL;
274 // Stop observing message loop destruction if no event is being monitored.
275 base::MessageLoop::current()->RemoveDestructionObserver(this);
278 void UserInputMonitorLinuxCore::OnFileCanReadWithoutBlocking(int fd) {
279 DCHECK(io_task_runner_->BelongsToCurrentThread());
280 XEvent event;
281 // Fetch pending events if any.
282 while (XPending(x_record_display_)) {
283 XNextEvent(x_record_display_, &event);
287 void UserInputMonitorLinuxCore::OnFileCanWriteWithoutBlocking(int fd) {
288 NOTREACHED();
291 void UserInputMonitorLinuxCore::ProcessXEvent(xEvent* event) {
292 DCHECK(io_task_runner_->BelongsToCurrentThread());
293 if (event->u.u.type == MotionNotify) {
294 SkIPoint position(SkIPoint::Make(event->u.keyButtonPointer.rootX,
295 event->u.keyButtonPointer.rootY));
296 mouse_listeners_->Notify(
297 FROM_HERE, &UserInputMonitor::MouseEventListener::OnMouseMoved,
298 position);
299 } else {
300 ui::EventType type;
301 if (event->u.u.type == KeyPress) {
302 type = ui::ET_KEY_PRESSED;
303 } else if (event->u.u.type == KeyRelease) {
304 type = ui::ET_KEY_RELEASED;
305 } else {
306 NOTREACHED();
307 return;
310 KeySym key_sym =
311 XkbKeycodeToKeysym(x_control_display_, event->u.u.detail, 0, 0);
312 ui::KeyboardCode key_code = ui::KeyboardCodeFromXKeysym(key_sym);
313 counter_.OnKeyboardEvent(type, key_code);
317 // static
318 void UserInputMonitorLinuxCore::ProcessReply(XPointer self,
319 XRecordInterceptData* data) {
320 if (data->category == XRecordFromServer) {
321 xEvent* event = reinterpret_cast<xEvent*>(data->data);
322 reinterpret_cast<UserInputMonitorLinuxCore*>(self)->ProcessXEvent(event);
324 XRecordFreeData(data);
328 // Implementation of UserInputMonitorLinux.
331 UserInputMonitorLinux::UserInputMonitorLinux(
332 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
333 : io_task_runner_(io_task_runner),
334 core_(new UserInputMonitorLinuxCore(io_task_runner, mouse_listeners())) {}
336 UserInputMonitorLinux::~UserInputMonitorLinux() {
337 if (!io_task_runner_->DeleteSoon(FROM_HERE, core_))
338 delete core_;
341 size_t UserInputMonitorLinux::GetKeyPressCount() const {
342 return core_->GetKeyPressCount();
345 void UserInputMonitorLinux::StartKeyboardMonitoring() {
346 io_task_runner_->PostTask(
347 FROM_HERE,
348 base::Bind(&UserInputMonitorLinuxCore::StartMonitor,
349 core_->AsWeakPtr(),
350 UserInputMonitorLinuxCore::KEYBOARD_EVENT));
353 void UserInputMonitorLinux::StopKeyboardMonitoring() {
354 io_task_runner_->PostTask(
355 FROM_HERE,
356 base::Bind(&UserInputMonitorLinuxCore::StopMonitor,
357 core_->AsWeakPtr(),
358 UserInputMonitorLinuxCore::KEYBOARD_EVENT));
361 void UserInputMonitorLinux::StartMouseMonitoring() {
362 io_task_runner_->PostTask(FROM_HERE,
363 base::Bind(&UserInputMonitorLinuxCore::StartMonitor,
364 core_->AsWeakPtr(),
365 UserInputMonitorLinuxCore::MOUSE_EVENT));
368 void UserInputMonitorLinux::StopMouseMonitoring() {
369 io_task_runner_->PostTask(FROM_HERE,
370 base::Bind(&UserInputMonitorLinuxCore::StopMonitor,
371 core_->AsWeakPtr(),
372 UserInputMonitorLinuxCore::MOUSE_EVENT));
375 } // namespace
377 scoped_ptr<UserInputMonitor> UserInputMonitor::Create(
378 const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner,
379 const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner) {
380 return scoped_ptr<UserInputMonitor>(
381 new UserInputMonitorLinux(io_task_runner));
384 } // namespace media