diff options
-rw-r--r-- | remoting/host/capturer_linux.cc | 1 | ||||
-rw-r--r-- | remoting/host/event_executor_linux.cc | 400 | ||||
-rw-r--r-- | remoting/host/event_executor_linux.h | 7 | ||||
-rw-r--r-- | remoting/remoting.gyp | 1 |
4 files changed, 405 insertions, 4 deletions
diff --git a/remoting/host/capturer_linux.cc b/remoting/host/capturer_linux.cc index 5b982e1..2f2f8f0 100644 --- a/remoting/host/capturer_linux.cc +++ b/remoting/host/capturer_linux.cc @@ -65,6 +65,7 @@ class CapturerLinuxPimpl { static const int kBytesPerPixel = 4; // Reference to containing class so we can access friend functions. + // Not owned. CapturerLinux* capturer_; // X11 graphics context. diff --git a/remoting/host/event_executor_linux.cc b/remoting/host/event_executor_linux.cc index 6f3acf2..f8212f1 100644 --- a/remoting/host/event_executor_linux.cc +++ b/remoting/host/event_executor_linux.cc @@ -4,20 +4,416 @@ #include "remoting/host/event_executor_linux.h" +#include <X11/Xlib.h> +#include <X11/keysym.h> +#include <X11/extensions/XTest.h> + +#include "base/logging.h" #include "remoting/proto/internal.pb.h" -#include "remoting/protocol/message_decoder.h" namespace remoting { +static int MouseButtonToX11ButtonNumber(MouseButton button) { + switch (button) { + case MouseButtonLeft: + return 1; + + case MouseButtonRight: + return 2; + + case MouseButtonMiddle: + return 3; + + case MouseButtonUndefined: + default: + return -1; + } +} + +// TODO(ajwong): Move this to a central keycodes translation file. +const int kPepperToX11Keysym[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_Menu, XK_Pause, + // 0x14 - 0x17 + /* VKEY_CAPITAL */ -1, XK_Kana_Shift, -1, /* VKEY_JUNJA */ -1, + // 0x18 - 0x1B + /* VKEY_FINAL */ -1, XK_Kanji, -1, XK_Escape, + // 0x1C - 0x1F + /* VKEY_CONVERT */ -1, /* VKEY_NONCONVERT */ -1, /* 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, XK_Print, XK_Execute, + // 0x2C - 0x2F + /* VKEY_SNAPSHOT */ -1, 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 + XK_0, 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_Meta_L, + // 0x5C - 0x5F + XK_Meta_R, /* VKEY_APPS */ -1, -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, /* VKEY_BROWSER_BACK */ -1, + /* VKEY_BROWSER_FORWARD */ -1, + // 0xA8 - 0xAB + /* VKEY_BROWSER_REFRESH */ -1, /* VKEY_BROWSER_STOP */ -1, + /* VKEY_BROWSER_SEARCH */ -1, /* VKEY_BROWSER_FAVORITES */ -1, + // 0xAC - 0xAF + /* VKEY_BROWSER_HOME */ -1, /* VKEY_VOLUME_MUTE */ -1, + /* VKEY_VOLUME_DOWN */ -1, /* VKEY_VOLUME_UP */ -1, + + // 0xB0 - 0xB3 + /* VKEY_MEDIA_NEXT_TRACK */ -1, /* VKEY_MEDIA_PREV_TRACK */ -1, + /* VKEY_MEDIA_STOP */ -1, /* VKEY_MEDIA_PLAY_PAUSE */ -1, + // 0xB4 - 0xB7 + /* VKEY_MEDIA_LAUNCH_MAIL */ -1, /* VKEY_MEDIA_LAUNCH_MEDIA_SELECT */ -1, + /* VKEY_MEDIA_LAUNCH_APP1 */ -1, /* VKEY_MEDIA_MEDIA_LAUNCH_APP2 */ -1, + // 0xB8 - 0xBB + -1, -1, /* VKEY_OEM_1 */ -1, /* VKEY_OEM_PLUS */ -1, + // 0xBC - 0xBF + /* VKEY_OEM_COMMA */ -1, /* VKEY_OEM_MINUS */ -1, + /* VKEY_OEM_PERIOD */ -1, /* VKEY_OEM_2 */ -1, + + // 0xC0 - 0xC3 + /* VKEY_OEM_3 */ -1, -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, /* VKEY_OEM_4 */ -1, + // 0xDC - 0xDF + /* VKEY_OEM_5 */ -1, /* VKEY_OEM_6 */ -1, /* VKEY_OEM_7 */ -1, + /* 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, +}; + +static int ChromotocolKeycodeToX11Keysym(int32_t keycode) { + if (keycode < 0 || keycode > 255) { + return -1; + } + + return kPepperToX11Keysym[keycode]; +} + +class EventExecutorLinuxPimpl { + public: + explicit EventExecutorLinuxPimpl(EventExecutorLinux* executor); + ~EventExecutorLinuxPimpl(); + + bool Init(); // TODO(ajwong): Do we really want this to be synchronous? + + void HandleInputEvent(ChromotingClientMessage* message); + + private: + void HandleMouseSetPosition(const MouseSetPositionEvent& position_event); + void HandleMouseMove(const MouseMoveEvent& move_event); + void HandleMouseWheel(const MouseWheelEvent& wheel_event); + void HandleMouseButtonDown(const MouseDownEvent& mouse_down_event); + void HandleMouseButtonUp(const MouseUpEvent& mouse_up_event); + void HandleKey(const KeyEvent& key_event); + void DeinitXlib(); + + // Reference to containing class so we can access friend functions. + // Not owned. + EventExecutorLinux* executor_; + + // X11 graphics context. + Display* display_; + GC gc_; + Window root_window_; + int width_; + int height_; + + int test_event_base_; + int test_error_base_; +}; + EventExecutorLinux::EventExecutorLinux(Capturer* capturer) - : EventExecutor(capturer) { + : EventExecutor(capturer), + pimpl_(new EventExecutorLinuxPimpl(this)) { + CHECK(pimpl_->Init()); } EventExecutorLinux::~EventExecutorLinux() { } void EventExecutorLinux::HandleInputEvent(ChromotingClientMessage* message) { + pimpl_->HandleInputEvent(message); +} + +EventExecutorLinuxPimpl::EventExecutorLinuxPimpl(EventExecutorLinux* executor) + : executor_(executor), + display_(NULL), + gc_(NULL), + root_window_(BadValue), + width_(0), + height_(0) { +} + +EventExecutorLinuxPimpl::~EventExecutorLinuxPimpl() { + DeinitXlib(); +} + +bool EventExecutorLinuxPimpl::Init() { + // TODO(ajwong): We should specify the display string we are attaching to + // in the constructor. + display_ = XOpenDisplay(NULL); + if (!display_) { + LOG(ERROR) << "Unable to open display"; + return false; + } + + root_window_ = RootWindow(display_, DefaultScreen(display_)); + if (root_window_ == BadValue) { + LOG(ERROR) << "Unable to get the root window"; + DeinitXlib(); + return false; + } + + gc_ = XCreateGC(display_, root_window_, 0, NULL); + if (gc_ == NULL) { + LOG(ERROR) << "Unable to get graphics context"; + DeinitXlib(); + 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."; + DeinitXlib(); + 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"; + DeinitXlib(); + return false; + } + + width_ = root_attr.width; + height_ = root_attr.height; + + return true; +} + +void EventExecutorLinuxPimpl::HandleInputEvent( + ChromotingClientMessage* message) { + if (message->has_mouse_set_position_event()) { + HandleMouseSetPosition(message->mouse_set_position_event()); + } else if (message->has_mouse_move_event()) { + HandleMouseMove(message->mouse_move_event()); + } else if (message->has_mouse_wheel_event()) { + HandleMouseWheel(message->mouse_wheel_event()); + } else if (message->has_mouse_down_event()) { + HandleMouseButtonDown(message->mouse_down_event()); + } else if (message->has_mouse_up_event()) { + HandleMouseButtonUp(message->mouse_up_event()); + } else if (message->has_key_event()) { + HandleKey(message->key_event()); + } delete message; } +void EventExecutorLinuxPimpl::HandleMouseSetPosition( + const MouseSetPositionEvent& position_event) { + if (position_event.x() < 0 || position_event.y() < 0 || + position_event.x() > width_ || position_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 " << position_event.x() + << "," << position_event.y(); + XTestFakeMotionEvent(display_, DefaultScreen(display_), + position_event.x(), position_event.y(), + CurrentTime); +} +void EventExecutorLinuxPimpl::HandleMouseMove( + const MouseMoveEvent& move_event) { + NOTIMPLEMENTED() << "We shouldn't be using relative moves."; +} + +void EventExecutorLinuxPimpl::HandleMouseWheel( + const MouseWheelEvent& wheel_event) { + NOTIMPLEMENTED() << "No scroll wheel support yet."; +} + +void EventExecutorLinuxPimpl::HandleMouseButtonDown( + const MouseDownEvent& mouse_down_event) { + int button_number = MouseButtonToX11ButtonNumber(mouse_down_event.button()); + + if (button_number < 0) { + LOG(WARNING) << "Ignoring unknown button type: " + << mouse_down_event.button(); + return; + } + + VLOG(3) << "Button " << mouse_down_event.button() + << " received, sending down " << button_number; + XTestFakeButtonEvent(display_, button_number, True, CurrentTime); +} + +void EventExecutorLinuxPimpl::HandleMouseButtonUp( + const MouseUpEvent& mouse_up_event) { + int button_number = MouseButtonToX11ButtonNumber(mouse_up_event.button()); + + if (button_number < 0) { + LOG(WARNING) << "Ignoring unknown button type: " + << mouse_up_event.button(); + return; + } + + VLOG(3) << "Button " << mouse_up_event.button() + << " received, sending up " << button_number; + XTestFakeButtonEvent(display_, button_number, False, CurrentTime); +} + +void EventExecutorLinuxPimpl::HandleKey(const KeyEvent& key_event) { + // TODO(ajwong): This will only work for QWERTY keyboards. + int keysym = ChromotocolKeycodeToX11Keysym(key_event.key()); + + if (keysym == -1) { + LOG(WARNING) << "Ignoring unknown key: " << key_event.key(); + 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: " << key_event.key(); + return; + } + + VLOG(3) << "Got pepper key: " << key_event.key() + << " sending keysym: " << keysym + << " to keycode: " << keycode; + XTestFakeKeyEvent(display_, keycode, key_event.pressed(), CurrentTime); +} + +void EventExecutorLinuxPimpl::DeinitXlib() { + // TODO(ajwong): We should expose a "close" or "shutdown" method. + if (gc_) { + if (!XFreeGC(display_, gc_)) { + LOG(ERROR) << "Unable to free Xlib GC"; + } + gc_ = NULL; + } + + if (display_) { + if (!XCloseDisplay(display_)) { + LOG(ERROR) << "Unable to close the Xlib Display."; + } + display_ = NULL; + } +} + } // namespace remoting diff --git a/remoting/host/event_executor_linux.h b/remoting/host/event_executor_linux.h index bdb3e73..cda6476 100644 --- a/remoting/host/event_executor_linux.h +++ b/remoting/host/event_executor_linux.h @@ -5,12 +5,13 @@ #ifndef REMOTING_HOST_EVENT_EXECUTOR_LINUX_H_ #define REMOTING_HOST_EVENT_EXECUTOR_LINUX_H_ -#include <vector> - +#include "base/scoped_ptr.h" #include "remoting/host/event_executor.h" namespace remoting { +class EventExecutorLinuxPimpl; + // A class to generate events on Linux. class EventExecutorLinux : public EventExecutor { public: @@ -20,6 +21,8 @@ class EventExecutorLinux : public EventExecutor { virtual void HandleInputEvent(ChromotingClientMessage* message); private: + scoped_ptr<EventExecutorLinuxPimpl> pimpl_; + DISALLOW_COPY_AND_ASSIGN(EventExecutorLinux); }; diff --git a/remoting/remoting.gyp b/remoting/remoting.gyp index 5f03f36..49f28a2 100644 --- a/remoting/remoting.gyp +++ b/remoting/remoting.gyp @@ -231,6 +231,7 @@ 'libraries': [ '-lX11', '-lXdamage', + '-lXtst', ], }, }], |