xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/mouse_cursor_monitor_mac.mm (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/mouse_cursor_monitor.h"
12
13
14#include <memory>
15
16#include <ApplicationServices/ApplicationServices.h>
17#include <Cocoa/Cocoa.h>
18#include <CoreFoundation/CoreFoundation.h>
19
20#include "api/scoped_refptr.h"
21#include "modules/desktop_capture/desktop_capture_options.h"
22#include "modules/desktop_capture/desktop_capture_types.h"
23#include "modules/desktop_capture/desktop_frame.h"
24#include "modules/desktop_capture/mac/desktop_configuration.h"
25#include "modules/desktop_capture/mac/desktop_configuration_monitor.h"
26#include "modules/desktop_capture/mac/window_list_utils.h"
27#include "modules/desktop_capture/mouse_cursor.h"
28#include "rtc_base/checks.h"
29
30namespace webrtc {
31
32namespace {
33CGImageRef CreateScaledCGImage(CGImageRef image, int width, int height) {
34  // Create context, keeping original image properties.
35  CGColorSpaceRef colorspace = CGImageGetColorSpace(image);
36  CGContextRef context = CGBitmapContextCreate(nullptr,
37                                               width,
38                                               height,
39                                               CGImageGetBitsPerComponent(image),
40                                               width * DesktopFrame::kBytesPerPixel,
41                                               colorspace,
42                                               CGImageGetBitmapInfo(image));
43
44  if (!context) return nil;
45
46  // Draw image to context, resizing it.
47  CGContextDrawImage(context, CGRectMake(0, 0, width, height), image);
48  // Extract resulting image from context.
49  CGImageRef imgRef = CGBitmapContextCreateImage(context);
50  CGContextRelease(context);
51
52  return imgRef;
53}
54}  // namespace
55
56class MouseCursorMonitorMac : public MouseCursorMonitor {
57 public:
58  MouseCursorMonitorMac(const DesktopCaptureOptions& options,
59                        CGWindowID window_id,
60                        ScreenId screen_id);
61  ~MouseCursorMonitorMac() override;
62
63  void Init(Callback* callback, Mode mode) override;
64  void Capture() override;
65
66 private:
67  static void DisplaysReconfiguredCallback(CGDirectDisplayID display,
68                                           CGDisplayChangeSummaryFlags flags,
69                                           void *user_parameter);
70  void DisplaysReconfigured(CGDirectDisplayID display,
71                            CGDisplayChangeSummaryFlags flags);
72
73  void CaptureImage(float scale);
74
75  rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_;
76  CGWindowID window_id_;
77  ScreenId screen_id_;
78  Callback* callback_ = NULL;
79  Mode mode_;
80  __strong NSImage* last_cursor_ = NULL;
81};
82
83MouseCursorMonitorMac::MouseCursorMonitorMac(const DesktopCaptureOptions& options,
84                                             CGWindowID window_id,
85                                             ScreenId screen_id)
86    : configuration_monitor_(options.configuration_monitor()),
87      window_id_(window_id),
88      screen_id_(screen_id),
89      mode_(SHAPE_AND_POSITION) {
90  RTC_DCHECK(window_id == kCGNullWindowID || screen_id == kInvalidScreenId);
91}
92
93MouseCursorMonitorMac::~MouseCursorMonitorMac() {}
94
95void MouseCursorMonitorMac::Init(Callback* callback, Mode mode) {
96  RTC_DCHECK(!callback_);
97  RTC_DCHECK(callback);
98
99  callback_ = callback;
100  mode_ = mode;
101}
102
103void MouseCursorMonitorMac::Capture() {
104  RTC_DCHECK(callback_);
105
106  CGEventRef event = CGEventCreate(NULL);
107  CGPoint gc_position = CGEventGetLocation(event);
108  CFRelease(event);
109
110  DesktopVector position(gc_position.x, gc_position.y);
111
112  MacDesktopConfiguration configuration =
113      configuration_monitor_->desktop_configuration();
114  float scale = GetScaleFactorAtPosition(configuration, position);
115
116  CaptureImage(scale);
117
118  if (mode_ != SHAPE_AND_POSITION)
119    return;
120
121  // Always report cursor position in DIP pixel.
122  callback_->OnMouseCursorPosition(
123      position.subtract(configuration.bounds.top_left()));
124}
125
126void MouseCursorMonitorMac::CaptureImage(float scale) {
127  NSCursor* nscursor = [NSCursor currentSystemCursor];
128
129  NSImage* nsimage = [nscursor image];
130  if (nsimage == nil || !nsimage.isValid) {
131    return;
132  }
133  NSSize nssize = [nsimage size];  // DIP size
134
135  // No need to caputre cursor image if it's unchanged since last capture.
136  if ([[nsimage TIFFRepresentation] isEqual:[last_cursor_ TIFFRepresentation]]) return;
137  last_cursor_ = nsimage;
138
139  DesktopSize size(round(nssize.width * scale),
140                   round(nssize.height * scale));  // Pixel size
141  NSPoint nshotspot = [nscursor hotSpot];
142  DesktopVector hotspot(
143      std::max(0,
144               std::min(size.width(), static_cast<int>(nshotspot.x * scale))),
145      std::max(0,
146               std::min(size.height(), static_cast<int>(nshotspot.y * scale))));
147  CGImageRef cg_image =
148      [nsimage CGImageForProposedRect:NULL context:nil hints:nil];
149  if (!cg_image)
150    return;
151
152  // Before 10.12, OSX may report 1X cursor on Retina screen. (See
153  // crbug.com/632995.) After 10.12, OSX may report 2X cursor on non-Retina
154  // screen. (See crbug.com/671436.) So scaling the cursor if needed.
155  CGImageRef scaled_cg_image = nil;
156  if (CGImageGetWidth(cg_image) != static_cast<size_t>(size.width())) {
157    scaled_cg_image = CreateScaledCGImage(cg_image, size.width(), size.height());
158    if (scaled_cg_image != nil) {
159      cg_image = scaled_cg_image;
160    }
161  }
162  if (CGImageGetBitsPerPixel(cg_image) != DesktopFrame::kBytesPerPixel * 8 ||
163      CGImageGetWidth(cg_image) != static_cast<size_t>(size.width()) ||
164      CGImageGetBitsPerComponent(cg_image) != 8) {
165    if (scaled_cg_image != nil) CGImageRelease(scaled_cg_image);
166    return;
167  }
168
169  CGDataProviderRef provider = CGImageGetDataProvider(cg_image);
170  CFDataRef image_data_ref = CGDataProviderCopyData(provider);
171  if (image_data_ref == NULL) {
172    if (scaled_cg_image != nil) CGImageRelease(scaled_cg_image);
173    return;
174  }
175
176  const uint8_t* src_data =
177      reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(image_data_ref));
178
179  // Create a MouseCursor that describes the cursor and pass it to
180  // the client.
181  std::unique_ptr<DesktopFrame> image(
182      new BasicDesktopFrame(DesktopSize(size.width(), size.height())));
183
184  int src_stride = CGImageGetBytesPerRow(cg_image);
185  image->CopyPixelsFrom(src_data, src_stride, DesktopRect::MakeSize(size));
186
187  CFRelease(image_data_ref);
188  if (scaled_cg_image != nil) CGImageRelease(scaled_cg_image);
189
190  std::unique_ptr<MouseCursor> cursor(
191      new MouseCursor(image.release(), hotspot));
192
193  callback_->OnMouseCursor(cursor.release());
194}
195
196MouseCursorMonitor* MouseCursorMonitor::CreateForWindow(
197    const DesktopCaptureOptions& options, WindowId window) {
198  return new MouseCursorMonitorMac(options, window, kInvalidScreenId);
199}
200
201MouseCursorMonitor* MouseCursorMonitor::CreateForScreen(
202    const DesktopCaptureOptions& options,
203    ScreenId screen) {
204  return new MouseCursorMonitorMac(options, kCGNullWindowID, screen);
205}
206
207std::unique_ptr<MouseCursorMonitor> MouseCursorMonitor::Create(
208    const DesktopCaptureOptions& options) {
209  return std::unique_ptr<MouseCursorMonitor>(
210      CreateForScreen(options, kFullDesktopScreenId));
211}
212
213}  // namespace webrtc
214