blob: 56ac3e43639f7a3a1192004c676e131d34e2808f [file] [log] [blame]
// Copyright (c) 2011 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/event_executor.h"
#include <X11/Xlib.h>
#include <X11/XF86keysym.h>
#include <X11/keysym.h>
#include <X11/extensions/XTest.h>
#include "base/basictypes.h"
#include "base/bind.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/message_loop.h"
#include "base/task.h"
#include "remoting/proto/internal.pb.h"
namespace remoting {
using protocol::MouseEvent;
using protocol::KeyEvent;
namespace {
// A class to generate events on Linux.
class EventExecutorLinux : public EventExecutor {
public:
EventExecutorLinux(MessageLoop* message_loop, Capturer* capturer);
virtual ~EventExecutorLinux() {};
bool Init();
virtual void InjectKeyEvent(const KeyEvent& event) OVERRIDE;
virtual void InjectMouseEvent(const MouseEvent& event) OVERRIDE;
private:
MessageLoop* message_loop_;
Capturer* capturer_;
// X11 graphics context.
Display* display_;
Window root_window_;
int width_;
int height_;
int test_event_base_;
int test_error_base_;
DISALLOW_COPY_AND_ASSIGN(EventExecutorLinux);
};
int MouseButtonToX11ButtonNumber(MouseEvent::MouseButton button) {
switch (button) {
case MouseEvent::BUTTON_LEFT:
return 1;
case MouseEvent::BUTTON_RIGHT:
return 3;
case MouseEvent::BUTTON_MIDDLE:
return 2;
case MouseEvent::BUTTON_UNDEFINED:
default:
return -1;
}
}
// Hard-coded mapping from Virtual Key codes to X11 KeySyms.
// This mapping is only valid if both client and host are using a
// US English keyboard layout.
// Because we're passing VK codes on the wire, with no Scancode,
// "extended" flag, etc, things like distinguishing left & right
// Shift keys doesn't work.
//
// TODO(wez): Replace this with something more closely tied to what
// WebInputEventFactory does on Linux/GTK, and which respects the
// host's keyboard layout (see http://crbug.com/74550 ).
const int kUsVkeyToKeysym[256] = {
// 0x00 - 0x07
-1, -1, -1, -1,
// 0x04 - 0x07
-1, -1, -1, -1,
// 0x08 - 0x0B
XK_BackSpace, XK_Tab, -1, -1,
// 0x0C - 0x0F
XK_Clear, XK_Return, -1, -1,
// 0x10 - 0x13
XK_Shift_L, XK_Control_L, XK_Alt_L, XK_Pause,
// 0x14 - 0x17
XK_Caps_Lock, XK_Kana_Shift, -1, /* VKEY_JUNJA */ -1,
// 0x18 - 0x1B
/* VKEY_FINAL */ -1, XK_Kanji, -1, XK_Escape,
// 0x1C - 0x1F
XK_Henkan, XK_Muhenkan, /* VKEY_ACCEPT */ -1, XK_Mode_switch,
// 0x20 - 0x23
XK_space, XK_Prior, XK_Next, XK_End,
// 0x24 - 0x27
XK_Home, XK_Left, XK_Up, XK_Right,
// 0x28 - 0x2B
XK_Down, XK_Select, /* VK_PRINT */ -1, XK_Execute,
// 0x2C - 0x2F
XK_Print, XK_Insert, XK_Delete, XK_Help,
// 0x30 - 0x33
XK_0, XK_1, XK_2, XK_3,
// 0x34 - 0x37
XK_4, XK_5, XK_6, XK_7,
// 0x38 - 0x3B
XK_8, XK_9, -1, -1,
// 0x3C - 0x3F
-1, -1, -1, -1,
// 0x40 - 0x43
-1, XK_A, XK_B, XK_C,
// 0x44 - 0x47
XK_D, XK_E, XK_F, XK_G,
// 0x48 - 0x4B
XK_H, XK_I, XK_J, XK_K,
// 0x4C - 0x4F
XK_L, XK_M, XK_N, XK_O,
// 0x50 - 0x53
XK_P, XK_Q, XK_R, XK_S,
// 0x54 - 0x57
XK_T, XK_U, XK_V, XK_W,
// 0x58 - 0x5B
XK_X, XK_Y, XK_Z, XK_Super_L,
// 0x5C - 0x5F
XK_Super_R, XK_Menu, -1, /* VKEY_SLEEP */ -1,
// 0x60 - 0x63
XK_KP_0, XK_KP_1, XK_KP_2, XK_KP_3,
// 0x64 - 0x67
XK_KP_4, XK_KP_5, XK_KP_6, XK_KP_7,
// 0x68 - 0x6B
XK_KP_8, XK_KP_9, XK_KP_Multiply, XK_KP_Add,
// 0x6C - 0x6F
XK_KP_Separator, XK_KP_Subtract, XK_KP_Decimal, XK_KP_Divide,
// 0x70 - 0x73
XK_F1, XK_F2, XK_F3, XK_F4,
// 0x74 - 0x77
XK_F5, XK_F6, XK_F7, XK_F8,
// 0x78 - 0x7B
XK_F9, XK_F10, XK_F11, XK_F12,
// 0x7C - 0x7F
XK_F13, XK_F14, XK_F15, XK_F16,
// 0x80 - 0x83
XK_F17, XK_F18, XK_F19, XK_F20,
// 0x84 - 0x87
XK_F21, XK_F22, XK_F23, XK_F24,
// 0x88 - 0x8B
-1, -1, -1, -1,
// 0x8C - 0x8F
-1, -1, -1, -1,
// 0x90 - 0x93
XK_Num_Lock, XK_Scroll_Lock, -1, -1,
// 0x94 - 0x97
-1, -1, -1, -1,
// 0x98 - 0x9B
-1, -1, -1, -1,
// 0x9C - 0x9F
-1, -1, -1, -1,
// 0xA0 - 0xA3
XK_Num_Lock, XK_Scroll_Lock, XK_Control_L, XK_Control_R,
// 0xA4 - 0xA7
XK_Meta_L, XK_Meta_R, XF86XK_Back, XF86XK_Forward,
// 0xA8 - 0xAB
XF86XK_Refresh, XF86XK_Stop, XF86XK_Search, XF86XK_Favorites,
// 0xAC - 0xAF
XF86XK_HomePage, XF86XK_AudioMute, XF86XK_AudioLowerVolume,
XF86XK_AudioRaiseVolume,
// 0xB0 - 0xB3
XF86XK_AudioNext, XF86XK_AudioPrev, XF86XK_AudioStop, XF86XK_AudioPause,
// 0xB4 - 0xB7
XF86XK_Mail, XF86XK_AudioMedia, XF86XK_Launch0, XF86XK_Launch1,
// 0xB8 - 0xBB
-1, -1, XK_semicolon, XK_plus,
// 0xBC - 0xBF
XK_comma, XK_minus, XK_period, XK_slash,
// 0xC0 - 0xC3
XK_grave, -1, -1, -1,
// 0xC4 - 0xC7
-1, -1, -1, -1,
// 0xC8 - 0xCB
-1, -1, -1, -1,
// 0xCC - 0xCF
-1, -1, -1, -1,
// 0xD0 - 0xD3
-1, -1, -1, -1,
// 0xD4 - 0xD7
-1, -1, -1, -1,
// 0xD8 - 0xDB
-1, -1, -1, XK_bracketleft,
// 0xDC - 0xDF
XK_backslash, XK_bracketright, XK_apostrophe,
/* VKEY_OEM_8 */ -1,
// 0xE0 - 0xE3
-1, -1, /* VKEY_OEM_102 */ -1, -1,
// 0xE4 - 0xE7
-1, /* VKEY_PROCESSKEY */ -1, -1, /* VKEY_PACKET */ -1,
// 0xE8 - 0xEB
-1, -1, -1, -1,
// 0xEC - 0xEF
-1, -1, -1, -1,
// 0xF0 - 0xF3
-1, -1, -1, -1,
// 0xF4 - 0xF7
-1, -1, /* VKEY_ATTN */ -1, /* VKEY_CRSEL */ -1,
// 0xF8 - 0xFB
/* VKEY_EXSEL */ -1, /* VKEY_EREOF */ -1, /* VKEY_PLAY */ -1,
/* VKEY_ZOOM */ -1,
// 0xFC - 0xFF
/* VKEY_NONAME */ -1, /* VKEY_PA1 */ -1, /* VKEY_OEM_CLEAR */ -1, -1
};
int ChromotocolKeycodeToX11Keysym(int32_t keycode) {
if (keycode < 0 || keycode > 255) {
return -1;
}
return kUsVkeyToKeysym[keycode];
}
EventExecutorLinux::EventExecutorLinux(MessageLoop* message_loop,
Capturer* capturer)
: message_loop_(message_loop),
capturer_(capturer),
display_(XOpenDisplay(NULL)),
root_window_(BadValue),
width_(0),
height_(0) {
}
bool EventExecutorLinux::Init() {
CHECK(display_);
root_window_ = RootWindow(display_, DefaultScreen(display_));
if (root_window_ == BadValue) {
LOG(ERROR) << "Unable to get the root window";
return false;
}
// TODO(ajwong): Do we want to check the major/minor version at all for XTest?
int major = 0;
int minor = 0;
if (!XTestQueryExtension(display_, &test_event_base_, &test_error_base_,
&major, &minor)) {
LOG(ERROR) << "Server does not support XTest.";
return false;
}
// Grab the width and height so we can figure out if mouse moves are out of
// range.
XWindowAttributes root_attr;
// TODO(ajwong): Handle resolution changes.
if (!XGetWindowAttributes(display_, root_window_, &root_attr)) {
LOG(ERROR) << "Unable to get window attributes";
return false;
}
width_ = root_attr.width;
height_ = root_attr.height;
return true;
}
void EventExecutorLinux::InjectKeyEvent(const KeyEvent& event) {
if (MessageLoop::current() != message_loop_) {
message_loop_->PostTask(
FROM_HERE,
base::Bind(&EventExecutorLinux::InjectKeyEvent, base::Unretained(this),
event));
return;
}
// TODO(ajwong): This will only work for QWERTY keyboards.
int keysym = ChromotocolKeycodeToX11Keysym(event.keycode());
if (keysym == -1) {
LOG(WARNING) << "Ignoring unknown key: " << event.keycode();
return;
}
// Translate the keysym into a keycode understandable by the X display.
int keycode = XKeysymToKeycode(display_, keysym);
if (keycode == 0) {
LOG(WARNING) << "Ignoring undefined keysym: " << keysym
<< " for key: " << event.keycode();
return;
}
VLOG(3) << "Got pepper key: " << event.keycode()
<< " sending keysym: " << keysym
<< " to keycode: " << keycode;
XTestFakeKeyEvent(display_, keycode, event.pressed(), CurrentTime);
XFlush(display_);
}
void EventExecutorLinux::InjectMouseEvent(const MouseEvent& event) {
if (MessageLoop::current() != message_loop_) {
message_loop_->PostTask(
FROM_HERE,
base::Bind(&EventExecutorLinux::InjectMouseEvent,
base::Unretained(this), event));
return;
}
if (event.has_x() && event.has_y()) {
if (event.x() < 0 || event.y() < 0 ||
event.x() > width_ || event.y() > height_) {
// A misbehaving client may send these. Drop events that are out of range.
// TODO(ajwong): How can we log this sanely? We don't want to DOS the
// server with a misbehaving client by logging like crazy.
return;
}
VLOG(3) << "Moving mouse to " << event.x()
<< "," << event.y();
XTestFakeMotionEvent(display_, DefaultScreen(display_),
event.x(), event.y(),
CurrentTime);
XFlush(display_);
}
if (event.has_button() && event.has_button_down()) {
int button_number = MouseButtonToX11ButtonNumber(event.button());
if (button_number < 0) {
LOG(WARNING) << "Ignoring unknown button type: "
<< event.button();
return;
}
VLOG(3) << "Button " << event.button()
<< " received, sending down " << button_number;
XTestFakeButtonEvent(display_, button_number, event.button_down(),
CurrentTime);
XFlush(display_);
}
if (event.has_wheel_offset_x() && event.has_wheel_offset_y()) {
NOTIMPLEMENTED() << "No scroll wheel support yet.";
}
}
} // namespace
EventExecutor* EventExecutor::Create(MessageLoop* message_loop,
Capturer* capturer) {
EventExecutorLinux* executor = new EventExecutorLinux(message_loop, capturer);
if (!executor->Init()) {
delete executor;
executor = NULL;
}
return executor;
}
} // namespace remoting