blob: 1adb05498617a56f17ade25a1b4f7decf3aa2cf7 [file] [log] [blame]
// 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 <sys/select.h>
#include <unistd.h>
#define XK_MISCELLANY
#include <X11/keysymdef.h>
#include "base/basictypes.h"
#include "base/eintr_wrapper.h"
#include "remoting/host/chromoting_host.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 {
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<xEvent*>(data->data);
if (event->u.u.type == MotionNotify) {
SkIPoint pos(SkIPoint::Make(event->u.keyButtonPointer.rootX,
event->u.keyButtonPointer.rootY));
reinterpret_cast<LocalInputMonitorThread*>(thread)->LocalMouseMoved(pos);
} else {
reinterpret_cast<LocalInputMonitorThread*>(thread)->LocalKeyPressed(
event->u.u.detail, event->u.u.type == KeyPress);
}
}
XRecordFreeData(data);
}
LocalInputMonitorThread::LocalInputMonitorThread(ChromotingHost* host)
: base::SimpleThread("LocalInputMonitor"),
host_(host), 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<XPointer>(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) {
host_->LocalMouseMoved(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) {
host_->Shutdown(base::Closure());
}
}
} // namespace remoting