xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/linux/x11/mouse_cursor_monitor_x11.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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