Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / third_party / webrtc / modules / desktop_capture / mouse_cursor_monitor_mac.mm
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/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"
28
29 namespace webrtc {
30
31 class MouseCursorMonitorMac : public MouseCursorMonitor {
32  public:
33   MouseCursorMonitorMac(const DesktopCaptureOptions& options,
34                         CGWindowID window_id,
35                         ScreenId screen_id);
36   virtual ~MouseCursorMonitorMac();
37
38   virtual void Init(Callback* callback, Mode mode) OVERRIDE;
39   virtual 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   scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_;
51   CGWindowID window_id_;
52   ScreenId screen_id_;
53   Callback* callback_;
54   Mode mode_;
55   scoped_ptr<MouseCursor> last_cursor_;
56   scoped_refptr<FullScreenChromeWindowDetector>
57       full_screen_chrome_window_detector_;
58 };
59
60 MouseCursorMonitorMac::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
79 MouseCursorMonitorMac::~MouseCursorMonitorMac() {}
80
81 void MouseCursorMonitorMac::Init(Callback* callback, Mode mode) {
82   assert(!callback_);
83   assert(callback);
84
85   callback_ = callback;
86   mode_ = mode;
87 }
88
89 void 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
229 void 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     return;
266   }
267
268   // Create a MouseCursor that describes the cursor and pass it to
269   // the client.
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);
274
275   CFRelease(image_data_ref);
276
277   scoped_ptr<MouseCursor> cursor(new MouseCursor(image.release(), hotspot));
278   last_cursor_.reset(MouseCursor::CopyOf(*cursor));
279
280   callback_->OnMouseCursor(cursor.release());
281 }
282
283 MouseCursorMonitor* MouseCursorMonitor::CreateForWindow(
284     const DesktopCaptureOptions& options, WindowId window) {
285   return new MouseCursorMonitorMac(options, window, kInvalidScreenId);
286 }
287
288 MouseCursorMonitor* MouseCursorMonitor::CreateForScreen(
289     const DesktopCaptureOptions& options,
290     ScreenId screen) {
291   return new MouseCursorMonitorMac(options, kCGNullWindowID, screen);
292 }
293
294 }  // namespace webrtc