blob: dcfa0bda5c8c94fabcd7604eff35f8d22fc91add [file] [log] [blame]
// Copyright 2018 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/input_monitor/local_hotkey_input_monitor.h"
#include <sys/select.h>
#include <unistd.h>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/files/file_descriptor_watcher_posix.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/sequence_checker.h"
#include "base/single_thread_task_runner.h"
#include "ui/gfx/x/x11.h"
namespace remoting {
namespace {
class LocalHotkeyInputMonitorX11 : public LocalHotkeyInputMonitor {
public:
LocalHotkeyInputMonitorX11(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
base::OnceClosure disconnect_callback);
~LocalHotkeyInputMonitorX11() override;
private:
// The implementation resides in LocalHotkeyInputMonitorX11::Core class.
class Core : public base::RefCountedThreadSafe<Core> {
public:
Core(scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
base::OnceClosure disconnect_callback);
void Start();
void Stop();
private:
friend class base::RefCountedThreadSafe<Core>;
~Core();
void StartOnInputThread();
void StopOnInputThread();
// Called when there are pending X events.
void OnPendingXEvents();
// Processes key events.
void ProcessXEvent(xEvent* event);
static void ProcessReply(XPointer self, XRecordInterceptData* data);
// Task runner on which public methods of this class must be called.
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
// Task runner on which X Window events are received.
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner_;
// Used to send session disconnect requests.
base::OnceClosure disconnect_callback_;
// Controls watching X events.
std::unique_ptr<base::FileDescriptorWatcher::Controller> controller_;
// True when Alt is pressed.
bool alt_pressed_ = false;
// True when Ctrl is pressed.
bool ctrl_pressed_ = false;
Display* display_ = nullptr;
Display* x_record_display_ = nullptr;
XRecordRange* x_record_range_ = nullptr;
XRecordContext x_record_context_ = 0;
DISALLOW_COPY_AND_ASSIGN(Core);
};
scoped_refptr<Core> core_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(LocalHotkeyInputMonitorX11);
};
LocalHotkeyInputMonitorX11::LocalHotkeyInputMonitorX11(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
base::OnceClosure disconnect_callback)
: core_(new Core(caller_task_runner,
input_task_runner,
std::move(disconnect_callback))) {
core_->Start();
}
LocalHotkeyInputMonitorX11::~LocalHotkeyInputMonitorX11() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
core_->Stop();
}
LocalHotkeyInputMonitorX11::Core::Core(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
base::OnceClosure disconnect_callback)
: caller_task_runner_(caller_task_runner),
input_task_runner_(input_task_runner),
disconnect_callback_(std::move(disconnect_callback)) {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
DCHECK(disconnect_callback_);
}
void LocalHotkeyInputMonitorX11::Core::Start() {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
input_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&Core::StartOnInputThread, this));
}
void LocalHotkeyInputMonitorX11::Core::Stop() {
DCHECK(caller_task_runner_->BelongsToCurrentThread());
input_task_runner_->PostTask(FROM_HERE,
base::BindOnce(&Core::StopOnInputThread, this));
}
LocalHotkeyInputMonitorX11::Core::~Core() {
DCHECK(!display_);
DCHECK(!x_record_display_);
DCHECK(!x_record_range_);
DCHECK(!x_record_context_);
}
void LocalHotkeyInputMonitorX11::Core::StartOnInputThread() {
DCHECK(input_task_runner_->BelongsToCurrentThread());
DCHECK(!display_);
DCHECK(!x_record_display_);
DCHECK(!x_record_range_);
DCHECK(!x_record_context_);
// 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(nullptr);
x_record_display_ = XOpenDisplay(nullptr);
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;
}
x_record_range_ = XRecordAllocRange();
if (!x_record_range_) {
LOG(ERROR) << "XRecordAllocRange failed.";
return;
}
x_record_range_->device_events.first = KeyPress;
x_record_range_->device_events.last = KeyRelease;
XRecordClientSpec client_spec = XRecordAllClients;
x_record_context_ = XRecordCreateContext(x_record_display_, 0, &client_spec,
1, &x_record_range_, 1);
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;
}
// Register OnPendingXEvents() to be called every time there is
// something to read from |x_record_display_|.
controller_ = base::FileDescriptorWatcher::WatchReadable(
ConnectionNumber(x_record_display_),
base::BindRepeating(&Core::OnPendingXEvents, base::Unretained(this)));
// Fetch pending events if any.
while (XPending(x_record_display_)) {
XEvent ev;
XNextEvent(x_record_display_, &ev);
}
}
void LocalHotkeyInputMonitorX11::Core::StopOnInputThread() {
DCHECK(input_task_runner_->BelongsToCurrentThread());
// 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_);
}
controller_.reset();
if (x_record_range_) {
XFree(x_record_range_);
x_record_range_ = nullptr;
}
if (x_record_context_) {
XRecordFreeContext(x_record_display_, x_record_context_);
x_record_context_ = 0;
}
if (x_record_display_) {
XCloseDisplay(x_record_display_);
x_record_display_ = nullptr;
}
if (display_) {
XCloseDisplay(display_);
display_ = nullptr;
}
}
void LocalHotkeyInputMonitorX11::Core::OnPendingXEvents() {
DCHECK(input_task_runner_->BelongsToCurrentThread());
// Fetch pending events if any.
while (XPending(x_record_display_)) {
XEvent ev;
XNextEvent(x_record_display_, &ev);
}
}
void LocalHotkeyInputMonitorX11::Core::ProcessXEvent(xEvent* event) {
DCHECK(input_task_runner_->BelongsToCurrentThread());
// Ignore input if we've already initiated a disconnect.
if (!disconnect_callback_) {
return;
}
int key_code = event->u.u.detail;
bool down = event->u.u.type == KeyPress;
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 (key_sym == XK_Escape && down && alt_pressed_ && ctrl_pressed_) {
caller_task_runner_->PostTask(FROM_HERE, std::move(disconnect_callback_));
}
}
// static
void LocalHotkeyInputMonitorX11::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
std::unique_ptr<LocalHotkeyInputMonitor> LocalHotkeyInputMonitor::Create(
scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
base::OnceClosure disconnect_callback) {
return std::make_unique<LocalHotkeyInputMonitorX11>(
caller_task_runner, input_task_runner, std::move(disconnect_callback));
}
} // namespace remoting