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