// 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_linux.h" #include #include #define XK_MISCELLANY #include #include "base/basictypes.h" #include "base/callback.h" #include "base/logging.h" #include "base/posix/eintr_wrapper.h" #include "remoting/host/mouse_move_observer.h" #include "third_party/skia/include/core/SkPoint.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 #include #include namespace { struct scoped_x_record_context { scoped_x_record_context() : data_channel(NULL), context(0) { range[0] = range[1] = NULL; } ~scoped_x_record_context() { if (range[0]) XFree(range[0]); if (range[1]) XFree(range[1]); if (context) XRecordFreeContext(data_channel, context); if (data_channel) XCloseDisplay(data_channel); } Display* data_channel; XRecordRange* range[2]; XRecordContext context; }; } // namespace namespace remoting { static void ProcessReply(XPointer thread, XRecordInterceptData* data) { if (data->category == XRecordFromServer) { xEvent* event = reinterpret_cast(data->data); if (event->u.u.type == MotionNotify) { SkIPoint pos(SkIPoint::Make(event->u.keyButtonPointer.rootX, event->u.keyButtonPointer.rootY)); reinterpret_cast(thread)->LocalMouseMoved(pos); } else { reinterpret_cast(thread)->LocalKeyPressed( event->u.u.detail, event->u.u.type == KeyPress); } } XRecordFreeData(data); } LocalInputMonitorThread::LocalInputMonitorThread( MouseMoveObserver* mouse_move_observer, const base::Closure& disconnect_callback) : base::SimpleThread("LocalInputMonitor"), mouse_move_observer_(mouse_move_observer), disconnect_callback_(disconnect_callback), display_(NULL), alt_pressed_(false), ctrl_pressed_(false) { wakeup_pipe_[0] = -1; wakeup_pipe_[1] = -1; CHECK_EQ(pipe(wakeup_pipe_), 0); } LocalInputMonitorThread::~LocalInputMonitorThread() { close(wakeup_pipe_[0]); close(wakeup_pipe_[1]); } void LocalInputMonitorThread::Stop() { if (HANDLE_EINTR(write(wakeup_pipe_[1], "", 1)) != 1) { NOTREACHED() << "Could not write to the local input monitor wakeup pipe!"; } } void LocalInputMonitorThread::Run() { // TODO(jamiewalch): For now, just don't run the thread if the wakeup pipe // could not be created. As part of the task of cleaning up the dis/connect // actions, this should be treated as an initialization failure. if (wakeup_pipe_[0] == -1) return; // 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)); display_ = XOpenDisplay(NULL); // Inner scope needed here, because the |scoper| destructor may call into // LocalKeyPressed() which needs |display_| to be still open. { scoped_x_record_context scoper; scoper.data_channel = XOpenDisplay(NULL); if (!display_ || !scoper.data_channel) { 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; } scoper.range[0] = XRecordAllocRange(); scoper.range[1] = XRecordAllocRange(); if (!scoper.range[0] || !scoper.range[1]) { LOG(ERROR) << "XRecordAllocRange failed."; return; } scoper.range[0]->device_events.first = MotionNotify; scoper.range[0]->device_events.last = MotionNotify; scoper.range[1]->device_events.first = KeyPress; scoper.range[1]->device_events.last = KeyRelease; XRecordClientSpec client_spec = XRecordAllClients; scoper.context = XRecordCreateContext( scoper.data_channel, 0, &client_spec, 1, scoper.range, arraysize(scoper.range)); if (!scoper.context) { LOG(ERROR) << "XRecordCreateContext failed."; return; } if (!XRecordEnableContextAsync(scoper.data_channel, scoper.context, ProcessReply, reinterpret_cast(this))) { LOG(ERROR) << "XRecordEnableContextAsync failed."; return; } bool stopped = false; while (!stopped) { while (XPending(scoper.data_channel)) { XEvent ev; XNextEvent(scoper.data_channel, &ev); } fd_set read_fs; FD_ZERO(&read_fs); FD_SET(ConnectionNumber(scoper.data_channel), &read_fs); FD_SET(wakeup_pipe_[0], &read_fs); select(FD_SETSIZE, &read_fs, NULL, NULL, NULL); stopped = FD_ISSET(wakeup_pipe_[0], &read_fs); } // 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. XRecordDisableContext(display_, scoper.context); XFlush(display_); } XCloseDisplay(display_); display_ = NULL; } void LocalInputMonitorThread::LocalMouseMoved(const SkIPoint& pos) { mouse_move_observer_->OnLocalMouseMoved(pos); } void LocalInputMonitorThread::LocalKeyPressed(int key_code, bool down) { KeySym key_sym = XkbKeycodeToKeysym(display_, key_code, 0, 0); if (key_sym == XK_Control_L || key_sym == XK_Control_R) { ctrl_pressed_ = down; } else if (key_sym == XK_Alt_L || key_sym == XK_Alt_R) { alt_pressed_ = down; } else if (alt_pressed_ && ctrl_pressed_ && key_sym == XK_Escape && down) { disconnect_callback_.Run(); } } } // namespace remoting