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#include <assert.h> 14 15#include <memory> 16 17#include <ApplicationServices/ApplicationServices.h> 18#include <Cocoa/Cocoa.h> 19#include <CoreFoundation/CoreFoundation.h> 20 21#include "api/scoped_refptr.h" 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/mac/desktop_configuration.h" 26#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" 27#include "modules/desktop_capture/mac/window_list_utils.h" 28#include "modules/desktop_capture/mouse_cursor.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_; 79 Mode mode_; 80 __strong NSImage* last_cursor_; 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 callback_(NULL), 90 mode_(SHAPE_AND_POSITION) { 91 assert(window_id == kCGNullWindowID || screen_id == kInvalidScreenId); 92} 93 94MouseCursorMonitorMac::~MouseCursorMonitorMac() {} 95 96void MouseCursorMonitorMac::Init(Callback* callback, Mode mode) { 97 assert(!callback_); 98 assert(callback); 99 100 callback_ = callback; 101 mode_ = mode; 102} 103 104void MouseCursorMonitorMac::Capture() { 105 assert(callback_); 106 107 CGEventRef event = CGEventCreate(NULL); 108 CGPoint gc_position = CGEventGetLocation(event); 109 CFRelease(event); 110 111 DesktopVector position(gc_position.x, gc_position.y); 112 113 MacDesktopConfiguration configuration = 114 configuration_monitor_->desktop_configuration(); 115 float scale = GetScaleFactorAtPosition(configuration, position); 116 117 CaptureImage(scale); 118 119 if (mode_ != SHAPE_AND_POSITION) 120 return; 121 122 // Always report cursor position in DIP pixel. 123 callback_->OnMouseCursorPosition( 124 position.subtract(configuration.bounds.top_left())); 125} 126 127void MouseCursorMonitorMac::CaptureImage(float scale) { 128 NSCursor* nscursor = [NSCursor currentSystemCursor]; 129 130 NSImage* nsimage = [nscursor image]; 131 if (nsimage == nil || !nsimage.isValid) { 132 return; 133 } 134 NSSize nssize = [nsimage size]; // DIP size 135 136 // No need to caputre cursor image if it's unchanged since last capture. 137 if ([[nsimage TIFFRepresentation] isEqual:[last_cursor_ TIFFRepresentation]]) return; 138 last_cursor_ = nsimage; 139 140 DesktopSize size(round(nssize.width * scale), 141 round(nssize.height * scale)); // Pixel size 142 NSPoint nshotspot = [nscursor hotSpot]; 143 DesktopVector hotspot( 144 std::max(0, 145 std::min(size.width(), static_cast<int>(nshotspot.x * scale))), 146 std::max(0, 147 std::min(size.height(), static_cast<int>(nshotspot.y * scale)))); 148 CGImageRef cg_image = 149 [nsimage CGImageForProposedRect:NULL context:nil hints:nil]; 150 if (!cg_image) 151 return; 152 153 // Before 10.12, OSX may report 1X cursor on Retina screen. (See 154 // crbug.com/632995.) After 10.12, OSX may report 2X cursor on non-Retina 155 // screen. (See crbug.com/671436.) So scaling the cursor if needed. 156 CGImageRef scaled_cg_image = nil; 157 if (CGImageGetWidth(cg_image) != static_cast<size_t>(size.width())) { 158 scaled_cg_image = CreateScaledCGImage(cg_image, size.width(), size.height()); 159 if (scaled_cg_image != nil) { 160 cg_image = scaled_cg_image; 161 } 162 } 163 if (CGImageGetBitsPerPixel(cg_image) != DesktopFrame::kBytesPerPixel * 8 || 164 CGImageGetWidth(cg_image) != static_cast<size_t>(size.width()) || 165 CGImageGetBitsPerComponent(cg_image) != 8) { 166 if (scaled_cg_image != nil) CGImageRelease(scaled_cg_image); 167 return; 168 } 169 170 CGDataProviderRef provider = CGImageGetDataProvider(cg_image); 171 CFDataRef image_data_ref = CGDataProviderCopyData(provider); 172 if (image_data_ref == NULL) { 173 if (scaled_cg_image != nil) CGImageRelease(scaled_cg_image); 174 return; 175 } 176 177 const uint8_t* src_data = 178 reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(image_data_ref)); 179 180 // Create a MouseCursor that describes the cursor and pass it to 181 // the client. 182 std::unique_ptr<DesktopFrame> image( 183 new BasicDesktopFrame(DesktopSize(size.width(), size.height()))); 184 185 int src_stride = CGImageGetBytesPerRow(cg_image); 186 image->CopyPixelsFrom(src_data, src_stride, DesktopRect::MakeSize(size)); 187 188 CFRelease(image_data_ref); 189 if (scaled_cg_image != nil) CGImageRelease(scaled_cg_image); 190 191 std::unique_ptr<MouseCursor> cursor( 192 new MouseCursor(image.release(), hotspot)); 193 194 callback_->OnMouseCursor(cursor.release()); 195} 196 197MouseCursorMonitor* MouseCursorMonitor::CreateForWindow( 198 const DesktopCaptureOptions& options, WindowId window) { 199 return new MouseCursorMonitorMac(options, window, kInvalidScreenId); 200} 201 202MouseCursorMonitor* MouseCursorMonitor::CreateForScreen( 203 const DesktopCaptureOptions& options, 204 ScreenId screen) { 205 return new MouseCursorMonitorMac(options, kCGNullWindowID, screen); 206} 207 208std::unique_ptr<MouseCursorMonitor> MouseCursorMonitor::Create( 209 const DesktopCaptureOptions& options) { 210 return std::unique_ptr<MouseCursorMonitor>( 211 CreateForScreen(options, kFullDesktopScreenId)); 212} 213 214} // namespace webrtc 215