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