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|.
GetTopLevelWindow(Display * display,Window 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
display()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
MouseCursorMonitorX11(const DesktopCaptureOptions & options,Window window)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
~MouseCursorMonitorX11()101 MouseCursorMonitorX11::~MouseCursorMonitorX11() {
102 if (have_xfixes_) {
103 x_display_->RemoveEventHandler(xfixes_event_base_ + XFixesCursorNotify,
104 this);
105 }
106 }
107
Init(Callback * callback,Mode mode)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
Capture()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
HandleXEvent(const XEvent & event)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
CaptureCursor()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
CreateForWindow(const DesktopCaptureOptions & options,WindowId window)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
CreateForScreen(const DesktopCaptureOptions & options,ScreenId screen)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