window_capturer_mac.mm (7856B)
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 30 namespace webrtc { 31 32 namespace { 33 34 // Returns true if the window exists. 35 bool 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 47 class WindowCapturerMac : public DesktopCapturer { 48 public: 49 explicit WindowCapturerMac( 50 webrtc::scoped_refptr<FullScreenWindowDetector> 51 full_screen_window_detector, 52 webrtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor); 53 ~WindowCapturerMac() override; 54 55 WindowCapturerMac(const WindowCapturerMac&) = delete; 56 WindowCapturerMac& operator=(const WindowCapturerMac&) = delete; 57 58 // DesktopCapturer interface. 59 void Start(Callback* callback) override; 60 void CaptureFrame() override; 61 bool GetSourceList(SourceList* sources) override; 62 bool SelectSource(SourceId id) override; 63 bool FocusOnSelectedSource() override; 64 bool IsOccluded(const DesktopVector& pos) override; 65 66 private: 67 Callback* callback_ = nullptr; 68 69 // The window being captured. 70 CGWindowID window_id_ = 0; 71 72 webrtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector_; 73 74 const webrtc::scoped_refptr<DesktopConfigurationMonitor> 75 configuration_monitor_; 76 77 WindowFinderMac window_finder_; 78 79 // Used to make sure that we only log the usage of fullscreen detection once. 80 bool fullscreen_usage_logged_ = false; 81 }; 82 83 WindowCapturerMac::WindowCapturerMac( 84 webrtc::scoped_refptr<FullScreenWindowDetector> full_screen_window_detector, 85 webrtc::scoped_refptr<DesktopConfigurationMonitor> configuration_monitor) 86 : full_screen_window_detector_(std::move(full_screen_window_detector)), 87 configuration_monitor_(std::move(configuration_monitor)), 88 window_finder_(configuration_monitor_) {} 89 90 WindowCapturerMac::~WindowCapturerMac() {} 91 92 bool WindowCapturerMac::GetSourceList(SourceList* sources) { 93 return webrtc::GetWindowList(sources, true, true); 94 } 95 96 bool WindowCapturerMac::SelectSource(SourceId id) { 97 if (!IsWindowValid(id)) return false; 98 window_id_ = id; 99 return true; 100 } 101 102 bool WindowCapturerMac::FocusOnSelectedSource() { 103 if (!window_id_) return false; 104 105 CGWindowID ids[1]; 106 ids[0] = window_id_; 107 CFArrayRef window_id_array = 108 CFArrayCreate(nullptr, reinterpret_cast<const void**>(&ids), 1, nullptr); 109 110 CFArrayRef window_array = 111 CGWindowListCreateDescriptionFromArray(window_id_array); 112 if (!window_array || 0 == CFArrayGetCount(window_array)) { 113 // Could not find the window. It might have been closed. 114 RTC_LOG(LS_INFO) << "Window not found"; 115 CFRelease(window_id_array); 116 return false; 117 } 118 119 CFDictionaryRef window = reinterpret_cast<CFDictionaryRef>( 120 CFArrayGetValueAtIndex(window_array, 0)); 121 CFNumberRef pid_ref = reinterpret_cast<CFNumberRef>( 122 CFDictionaryGetValue(window, kCGWindowOwnerPID)); 123 124 int pid; 125 CFNumberGetValue(pid_ref, kCFNumberIntType, &pid); 126 127 // TODO(jiayl): this will bring the process main window to the front. We 128 // should find a way to bring only the window to the front. 129 bool result = [[NSRunningApplication 130 runningApplicationWithProcessIdentifier:pid] activateWithOptions:0]; 131 132 CFRelease(window_id_array); 133 CFRelease(window_array); 134 return result; 135 } 136 137 bool WindowCapturerMac::IsOccluded(const DesktopVector& pos) { 138 DesktopVector sys_pos = pos; 139 if (configuration_monitor_) { 140 auto configuration = configuration_monitor_->desktop_configuration(); 141 sys_pos = pos.add(configuration.bounds.top_left()); 142 } 143 return window_finder_.GetWindowUnderPoint(sys_pos) != window_id_; 144 } 145 146 void WindowCapturerMac::Start(Callback* callback) { 147 RTC_DCHECK(!callback_); 148 RTC_DCHECK(callback); 149 150 callback_ = callback; 151 } 152 153 void WindowCapturerMac::CaptureFrame() { 154 TRACE_EVENT0("webrtc", "WindowCapturerMac::CaptureFrame"); 155 156 if (!IsWindowValid(window_id_)) { 157 RTC_LOG(LS_ERROR) << "The window is not valid any longer."; 158 callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); 159 return; 160 } 161 162 CGWindowID on_screen_window = window_id_; 163 if (full_screen_window_detector_) { 164 full_screen_window_detector_->UpdateWindowListIfNeeded( 165 window_id_, [](DesktopCapturer::SourceList* sources) { 166 // Not using webrtc::GetWindowList(sources, true, false) 167 // as it doesn't allow to have in the result window with 168 // empty title along with titled window owned by the same pid. 169 return webrtc::GetWindowList( 170 [sources](CFDictionaryRef window) { 171 WindowId window_id = GetWindowId(window); 172 int pid = GetWindowOwnerPid(window); 173 if (window_id != kNullWindowId) { 174 sources->push_back(DesktopCapturer::Source{ 175 window_id, pid, GetWindowTitle(window)}); 176 } 177 return true; 178 }, 179 true, 180 false); 181 }); 182 183 CGWindowID full_screen_window = 184 full_screen_window_detector_->FindFullScreenWindow(window_id_); 185 186 if (full_screen_window != kCGNullWindowID) { 187 // If this is the first time this happens, report to UMA that the feature 188 // is active. 189 if (!fullscreen_usage_logged_) { 190 LogDesktopCapturerFullscreenDetectorUsage(); 191 fullscreen_usage_logged_ = true; 192 } 193 on_screen_window = full_screen_window; 194 } 195 } 196 197 std::unique_ptr<DesktopFrame> frame = 198 DesktopFrameCGImage::CreateForWindow(on_screen_window); 199 if (!frame) { 200 RTC_LOG(LS_WARNING) << "Temporarily failed to capture window."; 201 callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr); 202 return; 203 } 204 205 frame->mutable_updated_region()->SetRect( 206 DesktopRect::MakeSize(frame->size())); 207 frame->set_top_left(GetWindowBounds(on_screen_window).top_left()); 208 209 float scale_factor = GetWindowScaleFactor(window_id_, frame->size()); 210 frame->set_dpi( 211 DesktopVector(kStandardDPI * scale_factor, kStandardDPI * scale_factor)); 212 213 callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); 214 } 215 216 } // namespace 217 218 // static 219 std::unique_ptr<DesktopCapturer> DesktopCapturer::CreateRawWindowCapturer( 220 const DesktopCaptureOptions& options) { 221 RTC_LOG(LS_INFO) << "video capture: DesktopCapturer::CreateRawWindowCapturer " 222 "creates DesktopCapturer of type WindowCapturerMac"; 223 return std::unique_ptr<DesktopCapturer>(new WindowCapturerMac( 224 options.full_screen_window_detector(), options.configuration_monitor())); 225 } 226 227 } // namespace webrtc