xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/window_capturer_mac.mm (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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