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 "modules/desktop_capture/linux/mouse_cursor_monitor_x11.h"
12
13 #include <X11/Xlib.h>
14 #include <X11/extensions/Xfixes.h>
15 #include <X11/extensions/xfixeswire.h>
16 #include <stddef.h>
17 #include <stdint.h>
18
19 #include <algorithm>
20 #include <memory>
21
22 #include "modules/desktop_capture/desktop_capture_options.h"
23 #include "modules/desktop_capture/desktop_capture_types.h"
24 #include "modules/desktop_capture/desktop_frame.h"
25 #include "modules/desktop_capture/desktop_geometry.h"
26 #include "modules/desktop_capture/linux/x_error_trap.h"
27 #include "modules/desktop_capture/mouse_cursor.h"
28 #include "modules/desktop_capture/mouse_cursor_monitor.h"
29 #include "rtc_base/checks.h"
30 #include "rtc_base/logging.h"
31
32 namespace {
33
34 // WindowCapturer returns window IDs of X11 windows with WM_STATE attribute.
35 // These windows may not be immediate children of the root window, because
36 // window managers may re-parent them to add decorations. However,
37 // XQueryPointer() expects to be passed children of the root. This function
38 // searches up the list of the windows to find the root child that corresponds
39 // to |window|.
GetTopLevelWindow(Display * display,Window window)40 Window GetTopLevelWindow(Display* display, Window window) {
41 while (true) {
42 // If the window is in WithdrawnState then look at all of its children.
43 ::Window root, parent;
44 ::Window* children;
45 unsigned int num_children;
46 if (!XQueryTree(display, window, &root, &parent, &children,
47 &num_children)) {
48 RTC_LOG(LS_ERROR) << "Failed to query for child windows although window"
49 "does not have a valid WM_STATE.";
50 return None;
51 }
52 if (children)
53 XFree(children);
54
55 if (parent == root)
56 break;
57
58 window = parent;
59 }
60
61 return window;
62 }
63
64 } // namespace
65
66 namespace webrtc {
67
MouseCursorMonitorX11(const DesktopCaptureOptions & options,Window window)68 MouseCursorMonitorX11::MouseCursorMonitorX11(
69 const DesktopCaptureOptions& options,
70 Window window)
71 : x_display_(options.x_display()),
72 callback_(NULL),
73 mode_(SHAPE_AND_POSITION),
74 window_(window),
75 have_xfixes_(false),
76 xfixes_event_base_(-1),
77 xfixes_error_base_(-1) {
78 // Set a default initial cursor shape in case XFixes is not present.
79 const int kSize = 5;
80 std::unique_ptr<DesktopFrame> default_cursor(
81 new BasicDesktopFrame(DesktopSize(kSize, kSize)));
82 const uint8_t pixels[kSize * kSize] = {
83 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,
84 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0x00, 0xff, 0xff,
85 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
86 uint8_t* ptr = default_cursor->data();
87 for (int y = 0; y < kSize; ++y) {
88 for (int x = 0; x < kSize; ++x) {
89 *ptr++ = pixels[kSize * y + x];
90 *ptr++ = pixels[kSize * y + x];
91 *ptr++ = pixels[kSize * y + x];
92 *ptr++ = 0xff;
93 }
94 }
95 DesktopVector hotspot(2, 2);
96 cursor_shape_.reset(new MouseCursor(default_cursor.release(), hotspot));
97 }
98
~MouseCursorMonitorX11()99 MouseCursorMonitorX11::~MouseCursorMonitorX11() {
100 if (have_xfixes_) {
101 x_display_->RemoveEventHandler(xfixes_event_base_ + XFixesCursorNotify,
102 this);
103 }
104 }
105
Init(Callback * callback,Mode mode)106 void MouseCursorMonitorX11::Init(Callback* callback, Mode mode) {
107 // Init can be called only once per instance of MouseCursorMonitor.
108 RTC_DCHECK(!callback_);
109 RTC_DCHECK(callback);
110
111 callback_ = callback;
112 mode_ = mode;
113
114 have_xfixes_ =
115 XFixesQueryExtension(display(), &xfixes_event_base_, &xfixes_error_base_);
116
117 if (have_xfixes_) {
118 // Register for changes to the cursor shape.
119 XFixesSelectCursorInput(display(), window_, XFixesDisplayCursorNotifyMask);
120 x_display_->AddEventHandler(xfixes_event_base_ + XFixesCursorNotify, this);
121
122 CaptureCursor();
123 } else {
124 RTC_LOG(LS_INFO) << "X server does not support XFixes.";
125 }
126 }
127
Capture()128 void MouseCursorMonitorX11::Capture() {
129 RTC_DCHECK(callback_);
130
131 // Process X11 events in case XFixes has sent cursor notification.
132 x_display_->ProcessPendingXEvents();
133
134 // cursor_shape_| is set only if we were notified of a cursor shape change.
135 if (cursor_shape_.get())
136 callback_->OnMouseCursor(cursor_shape_.release());
137
138 // Get cursor position if necessary.
139 if (mode_ == SHAPE_AND_POSITION) {
140 int root_x;
141 int root_y;
142 int win_x;
143 int win_y;
144 Window root_window;
145 Window child_window;
146 unsigned int mask;
147
148 XErrorTrap error_trap(display());
149 Bool result = XQueryPointer(display(), window_, &root_window, &child_window,
150 &root_x, &root_y, &win_x, &win_y, &mask);
151 CursorState state;
152 if (!result || error_trap.GetLastErrorAndDisable() != 0) {
153 state = OUTSIDE;
154 } else {
155 // In screen mode (window_ == root_window) the mouse is always inside.
156 // XQueryPointer() sets |child_window| to None if the cursor is outside
157 // |window_|.
158 state =
159 (window_ == root_window || child_window != None) ? INSIDE : OUTSIDE;
160 }
161
162 // As the comments to GetTopLevelWindow() above indicate, in window capture,
163 // the cursor position capture happens in |window_|, while the frame catpure
164 // happens in |child_window|. These two windows are not alwyas same, as
165 // window manager may add some decorations to the |window_|. So translate
166 // the coordinate in |window_| to the coordinate space of |child_window|.
167 if (window_ != root_window && state == INSIDE) {
168 int translated_x, translated_y;
169 Window unused;
170 if (XTranslateCoordinates(display(), window_, child_window, win_x, win_y,
171 &translated_x, &translated_y, &unused)) {
172 win_x = translated_x;
173 win_y = translated_y;
174 }
175 }
176
177 // X11 always starts the coordinate from (0, 0), so we do not need to
178 // translate here.
179 callback_->OnMouseCursorPosition(DesktopVector(root_x, root_y));
180 }
181 }
182
HandleXEvent(const XEvent & event)183 bool MouseCursorMonitorX11::HandleXEvent(const XEvent& event) {
184 if (have_xfixes_ && event.type == xfixes_event_base_ + XFixesCursorNotify) {
185 const XFixesCursorNotifyEvent* cursor_event =
186 reinterpret_cast<const XFixesCursorNotifyEvent*>(&event);
187 if (cursor_event->subtype == XFixesDisplayCursorNotify) {
188 CaptureCursor();
189 }
190 // Return false, even if the event has been handled, because there might be
191 // other listeners for cursor notifications.
192 }
193 return false;
194 }
195
CaptureCursor()196 void MouseCursorMonitorX11::CaptureCursor() {
197 RTC_DCHECK(have_xfixes_);
198
199 XFixesCursorImage* img;
200 {
201 XErrorTrap error_trap(display());
202 img = XFixesGetCursorImage(display());
203 if (!img || error_trap.GetLastErrorAndDisable() != 0)
204 return;
205 }
206
207 std::unique_ptr<DesktopFrame> image(
208 new BasicDesktopFrame(DesktopSize(img->width, img->height)));
209
210 // Xlib stores 32-bit data in longs, even if longs are 64-bits long.
211 unsigned long* src = img->pixels;
212 uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
213 uint32_t* dst_end = dst + (img->width * img->height);
214 while (dst < dst_end) {
215 *dst++ = static_cast<uint32_t>(*src++);
216 }
217
218 DesktopVector hotspot(std::min(img->width, img->xhot),
219 std::min(img->height, img->yhot));
220
221 XFree(img);
222
223 cursor_shape_.reset(new MouseCursor(image.release(), hotspot));
224 }
225
226 // static
CreateForWindow(const DesktopCaptureOptions & options,WindowId window)227 MouseCursorMonitor* MouseCursorMonitorX11::CreateForWindow(
228 const DesktopCaptureOptions& options,
229 WindowId window) {
230 if (!options.x_display())
231 return NULL;
232 window = GetTopLevelWindow(options.x_display()->display(), window);
233 if (window == None)
234 return NULL;
235 return new MouseCursorMonitorX11(options, window);
236 }
237
CreateForScreen(const DesktopCaptureOptions & options,ScreenId screen)238 MouseCursorMonitor* MouseCursorMonitorX11::CreateForScreen(
239 const DesktopCaptureOptions& options,
240 ScreenId screen) {
241 if (!options.x_display())
242 return NULL;
243 return new MouseCursorMonitorX11(
244 options, DefaultRootWindow(options.x_display()->display()));
245 }
246
Create(const DesktopCaptureOptions & options)247 std::unique_ptr<MouseCursorMonitor> MouseCursorMonitorX11::Create(
248 const DesktopCaptureOptions& options) {
249 return std::unique_ptr<MouseCursorMonitor>(
250 CreateForScreen(options, kFullDesktopScreenId));
251 }
252
253 } // namespace webrtc
254