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/window_capturer.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_ref_ptr.h"
20#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
21#include "webrtc/modules/desktop_capture/desktop_frame.h"
22#include "webrtc/modules/desktop_capture/mac/desktop_configuration.h"
23#include "webrtc/modules/desktop_capture/mac/full_screen_chrome_window_detector.h"
24#include "webrtc/modules/desktop_capture/mac/window_list_utils.h"
25#include "webrtc/system_wrappers/include/logging.h"
26#include "webrtc/system_wrappers/include/tick_util.h"
27
28namespace webrtc {
29
30namespace {
31
32// Returns true if the window exists.
33bool IsWindowValid(CGWindowID id) {
34  CFArrayRef window_id_array =
35      CFArrayCreate(NULL, reinterpret_cast<const void **>(&id), 1, NULL);
36  CFArrayRef window_array =
37      CGWindowListCreateDescriptionFromArray(window_id_array);
38  bool valid = window_array && CFArrayGetCount(window_array);
39  CFRelease(window_id_array);
40  CFRelease(window_array);
41
42  return valid;
43}
44
45class WindowCapturerMac : public WindowCapturer {
46 public:
47  explicit WindowCapturerMac(rtc::scoped_refptr<FullScreenChromeWindowDetector>
48                                 full_screen_chrome_window_detector);
49  virtual ~WindowCapturerMac();
50
51  // WindowCapturer interface.
52  bool GetWindowList(WindowList* windows) override;
53  bool SelectWindow(WindowId id) override;
54  bool BringSelectedWindowToFront() override;
55
56  // DesktopCapturer interface.
57  void Start(Callback* callback) override;
58  void Capture(const DesktopRegion& region) override;
59
60 private:
61  Callback* callback_;
62
63  // The window being captured.
64  CGWindowID window_id_;
65
66  rtc::scoped_refptr<FullScreenChromeWindowDetector>
67      full_screen_chrome_window_detector_;
68
69  RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerMac);
70};
71
72WindowCapturerMac::WindowCapturerMac(rtc::scoped_refptr<
73    FullScreenChromeWindowDetector> full_screen_chrome_window_detector)
74    : callback_(NULL),
75      window_id_(0),
76      full_screen_chrome_window_detector_(full_screen_chrome_window_detector) {
77}
78
79WindowCapturerMac::~WindowCapturerMac() {
80}
81
82bool WindowCapturerMac::GetWindowList(WindowList* windows) {
83  // Only get on screen, non-desktop windows.
84  CFArrayRef window_array = CGWindowListCopyWindowInfo(
85      kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements,
86      kCGNullWindowID);
87  if (!window_array)
88    return false;
89
90  // Check windows to make sure they have an id, title, and use window layer
91  // other than 0.
92  CFIndex count = CFArrayGetCount(window_array);
93  for (CFIndex i = 0; i < count; ++i) {
94    CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
95        CFArrayGetValueAtIndex(window_array, i));
96    CFStringRef window_title = reinterpret_cast<CFStringRef>(
97        CFDictionaryGetValue(window, kCGWindowName));
98    CFNumberRef window_id = reinterpret_cast<CFNumberRef>(
99        CFDictionaryGetValue(window, kCGWindowNumber));
100    CFNumberRef window_layer = reinterpret_cast<CFNumberRef>(
101        CFDictionaryGetValue(window, kCGWindowLayer));
102    if (window_title && window_id && window_layer) {
103      // Skip windows with layer=0 (menu, dock).
104      int layer;
105      CFNumberGetValue(window_layer, kCFNumberIntType, &layer);
106      if (layer != 0)
107        continue;
108
109      int id;
110      CFNumberGetValue(window_id, kCFNumberIntType, &id);
111      WindowCapturer::Window window;
112      window.id = id;
113      if (!rtc::ToUtf8(window_title, &(window.title)) ||
114          window.title.empty()) {
115        continue;
116      }
117      windows->push_back(window);
118    }
119  }
120
121  CFRelease(window_array);
122  return true;
123}
124
125bool WindowCapturerMac::SelectWindow(WindowId id) {
126  if (!IsWindowValid(id))
127    return false;
128  window_id_ = id;
129  return true;
130}
131
132bool WindowCapturerMac::BringSelectedWindowToFront() {
133  if (!window_id_)
134    return false;
135
136  CGWindowID ids[1];
137  ids[0] = window_id_;
138  CFArrayRef window_id_array =
139      CFArrayCreate(NULL, reinterpret_cast<const void **>(&ids), 1, NULL);
140
141  CFArrayRef window_array =
142      CGWindowListCreateDescriptionFromArray(window_id_array);
143  if (window_array == NULL || 0 == CFArrayGetCount(window_array)) {
144    // Could not find the window. It might have been closed.
145    LOG(LS_INFO) << "Window not found";
146    CFRelease(window_id_array);
147    return false;
148  }
149
150  CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>(
151      CFArrayGetValueAtIndex(window_array, 0));
152  CFNumberRef pid_ref = reinterpret_cast<CFNumberRef>(
153      CFDictionaryGetValue(window, kCGWindowOwnerPID));
154
155  int pid;
156  CFNumberGetValue(pid_ref, kCFNumberIntType, &pid);
157
158  // TODO(jiayl): this will bring the process main window to the front. We
159  // should find a way to bring only the window to the front.
160  bool result =
161      [[NSRunningApplication runningApplicationWithProcessIdentifier: pid]
162          activateWithOptions: NSApplicationActivateIgnoringOtherApps];
163
164  CFRelease(window_id_array);
165  CFRelease(window_array);
166  return result;
167}
168
169void WindowCapturerMac::Start(Callback* callback) {
170  assert(!callback_);
171  assert(callback);
172
173  callback_ = callback;
174}
175
176void WindowCapturerMac::Capture(const DesktopRegion& region) {
177  if (!IsWindowValid(window_id_)) {
178    callback_->OnCaptureCompleted(NULL);
179    return;
180  }
181
182  CGWindowID on_screen_window = window_id_;
183  if (full_screen_chrome_window_detector_) {
184    CGWindowID full_screen_window =
185        full_screen_chrome_window_detector_->FindFullScreenWindow(window_id_);
186
187    if (full_screen_window != kCGNullWindowID)
188      on_screen_window = full_screen_window;
189  }
190
191  CGImageRef window_image = CGWindowListCreateImage(
192      CGRectNull, kCGWindowListOptionIncludingWindow,
193      on_screen_window, kCGWindowImageBoundsIgnoreFraming);
194
195  if (!window_image) {
196    callback_->OnCaptureCompleted(NULL);
197    return;
198  }
199
200  int bits_per_pixel = CGImageGetBitsPerPixel(window_image);
201  if (bits_per_pixel != 32) {
202    LOG(LS_ERROR) << "Unsupported window image depth: " << bits_per_pixel;
203    CFRelease(window_image);
204    callback_->OnCaptureCompleted(NULL);
205    return;
206  }
207
208  int width = CGImageGetWidth(window_image);
209  int height = CGImageGetHeight(window_image);
210  CGDataProviderRef provider = CGImageGetDataProvider(window_image);
211  CFDataRef cf_data = CGDataProviderCopyData(provider);
212  DesktopFrame* frame = new BasicDesktopFrame(
213      DesktopSize(width, height));
214
215  int src_stride = CGImageGetBytesPerRow(window_image);
216  const uint8_t* src_data = CFDataGetBytePtr(cf_data);
217  for (int y = 0; y < height; ++y) {
218    memcpy(frame->data() + frame->stride() * y, src_data + src_stride * y,
219           DesktopFrame::kBytesPerPixel * width);
220  }
221
222  CFRelease(cf_data);
223  CFRelease(window_image);
224
225  frame->mutable_updated_region()->SetRect(
226      DesktopRect::MakeSize(frame->size()));
227
228  callback_->OnCaptureCompleted(frame);
229
230  if (full_screen_chrome_window_detector_)
231    full_screen_chrome_window_detector_->UpdateWindowListIfNeeded(window_id_);
232}
233
234}  // namespace
235
236// static
237WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
238  return new WindowCapturerMac(options.full_screen_chrome_window_detector());
239}
240
241}  // namespace webrtc
242