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/x11/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/x11/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 CursorState state;
149 {
150 XErrorTrap error_trap(display());
151 Bool result =
152 XQueryPointer(display(), window_, &root_window, &child_window,
153 &root_x, &root_y, &win_x, &win_y, &mask);
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
165 // As the comments to GetTopLevelWindow() above indicate, in window capture,
166 // the cursor position capture happens in `window_`, while the frame catpure
167 // happens in `child_window`. These two windows are not alwyas same, as
168 // window manager may add some decorations to the `window_`. So translate
169 // the coordinate in `window_` to the coordinate space of `child_window`.
170 if (window_ != root_window && state == INSIDE) {
171 int translated_x, translated_y;
172 Window unused;
173 if (XTranslateCoordinates(display(), window_, child_window, win_x, win_y,
174 &translated_x, &translated_y, &unused)) {
175 win_x = translated_x;
176 win_y = translated_y;
177 }
178 }
179
180 // X11 always starts the coordinate from (0, 0), so we do not need to
181 // translate here.
182 callback_->OnMouseCursorPosition(DesktopVector(root_x, root_y));
183 }
184 }
185
HandleXEvent(const XEvent & event)186 bool MouseCursorMonitorX11::HandleXEvent(const XEvent& event) {
187 if (have_xfixes_ && event.type == xfixes_event_base_ + XFixesCursorNotify) {
188 const XFixesCursorNotifyEvent* cursor_event =
189 reinterpret_cast<const XFixesCursorNotifyEvent*>(&event);
190 if (cursor_event->subtype == XFixesDisplayCursorNotify) {
191 CaptureCursor();
192 }
193 // Return false, even if the event has been handled, because there might be
194 // other listeners for cursor notifications.
195 }
196 return false;
197 }
198
CaptureCursor()199 void MouseCursorMonitorX11::CaptureCursor() {
200 RTC_DCHECK(have_xfixes_);
201
202 XFixesCursorImage* img;
203 {
204 XErrorTrap error_trap(display());
205 img = XFixesGetCursorImage(display());
206 if (!img || error_trap.GetLastErrorAndDisable() != 0)
207 return;
208 }
209
210 std::unique_ptr<DesktopFrame> image(
211 new BasicDesktopFrame(DesktopSize(img->width, img->height)));
212
213 // Xlib stores 32-bit data in longs, even if longs are 64-bits long.
214 unsigned long* src = img->pixels; // NOLINT(runtime/int)
215 uint32_t* dst = reinterpret_cast<uint32_t*>(image->data());
216 uint32_t* dst_end = dst + (img->width * img->height);
217 while (dst < dst_end) {
218 *dst++ = static_cast<uint32_t>(*src++);
219 }
220
221 DesktopVector hotspot(std::min(img->width, img->xhot),
222 std::min(img->height, img->yhot));
223
224 XFree(img);
225
226 cursor_shape_.reset(new MouseCursor(image.release(), hotspot));
227 }
228
229 // static
CreateForWindow(const DesktopCaptureOptions & options,WindowId window)230 MouseCursorMonitor* MouseCursorMonitorX11::CreateForWindow(
231 const DesktopCaptureOptions& options,
232 WindowId window) {
233 if (!options.x_display())
234 return NULL;
235 window = GetTopLevelWindow(options.x_display()->display(), window);
236 if (window == None)
237 return NULL;
238 return new MouseCursorMonitorX11(options, window);
239 }
240
CreateForScreen(const DesktopCaptureOptions & options,ScreenId screen)241 MouseCursorMonitor* MouseCursorMonitorX11::CreateForScreen(
242 const DesktopCaptureOptions& options,
243 ScreenId screen) {
244 if (!options.x_display())
245 return NULL;
246 return new MouseCursorMonitorX11(
247 options, DefaultRootWindow(options.x_display()->display()));
248 }
249
Create(const DesktopCaptureOptions & options)250 std::unique_ptr<MouseCursorMonitor> MouseCursorMonitorX11::Create(
251 const DesktopCaptureOptions& options) {
252 return std::unique_ptr<MouseCursorMonitor>(
253 CreateForScreen(options, kFullDesktopScreenId));
254 }
255
256 } // namespace webrtc
257