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