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 <ApplicationServices/ApplicationServices.h> 12#include <Cocoa/Cocoa.h> 13#include <CoreFoundation/CoreFoundation.h> 14 15#include <utility> 16 17#include "api/scoped_refptr.h" 18#include "modules/desktop_capture/desktop_capture_options.h" 19#include "modules/desktop_capture/desktop_capturer.h" 20#include "modules/desktop_capture/desktop_frame.h" 21#include "modules/desktop_capture/mac/desktop_configuration.h" 22#include "modules/desktop_capture/mac/desktop_configuration_monitor.h" 23#include "modules/desktop_capture/mac/desktop_frame_cgimage.h" 24#include "modules/desktop_capture/mac/window_list_utils.h" 25#include "modules/desktop_capture/window_finder_mac.h" 26#include "rtc_base/checks.h" 27#include "rtc_base/logging.h" 28#include "rtc_base/trace_event.h" 29 30namespace webrtc { 31 32namespace { 33 34// Returns true if the window exists. 35bool IsWindowValid(CGWindowID id) { 36 CFArrayRef window_id_array = 37 CFArrayCreate(nullptr, reinterpret_cast<const void**>(&id), 1, nullptr); 38 CFArrayRef window_array = 39 CGWindowListCreateDescriptionFromArray(window_id_array); 40 bool valid = window_array && CFArrayGetCount(window_array); 41 CFRelease(window_id_array); 42 CFRelease(window_array); 43 44 return valid; 45} 46 47class WindowCapturerMac : public DesktopCapturer { 48 public: 49 explicit WindowCapturerMac( 50 rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector, 51 rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor); 52 ~WindowCapturerMac() override; 53 54 WindowCapturerMac(const WindowCapturerMac&) = delete; 55 WindowCapturerMac& operator=(const WindowCapturerMac&) = delete; 56 57 // DesktopCapturer interface. 58 void Start(Callback* callback) override; 59 void CaptureFrame() override; 60 bool GetSourceList(SourceList* sources) override; 61 bool SelectSource(SourceId id) override; 62 bool FocusOnSelectedSource() override; 63 bool IsOccluded(const DesktopVector& pos) override; 64 65 private: 66 Callback* callback_ = nullptr; 67 68 // The window being captured. 69 CGWindowID window_id_ = 0; 70 71 rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector_; 72 73 const rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor_; 74 75 WindowFinderMac window_finder_; 76}; 77 78WindowCapturerMac::WindowCapturerMac( 79 rtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector, 80 rtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor) 81 : full_screen_window_detector_(std::move(full_screen_window_detector)), 82 configuration_monitor_(std::move(configuration_monitor)), 83 window_finder_(configuration_monitor_) {} 84 85WindowCapturerMac::~WindowCapturerMac() {} 86 87bool WindowCapturerMac::GetSourceList(SourceList* sources) { 88 return webrtc::GetWindowList(sources, true, true); 89} 90 91bool WindowCapturerMac::SelectSource(SourceId id) { 92 if (!IsWindowValid(id)) 93 return false; 94 window_id_ = id; 95 return true; 96} 97 98bool WindowCapturerMac::FocusOnSelectedSource() { 99 if (!window_id_) 100 return false; 101 102 CGWindowID ids[1]; 103 ids[0] = window_id_; 104 CFArrayRef window_id_array = 105 CFArrayCreate(nullptr, reinterpret_cast<const void**>(&ids), 1, nullptr); 106 107 CFArrayRef window_array = 108 CGWindowListCreateDescriptionFromArray(window_id_array); 109 if (!window_array || 0 == CFArrayGetCount(window_array)) { 110 // Could not find the window. It might have been closed. 111 RTC_LOG(LS_INFO) << "Window not found"; 112 CFRelease(window_id_array); 113 return false; 114 } 115 116 CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( 117 CFArrayGetValueAtIndex(window_array, 0)); 118 CFNumberRef pid_ref = reinterpret_cast<CFNumberRef>( 119 CFDictionaryGetValue(window, kCGWindowOwnerPID)); 120 121 int pid; 122 CFNumberGetValue(pid_ref, kCFNumberIntType, &pid); 123 124 // TODO(jiayl): this will bring the process main window to the front. We 125 // should find a way to bring only the window to the front. 126 bool result = 127 [[NSRunningApplication runningApplicationWithProcessIdentifier: pid] 128 activateWithOptions: NSApplicationActivateIgnoringOtherApps]; 129 130 CFRelease(window_id_array); 131 CFRelease(window_array); 132 return result; 133} 134 135bool WindowCapturerMac::IsOccluded(const DesktopVector& pos) { 136 DesktopVector sys_pos = pos; 137 if (configuration_monitor_) { 138 auto configuration = configuration_monitor_->desktop_configuration(); 139 sys_pos = pos.add(configuration.bounds.top_left()); 140 } 141 return window_finder_.GetWindowUnderPoint(sys_pos) != window_id_; 142} 143 144void WindowCapturerMac::Start(Callback* callback) { 145 RTC_DCHECK(!callback_); 146 RTC_DCHECK(callback); 147 148 callback_ = callback; 149} 150 151void WindowCapturerMac::CaptureFrame() { 152 TRACE_EVENT0("webrtc", "WindowCapturerMac::CaptureFrame"); 153 154 if (!IsWindowValid(window_id_)) { 155 RTC_LOG(LS_ERROR) << "The window is not valid any longer."; 156 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); 157 return; 158 } 159 160 CGWindowID on_screen_window = window_id_; 161 if (full_screen_window_detector_) { 162 full_screen_window_detector_->UpdateWindowListIfNeeded( 163 window_id_, [](DesktopCapturer::SourceList* sources) { 164 // Not using webrtc::GetWindowList(sources, true, false) 165 // as it doesn't allow to have in the result window with 166 // empty title along with titled window owned by the same pid. 167 return webrtc::GetWindowList( 168 [sources](CFDictionaryRef window) { 169 WindowId window_id = GetWindowId(window); 170 if (window_id != kNullWindowId) { 171 sources->push_back(DesktopCapturer::Source{window_id, GetWindowTitle(window)}); 172 } 173 return true; 174 }, 175 true, 176 false); 177 }); 178 179 CGWindowID full_screen_window = full_screen_window_detector_->FindFullScreenWindow(window_id_); 180 181 if (full_screen_window != kCGNullWindowID) on_screen_window = full_screen_window; 182 } 183 184 std::unique_ptr<DesktopFrame> frame = DesktopFrameCGImage::CreateForWindow(on_screen_window); 185 if (!frame) { 186 RTC_LOG(LS_WARNING) << "Temporarily failed to capture window."; 187 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); 188 return; 189 } 190 191 frame->mutable_updated_region()->SetRect( 192 DesktopRect::MakeSize(frame->size())); 193 frame->set_top_left(GetWindowBounds(on_screen_window).top_left()); 194 195 float scale_factor = GetWindowScaleFactor(window_id_, frame->size()); 196 frame->set_dpi(DesktopVector(kStandardDPI * scale_factor, kStandardDPI * scale_factor)); 197 198 callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); 199} 200 201} // namespace 202 203// static 204std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawWindowCapturer( 205 const DesktopCaptureOptions& options) { 206 return std::unique_ptr<DesktopCapturer>(new WindowCapturerMac( 207 options.full_screen_window_detector(), options.configuration_monitor())); 208} 209 210} // namespace webrtc 211