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