Home | History | Annotate | Download | only in host
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "remoting/host/local_input_monitor.h"
      6 
      7 #include <sys/select.h>
      8 #include <unistd.h>
      9 #define XK_MISCELLANY
     10 #include <X11/keysymdef.h>
     11 
     12 #include "base/basictypes.h"
     13 #include "base/bind.h"
     14 #include "base/callback.h"
     15 #include "base/compiler_specific.h"
     16 #include "base/location.h"
     17 #include "base/logging.h"
     18 #include "base/message_loop/message_loop.h"
     19 #include "base/message_loop/message_pump_libevent.h"
     20 #include "base/single_thread_task_runner.h"
     21 #include "base/threading/non_thread_safe.h"
     22 #include "remoting/host/client_session_control.h"
     23 #include "third_party/webrtc/modules/desktop_capture/desktop_geometry.h"
     24 
     25 // These includes need to be later than dictated by the style guide due to
     26 // Xlib header pollution, specifically the min, max, and Status macros.
     27 #include <X11/XKBlib.h>
     28 #include <X11/Xlibint.h>
     29 #include <X11/extensions/record.h>
     30 
     31 namespace remoting {
     32 
     33 namespace {
     34 
     35 class LocalInputMonitorLinux : public base::NonThreadSafe,
     36                                public LocalInputMonitor {
     37  public:
     38   LocalInputMonitorLinux(
     39       scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
     40       scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
     41       base::WeakPtr<ClientSessionControl> client_session_control);
     42   virtual ~LocalInputMonitorLinux();
     43 
     44  private:
     45   // The actual implementation resides in LocalInputMonitorLinux::Core class.
     46   class Core
     47       : public base::RefCountedThreadSafe<Core>,
     48         public base::MessagePumpLibevent::Watcher {
     49    public:
     50     Core(scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
     51          scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
     52          base::WeakPtr<ClientSessionControl> client_session_control);
     53 
     54     void Start();
     55     void Stop();
     56 
     57    private:
     58     friend class base::RefCountedThreadSafe<Core>;
     59     virtual ~Core();
     60 
     61     void StartOnInputThread();
     62     void StopOnInputThread();
     63 
     64     // base::MessagePumpLibevent::Watcher interface.
     65     virtual void OnFileCanReadWithoutBlocking(int fd) OVERRIDE;
     66     virtual void OnFileCanWriteWithoutBlocking(int fd) OVERRIDE;
     67 
     68     // Processes key and mouse events.
     69     void ProcessXEvent(xEvent* event);
     70 
     71     static void ProcessReply(XPointer self, XRecordInterceptData* data);
     72 
     73     // Task runner on which public methods of this class must be called.
     74     scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner_;
     75 
     76     // Task runner on which X Window events are received.
     77     scoped_refptr<base::SingleThreadTaskRunner> input_task_runner_;
     78 
     79     // Points to the object receiving mouse event notifications and session
     80     // disconnect requests.
     81     base::WeakPtr<ClientSessionControl> client_session_control_;
     82 
     83     // Used to receive base::MessagePumpLibevent::Watcher events.
     84     base::MessagePumpLibevent::FileDescriptorWatcher controller_;
     85 
     86     // True when Alt is pressed.
     87     bool alt_pressed_;
     88 
     89     // True when Ctrl is pressed.
     90     bool ctrl_pressed_;
     91 
     92     Display* display_;
     93     Display* x_record_display_;
     94     XRecordRange* x_record_range_[2];
     95     XRecordContext x_record_context_;
     96 
     97     DISALLOW_COPY_AND_ASSIGN(Core);
     98   };
     99 
    100   scoped_refptr<Core> core_;
    101 
    102   DISALLOW_COPY_AND_ASSIGN(LocalInputMonitorLinux);
    103 };
    104 
    105 LocalInputMonitorLinux::LocalInputMonitorLinux(
    106     scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
    107     scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
    108     base::WeakPtr<ClientSessionControl> client_session_control)
    109     : core_(new Core(caller_task_runner,
    110                      input_task_runner,
    111                      client_session_control)) {
    112   core_->Start();
    113 }
    114 
    115 LocalInputMonitorLinux::~LocalInputMonitorLinux() {
    116   core_->Stop();
    117 }
    118 
    119 LocalInputMonitorLinux::Core::Core(
    120     scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
    121     scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
    122     base::WeakPtr<ClientSessionControl> client_session_control)
    123     : caller_task_runner_(caller_task_runner),
    124       input_task_runner_(input_task_runner),
    125       client_session_control_(client_session_control),
    126       alt_pressed_(false),
    127       ctrl_pressed_(false),
    128       display_(NULL),
    129       x_record_display_(NULL),
    130       x_record_context_(0) {
    131   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    132   DCHECK(client_session_control_.get());
    133 
    134   x_record_range_[0] = NULL;
    135   x_record_range_[1] = NULL;
    136 }
    137 
    138 void LocalInputMonitorLinux::Core::Start() {
    139   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    140 
    141   input_task_runner_->PostTask(FROM_HERE,
    142                                base::Bind(&Core::StartOnInputThread, this));
    143 }
    144 
    145 void LocalInputMonitorLinux::Core::Stop() {
    146   DCHECK(caller_task_runner_->BelongsToCurrentThread());
    147 
    148   input_task_runner_->PostTask(FROM_HERE,
    149                                base::Bind(&Core::StopOnInputThread, this));
    150 }
    151 
    152 LocalInputMonitorLinux::Core::~Core() {
    153   DCHECK(!display_);
    154   DCHECK(!x_record_display_);
    155   DCHECK(!x_record_range_[0]);
    156   DCHECK(!x_record_range_[1]);
    157   DCHECK(!x_record_context_);
    158 }
    159 
    160 void LocalInputMonitorLinux::Core::StartOnInputThread() {
    161   DCHECK(input_task_runner_->BelongsToCurrentThread());
    162   DCHECK(!display_);
    163   DCHECK(!x_record_display_);
    164   DCHECK(!x_record_range_[0]);
    165   DCHECK(!x_record_range_[1]);
    166   DCHECK(!x_record_context_);
    167 
    168   // TODO(jamiewalch): We should pass the display in. At that point, since
    169   // XRecord needs a private connection to the X Server for its data channel
    170   // and both channels are used from a separate thread, we'll need to duplicate
    171   // them with something like the following:
    172   //   XOpenDisplay(DisplayString(display));
    173   display_ = XOpenDisplay(NULL);
    174   x_record_display_ = XOpenDisplay(NULL);
    175   if (!display_ || !x_record_display_) {
    176     LOG(ERROR) << "Couldn't open X display";
    177     return;
    178   }
    179 
    180   int xr_opcode, xr_event, xr_error;
    181   if (!XQueryExtension(display_, "RECORD", &xr_opcode, &xr_event, &xr_error)) {
    182     LOG(ERROR) << "X Record extension not available.";
    183     return;
    184   }
    185 
    186   x_record_range_[0] = XRecordAllocRange();
    187   x_record_range_[1] = XRecordAllocRange();
    188   if (!x_record_range_[0] || !x_record_range_[1]) {
    189     LOG(ERROR) << "XRecordAllocRange failed.";
    190     return;
    191   }
    192   x_record_range_[0]->device_events.first = MotionNotify;
    193   x_record_range_[0]->device_events.last = MotionNotify;
    194   x_record_range_[1]->device_events.first = KeyPress;
    195   x_record_range_[1]->device_events.last = KeyRelease;
    196   XRecordClientSpec client_spec = XRecordAllClients;
    197 
    198   x_record_context_ = XRecordCreateContext(
    199       x_record_display_, 0, &client_spec, 1, x_record_range_,
    200       arraysize(x_record_range_));
    201   if (!x_record_context_) {
    202     LOG(ERROR) << "XRecordCreateContext failed.";
    203     return;
    204   }
    205 
    206   if (!XRecordEnableContextAsync(x_record_display_, x_record_context_,
    207                                  &Core::ProcessReply,
    208                                  reinterpret_cast<XPointer>(this))) {
    209     LOG(ERROR) << "XRecordEnableContextAsync failed.";
    210     return;
    211   }
    212 
    213   // Register OnFileCanReadWithoutBlocking() to be called every time there is
    214   // something to read from |x_record_display_|.
    215   base::MessageLoopForIO* message_loop = base::MessageLoopForIO::current();
    216   int result =
    217       message_loop->WatchFileDescriptor(ConnectionNumber(x_record_display_),
    218                                         true,
    219                                         base::MessageLoopForIO::WATCH_READ,
    220                                         &controller_,
    221                                         this);
    222   if (!result) {
    223     LOG(ERROR) << "Failed to create X record task.";
    224     return;
    225   }
    226 
    227   // Fetch pending events if any.
    228   while (XPending(x_record_display_)) {
    229     XEvent ev;
    230     XNextEvent(x_record_display_, &ev);
    231   }
    232 }
    233 
    234 void LocalInputMonitorLinux::Core::StopOnInputThread() {
    235   DCHECK(input_task_runner_->BelongsToCurrentThread());
    236 
    237   // Context must be disabled via the control channel because we can't send
    238   // any X protocol traffic over the data channel while it's recording.
    239   if (x_record_context_) {
    240     XRecordDisableContext(display_, x_record_context_);
    241     XFlush(display_);
    242   }
    243 
    244   controller_.StopWatchingFileDescriptor();
    245 
    246   if (x_record_range_[0]) {
    247     XFree(x_record_range_[0]);
    248     x_record_range_[0] = NULL;
    249   }
    250   if (x_record_range_[1]) {
    251     XFree(x_record_range_[1]);
    252     x_record_range_[1] = NULL;
    253   }
    254   if (x_record_context_) {
    255     XRecordFreeContext(x_record_display_, x_record_context_);
    256     x_record_context_ = 0;
    257   }
    258   if (x_record_display_) {
    259     XCloseDisplay(x_record_display_);
    260     x_record_display_ = NULL;
    261   }
    262   if (display_) {
    263     XCloseDisplay(display_);
    264     display_ = NULL;
    265   }
    266 }
    267 
    268 void LocalInputMonitorLinux::Core::OnFileCanReadWithoutBlocking(int fd) {
    269   DCHECK(input_task_runner_->BelongsToCurrentThread());
    270 
    271   // Fetch pending events if any.
    272   while (XPending(x_record_display_)) {
    273     XEvent ev;
    274     XNextEvent(x_record_display_, &ev);
    275   }
    276 }
    277 
    278 void LocalInputMonitorLinux::Core::OnFileCanWriteWithoutBlocking(int fd) {
    279   NOTREACHED();
    280 }
    281 
    282 void LocalInputMonitorLinux::Core::ProcessXEvent(xEvent* event) {
    283   DCHECK(input_task_runner_->BelongsToCurrentThread());
    284 
    285   if (event->u.u.type == MotionNotify) {
    286     webrtc::DesktopVector position(event->u.keyButtonPointer.rootX,
    287                                    event->u.keyButtonPointer.rootY);
    288     caller_task_runner_->PostTask(
    289         FROM_HERE, base::Bind(&ClientSessionControl::OnLocalMouseMoved,
    290                               client_session_control_,
    291                               position));
    292   } else {
    293     int key_code = event->u.u.detail;
    294     bool down = event->u.u.type == KeyPress;
    295     KeySym key_sym = XkbKeycodeToKeysym(display_, key_code, 0, 0);
    296     if (key_sym == XK_Control_L || key_sym == XK_Control_R) {
    297       ctrl_pressed_ = down;
    298     } else if (key_sym == XK_Alt_L || key_sym == XK_Alt_R) {
    299       alt_pressed_ = down;
    300     } else if (key_sym == XK_Escape && down && alt_pressed_ && ctrl_pressed_) {
    301       caller_task_runner_->PostTask(
    302           FROM_HERE, base::Bind(&ClientSessionControl::DisconnectSession,
    303                                 client_session_control_));
    304     }
    305   }
    306 }
    307 
    308 // static
    309 void LocalInputMonitorLinux::Core::ProcessReply(XPointer self,
    310                                                 XRecordInterceptData* data) {
    311   if (data->category == XRecordFromServer) {
    312     xEvent* event = reinterpret_cast<xEvent*>(data->data);
    313     reinterpret_cast<Core*>(self)->ProcessXEvent(event);
    314   }
    315   XRecordFreeData(data);
    316 }
    317 
    318 }  // namespace
    319 
    320 scoped_ptr<LocalInputMonitor> LocalInputMonitor::Create(
    321     scoped_refptr<base::SingleThreadTaskRunner> caller_task_runner,
    322     scoped_refptr<base::SingleThreadTaskRunner> input_task_runner,
    323     scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
    324     base::WeakPtr<ClientSessionControl> client_session_control) {
    325   return scoped_ptr<LocalInputMonitor>(
    326       new LocalInputMonitorLinux(caller_task_runner,
    327                                  input_task_runner,
    328                                  client_session_control));
    329 }
    330 
    331 }  // namespace remoting
    332