xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/mac/full_screen_mac_application_handler.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2019 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 "modules/desktop_capture/mac/full_screen_mac_application_handler.h"
12 
13 #include <libproc.h>
14 
15 #include <algorithm>
16 #include <functional>
17 #include <string>
18 
19 #include "absl/strings/match.h"
20 #include "absl/strings/string_view.h"
21 #include "api/function_view.h"
22 #include "modules/desktop_capture/mac/window_list_utils.h"
23 
24 namespace webrtc {
25 namespace {
26 
27 static constexpr const char* kPowerPointSlideShowTitles[] = {
28     "PowerPoint-Bildschirmpräsentation",
29     "Προβολή παρουσίασης PowerPoint",
30     "PowerPoint スライド ショー",
31     "PowerPoint Slide Show",
32     "PowerPoint 幻灯片放映",
33     "Presentación de PowerPoint",
34     "PowerPoint-slideshow",
35     "Presentazione di PowerPoint",
36     "Prezentácia programu PowerPoint",
37     "Apresentação do PowerPoint",
38     "PowerPoint-bildspel",
39     "Prezentace v aplikaci PowerPoint",
40     "PowerPoint 슬라이드 쇼",
41     "PowerPoint-lysbildefremvisning",
42     "PowerPoint-vetítés",
43     "PowerPoint Slayt Gösterisi",
44     "Pokaz slajdów programu PowerPoint",
45     "PowerPoint 投影片放映",
46     "Демонстрация PowerPoint",
47     "Diaporama PowerPoint",
48     "PowerPoint-diaesitys",
49     "Peragaan Slide PowerPoint",
50     "PowerPoint-diavoorstelling",
51     "การนำเสนอสไลด์ PowerPoint",
52     "Apresentação de slides do PowerPoint",
53     "הצגת שקופיות של PowerPoint",
54     "عرض شرائح في PowerPoint"};
55 
56 class FullScreenMacApplicationHandler : public FullScreenApplicationHandler {
57  public:
58   using TitlePredicate =
59       std::function<bool(absl::string_view, absl::string_view)>;
60 
FullScreenMacApplicationHandler(DesktopCapturer::SourceId sourceId,TitlePredicate title_predicate,bool ignore_original_window)61   FullScreenMacApplicationHandler(DesktopCapturer::SourceId sourceId,
62                                   TitlePredicate title_predicate,
63                                   bool ignore_original_window)
64       : FullScreenApplicationHandler(sourceId),
65         title_predicate_(title_predicate),
66         owner_pid_(GetWindowOwnerPid(sourceId)),
67         ignore_original_window_(ignore_original_window) {}
68 
69  protected:
70   using CachePredicate =
71       rtc::FunctionView<bool(const DesktopCapturer::Source&)>;
72 
InvalidateCacheIfNeeded(const DesktopCapturer::SourceList & source_list,int64_t timestamp,CachePredicate predicate) const73   void InvalidateCacheIfNeeded(const DesktopCapturer::SourceList& source_list,
74                                int64_t timestamp,
75                                CachePredicate predicate) const {
76     if (timestamp != cache_timestamp_) {
77       cache_sources_.clear();
78       std::copy_if(source_list.begin(), source_list.end(),
79                    std::back_inserter(cache_sources_), predicate);
80       cache_timestamp_ = timestamp;
81     }
82   }
83 
FindFullScreenWindowWithSamePid(const DesktopCapturer::SourceList & source_list,int64_t timestamp) const84   WindowId FindFullScreenWindowWithSamePid(
85       const DesktopCapturer::SourceList& source_list,
86       int64_t timestamp) const {
87     InvalidateCacheIfNeeded(source_list, timestamp,
88                             [&](const DesktopCapturer::Source& src) {
89                               return src.id != GetSourceId() &&
90                                      GetWindowOwnerPid(src.id) == owner_pid_;
91                             });
92     if (cache_sources_.empty())
93       return kCGNullWindowID;
94 
95     const auto original_window = GetSourceId();
96     const std::string title = GetWindowTitle(original_window);
97 
98     // We can ignore any windows with empty titles cause regardless type of
99     // application it's impossible to verify that full screen window and
100     // original window are related to the same document.
101     if (title.empty())
102       return kCGNullWindowID;
103 
104     MacDesktopConfiguration desktop_config =
105         MacDesktopConfiguration::GetCurrent(
106             MacDesktopConfiguration::TopLeftOrigin);
107 
108     const auto it = std::find_if(
109         cache_sources_.begin(), cache_sources_.end(),
110         [&](const DesktopCapturer::Source& src) {
111           const std::string window_title = GetWindowTitle(src.id);
112 
113           if (window_title.empty())
114             return false;
115 
116           if (title_predicate_ && !title_predicate_(title, window_title))
117             return false;
118 
119           return IsWindowFullScreen(desktop_config, src.id);
120         });
121 
122     return it != cache_sources_.end() ? it->id : 0;
123   }
124 
FindFullScreenWindow(const DesktopCapturer::SourceList & source_list,int64_t timestamp) const125   DesktopCapturer::SourceId FindFullScreenWindow(
126       const DesktopCapturer::SourceList& source_list,
127       int64_t timestamp) const override {
128     return !ignore_original_window_ && IsWindowOnScreen(GetSourceId())
129                ? 0
130                : FindFullScreenWindowWithSamePid(source_list, timestamp);
131   }
132 
133  protected:
134   const TitlePredicate title_predicate_;
135   const int owner_pid_;
136   const bool ignore_original_window_;
137   mutable int64_t cache_timestamp_ = 0;
138   mutable DesktopCapturer::SourceList cache_sources_;
139 };
140 
equal_title_predicate(absl::string_view original_title,absl::string_view title)141 bool equal_title_predicate(absl::string_view original_title,
142                            absl::string_view title) {
143   return original_title == title;
144 }
145 
slide_show_title_predicate(absl::string_view original_title,absl::string_view title)146 bool slide_show_title_predicate(absl::string_view original_title,
147                                 absl::string_view title) {
148   if (title.find(original_title) == absl::string_view::npos)
149     return false;
150 
151   for (const char* pp_slide_title : kPowerPointSlideShowTitles) {
152     if (absl::StartsWith(title, pp_slide_title))
153       return true;
154   }
155   return false;
156 }
157 
158 class OpenOfficeApplicationHandler : public FullScreenMacApplicationHandler {
159  public:
OpenOfficeApplicationHandler(DesktopCapturer::SourceId sourceId)160   OpenOfficeApplicationHandler(DesktopCapturer::SourceId sourceId)
161       : FullScreenMacApplicationHandler(sourceId, nullptr, false) {}
162 
FindFullScreenWindow(const DesktopCapturer::SourceList & source_list,int64_t timestamp) const163   DesktopCapturer::SourceId FindFullScreenWindow(
164       const DesktopCapturer::SourceList& source_list,
165       int64_t timestamp) const override {
166     InvalidateCacheIfNeeded(source_list, timestamp,
167                             [&](const DesktopCapturer::Source& src) {
168                               return GetWindowOwnerPid(src.id) == owner_pid_;
169                             });
170 
171     const auto original_window = GetSourceId();
172     const std::string original_title = GetWindowTitle(original_window);
173 
174     // Check if we have only one document window, otherwise it's not possible
175     // to securely match a document window and a slide show window which has
176     // empty title.
177     if (std::any_of(cache_sources_.begin(), cache_sources_.end(),
178                     [&original_title](const DesktopCapturer::Source& src) {
179                       return src.title.length() && src.title != original_title;
180                     })) {
181       return kCGNullWindowID;
182     }
183 
184     MacDesktopConfiguration desktop_config =
185         MacDesktopConfiguration::GetCurrent(
186             MacDesktopConfiguration::TopLeftOrigin);
187 
188     // Looking for slide show window,
189     // it must be a full screen window with empty title
190     const auto slide_show_window = std::find_if(
191         cache_sources_.begin(), cache_sources_.end(), [&](const auto& src) {
192           return src.title.empty() &&
193                  IsWindowFullScreen(desktop_config, src.id);
194         });
195 
196     if (slide_show_window == cache_sources_.end()) {
197       return kCGNullWindowID;
198     }
199 
200     return slide_show_window->id;
201   }
202 };
203 
204 }  // namespace
205 
206 std::unique_ptr<FullScreenApplicationHandler>
CreateFullScreenMacApplicationHandler(DesktopCapturer::SourceId sourceId)207 CreateFullScreenMacApplicationHandler(DesktopCapturer::SourceId sourceId) {
208   std::unique_ptr<FullScreenApplicationHandler> result;
209   int pid = GetWindowOwnerPid(sourceId);
210   char buffer[PROC_PIDPATHINFO_MAXSIZE];
211   int path_length = proc_pidpath(pid, buffer, sizeof(buffer));
212   if (path_length > 0) {
213     const char* last_slash = strrchr(buffer, '/');
214     const std::string name{last_slash ? last_slash + 1 : buffer};
215     const std::string owner_name = GetWindowOwnerName(sourceId);
216     FullScreenMacApplicationHandler::TitlePredicate predicate = nullptr;
217     bool ignore_original_window = false;
218     if (name.find("Google Chrome") == 0 || name == "Chromium") {
219       predicate = equal_title_predicate;
220     } else if (name == "Microsoft PowerPoint") {
221       predicate = slide_show_title_predicate;
222       ignore_original_window = true;
223     } else if (name == "Keynote") {
224       predicate = equal_title_predicate;
225     } else if (owner_name == "OpenOffice") {
226       return std::make_unique<OpenOfficeApplicationHandler>(sourceId);
227     }
228 
229     if (predicate) {
230       result.reset(new FullScreenMacApplicationHandler(sourceId, predicate,
231                                                        ignore_original_window));
232     }
233   }
234 
235   return result;
236 }
237 
238 }  // namespace webrtc
239