2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
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.
11 #include "webrtc/modules/desktop_capture/mouse_cursor_monitor.h"
14 #include <ApplicationServices/ApplicationServices.h>
15 #include <Cocoa/Cocoa.h>
16 #include <CoreFoundation/CoreFoundation.h>
18 #include "webrtc/base/macutils.h"
19 #include "webrtc/modules/desktop_capture/desktop_capture_options.h"
20 #include "webrtc/modules/desktop_capture/desktop_frame.h"
21 #include "webrtc/modules/desktop_capture/mac/desktop_configuration.h"
22 #include "webrtc/modules/desktop_capture/mac/desktop_configuration_monitor.h"
23 #include "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h"
24 #include "webrtc/modules/desktop_capture/mouse_cursor.h"
25 #include "webrtc/system_wrappers/interface/logging.h"
26 #include "webrtc/system_wrappers/interface/scoped_ptr.h"
27 #include "webrtc/system_wrappers/interface/scoped_refptr.h"
31 class MouseCursorMonitorMac : public MouseCursorMonitor {
33 MouseCursorMonitorMac(const DesktopCaptureOptions& options,
36 virtual ~MouseCursorMonitorMac();
38 virtual void Init(Callback* callback, Mode mode) OVERRIDE;
39 virtual void Capture() OVERRIDE;
42 static void DisplaysReconfiguredCallback(CGDirectDisplayID display,
43 CGDisplayChangeSummaryFlags flags,
44 void *user_parameter);
45 void DisplaysReconfigured(CGDirectDisplayID display,
46 CGDisplayChangeSummaryFlags flags);
50 scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_;
51 CGWindowID window_id_;
55 scoped_ptr<MouseCursor> last_cursor_;
56 scoped_refptr<FullScreenChromeWindowDetector>
57 full_screen_chrome_window_detector_;
60 MouseCursorMonitorMac::MouseCursorMonitorMac(
61 const DesktopCaptureOptions& options,
64 : configuration_monitor_(options.configuration_monitor()),
65 window_id_(window_id),
66 screen_id_(screen_id),
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;
79 MouseCursorMonitorMac::~MouseCursorMonitorMac() {}
81 void MouseCursorMonitorMac::Init(Callback* callback, Mode mode) {
89 void MouseCursorMonitorMac::Capture() {
94 if (mode_ != SHAPE_AND_POSITION)
97 CursorState state = INSIDE;
99 CGEventRef event = CGEventCreate(NULL);
100 CGPoint gc_position = CGEventGetLocation(event);
103 DesktopVector position(gc_position.x, gc_position.y);
105 configuration_monitor_->Lock();
106 MacDesktopConfiguration configuration =
107 configuration_monitor_->desktop_configuration();
108 configuration_monitor_->Unlock();
111 // Find the dpi to physical pixel scale for the screen where the mouse cursor
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;
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_);
129 if (full_screen_window != kCGNullWindowID)
130 on_screen_window = full_screen_window;
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,
141 bool found_window = false;
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));
148 // Skip the Dock window. Dock window covers the whole screen, but it is
150 CFStringRef window_name = reinterpret_cast<CFStringRef>(
151 CFDictionaryGetValue(window, kCGWindowName));
152 if (window_name && CFStringCompare(window_name, CFSTR("Dock"), 0) == 0)
155 CFDictionaryRef window_bounds = reinterpret_cast<CFDictionaryRef>(
156 CFDictionaryGetValue(window, kCGWindowBounds));
157 CFNumberRef window_number = reinterpret_cast<CFNumberRef>(
158 CFDictionaryGetValue(window, kCGWindowNumber));
160 if (window_bounds && window_number) {
161 CGRect gc_window_rect;
162 if (!CGRectMakeWithDictionaryRepresentation(window_bounds,
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);
172 CGWindowID window_id;
173 if (!CFNumberGetValue(window_number, kCFNumberIntType, &window_id))
176 if (window_id == on_screen_window) {
178 if (!window_rect.Contains(position))
180 position = position.subtract(window_rect.top_left());
182 assert(i == count - 1);
184 } else if (window_rect.Contains(position)) {
186 position.set(-1, -1);
191 CFRelease(window_array);
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.
198 position.set(-1, -1);
201 assert(screen_id_ >= kFullDesktopScreenId);
202 if (screen_id_ != kFullDesktopScreenId) {
203 // For single screen capturing, convert the position to relative to the
205 const MacDisplayConfiguration* config =
206 configuration.FindDisplayConfigurationById(
207 static_cast<CGDirectDisplayID>(screen_id_));
209 if (!config->pixel_bounds.Contains(position))
211 position = position.subtract(config->bounds.top_left());
213 // The target screen is no longer valid.
215 position.set(-1, -1);
218 position.subtract(configuration.bounds.top_left());
221 if (state == INSIDE) {
222 // Convert Density Independent Pixel to physical pixel.
223 position = DesktopVector(round(position.x() * scale),
224 round(position.y() * scale));
226 callback_->OnMouseCursorPosition(state, position);
229 void MouseCursorMonitorMac::CaptureImage() {
230 NSCursor* nscursor = [NSCursor currentSystemCursor];
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];
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) {
251 CGDataProviderRef provider = CGImageGetDataProvider(cg_image);
252 CFDataRef image_data_ref = CGDataProviderCopyData(provider);
253 if (image_data_ref == NULL)
256 const uint8_t* src_data =
257 reinterpret_cast<const uint8_t*>(CFDataGetBytePtr(image_data_ref));
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) {
268 // Create a MouseCursor that describes the cursor and pass it to
270 scoped_ptr<DesktopFrame> image(
271 new BasicDesktopFrame(DesktopSize(size.width(), size.height())));
272 memcpy(image->data(), src_data,
273 size.width() * size.height() * DesktopFrame::kBytesPerPixel);
275 CFRelease(image_data_ref);
277 scoped_ptr<MouseCursor> cursor(new MouseCursor(image.release(), hotspot));
278 last_cursor_.reset(MouseCursor::CopyOf(*cursor));
280 callback_->OnMouseCursor(cursor.release());
283 MouseCursorMonitor* MouseCursorMonitor::CreateForWindow(
284 const DesktopCaptureOptions& options, WindowId window) {
285 return new MouseCursorMonitorMac(options, window, kInvalidScreenId);
288 MouseCursorMonitor* MouseCursorMonitor::CreateForScreen(
289 const DesktopCaptureOptions& options,
291 return new MouseCursorMonitorMac(options, kCGNullWindowID, screen);
294 } // namespace webrtc