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/modules/desktop_capture/desktop_capture_options.h"
18#include "webrtc/modules/desktop_capture/desktop_frame.h"
19#include "webrtc/modules/desktop_capture/mouse_cursor.h"
20#include "webrtc/modules/desktop_capture/x11/x_error_trap.h"
21#include "webrtc/system_wrappers/interface/logging.h"
22#include "webrtc/system_wrappers/interface/scoped_ptr.h"
23
24namespace {
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|.
32Window 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
58namespace webrtc {
59
60class MouseCursorMonitorX11 : public MouseCursorMonitor,
61                              public SharedXDisplay::XEventHandler {
62 public:
63  MouseCursorMonitorX11(const DesktopCaptureOptions& options, Window window);
64  virtual ~MouseCursorMonitorX11();
65
66  virtual void Init(Callback* callback, Mode mode) OVERRIDE;
67  virtual void Capture() OVERRIDE;
68
69 private:
70  // SharedXDisplay::XEventHandler interface.
71  virtual 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  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  scoped_ptr<MouseCursor> cursor_shape_;
88};
89
90MouseCursorMonitorX11::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
101MouseCursorMonitorX11::~MouseCursorMonitorX11() {
102  if (have_xfixes_) {
103    x_display_->RemoveEventHandler(xfixes_event_base_ + XFixesCursorNotify,
104                                   this);
105  }
106}
107
108void 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
130void 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
169bool 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
182void MouseCursorMonitorX11::CaptureCursor() {
183  assert(have_xfixes_);
184
185  XFixesCursorImage* img = XFixesGetCursorImage(display());
186  if (!img)
187     return;
188
189  scoped_ptr<DesktopFrame> image(
190      new BasicDesktopFrame(DesktopSize(img->width, img->height)));
191
192  // Xlib stores 32-bit data in longs, even if longs are 64-bits long.
193  unsigned long* src = img->pixels;
194  uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
195  uint32_t* dst_end = dst + (img->width * img->height);
196  while (dst < dst_end) {
197    *dst++ = static_cast<uint32_t>(*src++);
198  }
199
200  DesktopVector hotspot(std::min(img->width, img->xhot),
201                        std::min(img->height, img->yhot));
202
203  XFree(img);
204
205  cursor_shape_.reset(new MouseCursor(image.release(), hotspot));
206}
207
208// static
209MouseCursorMonitor* MouseCursorMonitor::CreateForWindow(
210    const DesktopCaptureOptions& options, WindowId window) {
211  if (!options.x_display())
212    return NULL;
213  window = GetTopLevelWindow(options.x_display()->display(), window);
214  if (window == None)
215    return NULL;
216  return new MouseCursorMonitorX11(options, window);
217}
218
219MouseCursorMonitor* MouseCursorMonitor::CreateForScreen(
220    const DesktopCaptureOptions& options,
221    ScreenId screen) {
222  if (!options.x_display())
223    return NULL;
224  return new MouseCursorMonitorX11(
225      options, DefaultRootWindow(options.x_display()->display()));
226}
227
228}  // namespace webrtc
229