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 "webrtc/modules/desktop_capture/mouse_cursor_monitor.h" 12 13#include <assert.h> 14#include <ApplicationServices/ApplicationServices.h> 15#include <Cocoa/Cocoa.h> 16#include <CoreFoundation/CoreFoundation.h> 17 18#include "webrtc/base/macutils.h" 19#include "webrtc/base/scoped_ptr.h" 20#include "webrtc/base/scoped_ref_ptr.h" 21#include "webrtc/modules/desktop_capture/desktop_capture_options.h" 22#include "webrtc/modules/desktop_capture/desktop_frame.h" 23#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h" 24#include "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h" 25#include "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h" 26#include "webrtc/modules/desktop_capture/mouse_cursor.h" 27#include "webrtc/system_wrappers/include/logging.h" 28 29namespace webrtc { 30 31class MouseCursorMonitorMac : public MouseCursorMonitor { 32 public: 33 MouseCursorMonitorMac(const DesktopCaptureOptions& options, 34 CGWindowID window_id, 35 ScreenId screen_id); 36 virtual ~MouseCursorMonitorMac(); 37 38 void Init(Callback* callback, Mode mode) override; 39 void Capture() override; 40 41 private: 42 static void DisplaysReconfiguredCallback(CGDirectDisplayID display, 43 CGDisplayChangeSummaryFlags flags, 44 void *user_parameter); 45 void DisplaysReconfigured(CGDirectDisplayID display, 46 CGDisplayChangeSummaryFlags flags); 47 48 void CaptureImage(); 49 50 rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_; 51 CGWindowID window_id_; 52 ScreenId screen_id_; 53 Callback* callback_; 54 Mode mode_; 55 rtc::scoped_ptr<MouseCursor> last_cursor_; 56 rtc::scoped_refptr<FullScreenChromeWindowDetector> 57 full_screen_chrome_window_detector_; 58}; 59 60MouseCursorMonitorMac::MouseCursorMonitorMac( 61 const DesktopCaptureOptions& options, 62 CGWindowID window_id, 63 ScreenId screen_id) 64 : configuration_monitor_(options.configuration_monitor()), 65 window_id_(window_id), 66 screen_id_(screen_id), 67 callback_(NULL), 68 mode_(SHAPE_AND_POSITION), 69 full_screen_chrome_window_detector_( 70 options.full_screen_chrome_window_detector()) { 71 assert(window_id == kCGNullWindowID || screen_id == kInvalidScreenId); 72 if (screen_id != kInvalidScreenId && 73 rtc::GetOSVersionName() < rtc::kMacOSLion) { 74 // Single screen capture is not supported on pre OS X 10.7. 75 screen_id_ = kFullDesktopScreenId; 76 } 77} 78 79MouseCursorMonitorMac::~MouseCursorMonitorMac() {} 80 81void MouseCursorMonitorMac::Init(Callback* callback, Mode mode) { 82 assert(!callback_); 83 assert(callback); 84 85 callback_ = callback; 86 mode_ = mode; 87} 88 89void MouseCursorMonitorMac::Capture() { 90 assert(callback_); 91 92 CaptureImage(); 93 94 if (mode_ != SHAPE_AND_POSITION) 95 return; 96 97 CursorState state = INSIDE; 98 99 CGEventRef event = CGEventCreate(NULL); 100 CGPoint gc_position = CGEventGetLocation(event); 101 CFRelease(event); 102 103 DesktopVector position(gc_position.x, gc_position.y); 104 105 configuration_monitor_->Lock(); 106 MacDesktopConfiguration configuration = 107 configuration_monitor_->desktop_configuration(); 108 configuration_monitor_->Unlock(); 109 float scale = 1.0f; 110 111 // Find the dpi to physical pixel scale for the screen where the mouse cursor 112 // is. 113 for (MacDisplayConfigurations::iterator it = configuration.displays.begin(); 114 it != configuration.displays.end(); ++it) { 115 if (it->bounds.Contains(position)) { 116 scale = it->dip_to_pixel_scale; 117 break; 118 } 119 } 120 // If we are capturing cursor for a specific window then we need to figure out 121 // if the current mouse position is covered by another window and also adjust 122 // |position| to make it relative to the window origin. 123 if (window_id_ != kCGNullWindowID) { 124 CGWindowID on_screen_window = window_id_; 125 if (full_screen_chrome_window_detector_) { 126 CGWindowID full_screen_window = 127 full_screen_chrome_window_detector_->FindFullScreenWindow(window_id_); 128 129 if (full_screen_window != kCGNullWindowID) 130 on_screen_window = full_screen_window; 131 } 132 133 // Get list of windows that may be covering parts of |on_screen_window|. 134 // CGWindowListCopyWindowInfo() returns windows in order from front to back, 135 // so |on_screen_window| is expected to be the last in the list. 136 CFArrayRef window_array = 137 CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly | 138 kCGWindowListOptionOnScreenAboveWindow | 139 kCGWindowListOptionIncludingWindow, 140 on_screen_window); 141 bool found_window = false; 142 if (window_array) { 143 CFIndex count = CFArrayGetCount(window_array); 144 for (CFIndex i = 0; i < count; ++i) { 145 CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( 146 CFArrayGetValueAtIndex(window_array, i)); 147 148 // Skip the Dock window. Dock window covers the whole screen, but it is 149 // transparent. 150 CFStringRef window_name = reinterpret_cast<CFStringRef>( 151 CFDictionaryGetValue(window, kCGWindowName)); 152 if (window_name && CFStringCompare(window_name, CFSTR("Dock"), 0) == 0) 153 continue; 154 155 CFDictionaryRef window_bounds = reinterpret_cast<CFDictionaryRef>( 156 CFDictionaryGetValue(window, kCGWindowBounds)); 157 CFNumberRef window_number = reinterpret_cast<CFNumberRef>( 158 CFDictionaryGetValue(window, kCGWindowNumber)); 159 160 if (window_bounds && window_number) { 161 CGRect gc_window_rect; 162 if (!CGRectMakeWithDictionaryRepresentation(window_bounds, 163 &gc_window_rect)) { 164 continue; 165 } 166 DesktopRect window_rect = 167 DesktopRect::MakeXYWH(gc_window_rect.origin.x, 168 gc_window_rect.origin.y, 169 gc_window_rect.size.width, 170 gc_window_rect.size.height); 171 172 CGWindowID window_id; 173 if (!CFNumberGetValue(window_number, kCFNumberIntType, &window_id)) 174 continue; 175 176 if (window_id == on_screen_window) { 177 found_window = true; 178 if (!window_rect.Contains(position)) 179 state = OUTSIDE; 180 position = position.subtract(window_rect.top_left()); 181 182 assert(i == count - 1); 183 break; 184 } else if (window_rect.Contains(position)) { 185 state = OUTSIDE; 186 position.set(-1, -1); 187 break; 188 } 189 } 190 } 191 CFRelease(window_array); 192 } 193 if (!found_window) { 194 // If we failed to get list of windows or the window wasn't in the list 195 // pretend that the cursor is outside the window. This can happen, e.g. if 196 // the window was closed. 197 state = OUTSIDE; 198 position.set(-1, -1); 199 } 200 } else { 201 assert(screen_id_ >= kFullDesktopScreenId); 202 if (screen_id_ != kFullDesktopScreenId) { 203 // For single screen capturing, convert the position to relative to the 204 // target screen. 205 const MacDisplayConfiguration* config = 206 configuration.FindDisplayConfigurationById( 207 static_cast<CGDirectDisplayID>(screen_id_)); 208 if (config) { 209 if (!config->pixel_bounds.Contains(position)) 210 state = OUTSIDE; 211 position = position.subtract(config->bounds.top_left()); 212 } else { 213 // The target screen is no longer valid. 214 state = OUTSIDE; 215 position.set(-1, -1); 216 } 217 } else { 218 position.subtract(configuration.bounds.top_left()); 219 } 220 } 221 if (state == INSIDE) { 222 // Convert Density Independent Pixel to physical pixel. 223 position = DesktopVector(round(position.x() * scale), 224 round(position.y() * scale)); 225 } 226 callback_->OnMouseCursorPosition(state, position); 227} 228 229void MouseCursorMonitorMac::CaptureImage() { 230 NSCursor* nscursor = [NSCursor currentSystemCursor]; 231 232 NSImage* nsimage = [nscursor image]; 233 NSSize nssize = [nsimage size]; 234 DesktopSize size(nssize.width, nssize.height); 235 NSPoint nshotspot = [nscursor hotSpot]; 236 DesktopVector hotspot( 237 std::max(0, std::min(size.width(), static_cast<int>(nshotspot.x))), 238 std::max(0, std::min(size.height(), static_cast<int>(nshotspot.y)))); 239 CGImageRef cg_image = 240 [nsimage CGImageForProposedRect:NULL context:nil hints:nil]; 241 if (!cg_image) 242 return; 243 244 if (CGImageGetBitsPerPixel(cg_image) != DesktopFrame::kBytesPerPixel * 8 || 245 CGImageGetBytesPerRow(cg_image) != 246 static_cast<size_t>(DesktopFrame::kBytesPerPixel * size.width()) || 247 CGImageGetBitsPerComponent(cg_image) != 8) { 248 return; 249 } 250 251 CGDataProviderRef provider = CGImageGetDataProvider(cg_image); 252 CFDataRef image_data_ref = CGDataProviderCopyData(provider); 253 if (image_data_ref == NULL) 254 return; 255 256 const uint8_t* src_data = 257 reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(image_data_ref)); 258 259 // Compare the cursor with the previous one. 260 if (last_cursor_.get() && 261 last_cursor_->image()->size().equals(size) && 262 last_cursor_->hotspot().equals(hotspot) && 263 memcmp(last_cursor_->image()->data(), src_data, 264 last_cursor_->image()->stride() * size.height()) == 0) { 265 CFRelease(image_data_ref); 266 return; 267 } 268 269 // Create a MouseCursor that describes the cursor and pass it to 270 // the client. 271 rtc::scoped_ptr<DesktopFrame> image( 272 new BasicDesktopFrame(DesktopSize(size.width(), size.height()))); 273 memcpy(image->data(), src_data, 274 size.width() * size.height() * DesktopFrame::kBytesPerPixel); 275 276 CFRelease(image_data_ref); 277 278 rtc::scoped_ptr<MouseCursor> cursor( 279 new MouseCursor(image.release(), hotspot)); 280 last_cursor_.reset(MouseCursor::CopyOf(*cursor)); 281 282 callback_->OnMouseCursor(cursor.release()); 283} 284 285MouseCursorMonitor* MouseCursorMonitor::CreateForWindow( 286 const DesktopCaptureOptions& options, WindowId window) { 287 return new MouseCursorMonitorMac(options, window, kInvalidScreenId); 288} 289 290MouseCursorMonitor* MouseCursorMonitor::CreateForScreen( 291 const DesktopCaptureOptions& options, 292 ScreenId screen) { 293 return new MouseCursorMonitorMac(options, kCGNullWindowID, screen); 294} 295 296} // namespace webrtc 297