Home | History | Annotate | Download | only in desktop_capture
      1 /*
      2  *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
      3  *
      4  *  Use of this source code is governed by a BSD-style license
      5  *  that can be found in the LICENSE file in the root of the source
      6  *  tree. An additional intellectual property rights grant can be found
      7  *  in the file PATENTS.  All contributing project authors may
      8  *  be found in the AUTHORS file in the root of the source tree.
      9  */
     10 
     11 #include "webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
     12 
     13 #include <X11/extensions/Xfixes.h>
     14 #include <X11/Xlib.h>
     15 #include <X11/Xutil.h>
     16 
     17 #include "webrtc/base/scoped_ptr.h"
     18 #include "webrtc/modules/desktop_capture/desktop_capture_options.h"
     19 #include "webrtc/modules/desktop_capture/desktop_frame.h"
     20 #include "webrtc/modules/desktop_capture/mouse_cursor.h"
     21 #include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
     22 #include "webrtc/system_wrappers/include/logging.h"
     23 
     24 namespace {
     25 
     26 // WindowCapturer returns window IDs of X11 windows with WM_STATE attribute.
     27 // These windows may not be immediate children of the root window, because
     28 // window managers may re-parent them to add decorations. However,
     29 // XQueryPointer() expects to be passed children of the root. This function
     30 // searches up the list of the windows to find the root child that corresponds
     31 // to |window|.
     32 Window GetTopLevelWindow(Display* display, Window window) {
     33   while (true) {
     34     // If the window is in WithdrawnState then look at all of its children.
     35     ::Window root, parent;
     36     ::Window *children;
     37     unsigned int num_children;
     38     if (!XQueryTree(display, window, &root, &parent, &children,
     39                     &num_children)) {
     40       LOG(LS_ERROR) << "Failed to query for child windows although window"
     41                     << "does not have a valid WM_STATE.";
     42       return None;
     43     }
     44     if (children)
     45       XFree(children);
     46 
     47     if (parent == root)
     48       break;
     49 
     50     window = parent;
     51   }
     52 
     53   return window;
     54 }
     55 
     56 }  // namespace
     57 
     58 namespace webrtc {
     59 
     60 class MouseCursorMonitorX11 : public MouseCursorMonitor,
     61                               public SharedXDisplay::XEventHandler {
     62  public:
     63   MouseCursorMonitorX11(const DesktopCaptureOptions& options, Window window);
     64   virtual ~MouseCursorMonitorX11();
     65 
     66   void Init(Callback* callback, Mode mode) override;
     67   void Capture() override;
     68 
     69  private:
     70   // SharedXDisplay::XEventHandler interface.
     71   bool HandleXEvent(const XEvent& event) override;
     72 
     73   Display* display() { return x_display_->display(); }
     74 
     75   // Captures current cursor shape and stores it in |cursor_shape_|.
     76   void CaptureCursor();
     77 
     78   rtc::scoped_refptr<SharedXDisplay> x_display_;
     79   Callback* callback_;
     80   Mode mode_;
     81   Window window_;
     82 
     83   bool have_xfixes_;
     84   int xfixes_event_base_;
     85   int xfixes_error_base_;
     86 
     87   rtc::scoped_ptr<MouseCursor> cursor_shape_;
     88 };
     89 
     90 MouseCursorMonitorX11::MouseCursorMonitorX11(
     91     const DesktopCaptureOptions& options,
     92     Window window)
     93     : x_display_(options.x_display()),
     94       callback_(NULL),
     95       mode_(SHAPE_AND_POSITION),
     96       window_(window),
     97       have_xfixes_(false),
     98       xfixes_event_base_(-1),
     99       xfixes_error_base_(-1) {}
    100 
    101 MouseCursorMonitorX11::~MouseCursorMonitorX11() {
    102   if (have_xfixes_) {
    103     x_display_->RemoveEventHandler(xfixes_event_base_ + XFixesCursorNotify,
    104                                    this);
    105   }
    106 }
    107 
    108 void MouseCursorMonitorX11::Init(Callback* callback, Mode mode) {
    109   // Init can be called only once per instance of MouseCursorMonitor.
    110   assert(!callback_);
    111   assert(callback);
    112 
    113   callback_ = callback;
    114   mode_ = mode;
    115 
    116   have_xfixes_ =
    117       XFixesQueryExtension(display(), &xfixes_event_base_, &xfixes_error_base_);
    118 
    119   if (have_xfixes_) {
    120     // Register for changes to the cursor shape.
    121     XFixesSelectCursorInput(display(), window_, XFixesDisplayCursorNotifyMask);
    122     x_display_->AddEventHandler(xfixes_event_base_ + XFixesCursorNotify, this);
    123 
    124     CaptureCursor();
    125   } else {
    126     LOG(LS_INFO) << "X server does not support XFixes.";
    127   }
    128 }
    129 
    130 void MouseCursorMonitorX11::Capture() {
    131   assert(callback_);
    132 
    133   // Process X11 events in case XFixes has sent cursor notification.
    134   x_display_->ProcessPendingXEvents();
    135 
    136   // cursor_shape_| is set only if we were notified of a cursor shape change.
    137   if (cursor_shape_.get())
    138     callback_->OnMouseCursor(cursor_shape_.release());
    139 
    140   // Get cursor position if necessary.
    141   if (mode_ == SHAPE_AND_POSITION) {
    142     int root_x;
    143     int root_y;
    144     int win_x;
    145     int win_y;
    146     Window root_window;
    147     Window child_window;
    148     unsigned int mask;
    149 
    150     XErrorTrap error_trap(display());
    151     Bool result = XQueryPointer(display(), window_, &root_window, &child_window,
    152                                 &root_x, &root_y, &win_x, &win_y, &mask);
    153     CursorState state;
    154     if (!result || error_trap.GetLastErrorAndDisable() != 0) {
    155       state = OUTSIDE;
    156     } else {
    157       // In screen mode (window_ == root_window) the mouse is always inside.
    158       // XQueryPointer() sets |child_window| to None if the cursor is outside
    159       // |window_|.
    160       state =
    161           (window_ == root_window || child_window != None) ? INSIDE : OUTSIDE;
    162     }
    163 
    164     callback_->OnMouseCursorPosition(state,
    165                                      webrtc::DesktopVector(win_x, win_y));
    166   }
    167 }
    168 
    169 bool MouseCursorMonitorX11::HandleXEvent(const XEvent& event) {
    170   if (have_xfixes_ && event.type == xfixes_event_base_ + XFixesCursorNotify) {
    171     const XFixesCursorNotifyEvent* cursor_event =
    172         reinterpret_cast<const XFixesCursorNotifyEvent*>(&event);
    173     if (cursor_event->subtype == XFixesDisplayCursorNotify) {
    174       CaptureCursor();
    175     }
    176     // Return false, even if the event has been handled, because there might be
    177     // other listeners for cursor notifications.
    178   }
    179   return false;
    180 }
    181 
    182 void MouseCursorMonitorX11::CaptureCursor() {
    183   assert(have_xfixes_);
    184 
    185   XFixesCursorImage* img;
    186   {
    187     XErrorTrap error_trap(display());
    188     img = XFixesGetCursorImage(display());
    189     if (!img || error_trap.GetLastErrorAndDisable() != 0)
    190        return;
    191    }
    192 
    193    rtc::scoped_ptr<DesktopFrame> image(
    194        new BasicDesktopFrame(DesktopSize(img->width, img->height)));
    195 
    196   // Xlib stores 32-bit data in longs, even if longs are 64-bits long.
    197   unsigned long* src = img->pixels;
    198   uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
    199   uint32_t* dst_end = dst + (img->width * img->height);
    200   while (dst < dst_end) {
    201     *dst++ = static_cast<uint32_t>(*src++);
    202   }
    203 
    204   DesktopVector hotspot(std::min(img->width, img->xhot),
    205                         std::min(img->height, img->yhot));
    206 
    207   XFree(img);
    208 
    209   cursor_shape_.reset(new MouseCursor(image.release(), hotspot));
    210 }
    211 
    212 // static
    213 MouseCursorMonitor* MouseCursorMonitor::CreateForWindow(
    214     const DesktopCaptureOptions& options, WindowId window) {
    215   if (!options.x_display())
    216     return NULL;
    217   window = GetTopLevelWindow(options.x_display()->display(), window);
    218   if (window == None)
    219     return NULL;
    220   return new MouseCursorMonitorX11(options, window);
    221 }
    222 
    223 MouseCursorMonitor* MouseCursorMonitor::CreateForScreen(
    224     const DesktopCaptureOptions& options,
    225     ScreenId screen) {
    226   if (!options.x_display())
    227     return NULL;
    228   return new MouseCursorMonitorX11(
    229       options, DefaultRootWindow(options.x_display()->display()));
    230 }
    231 
    232 }  // namespace webrtc
    233