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/win/full_screen_win_application_handler.h"
12
13 #include <algorithm>
14 #include <memory>
15 #include <string>
16 #include <vector>
17
18 #include "absl/strings/ascii.h"
19 #include "absl/strings/match.h"
20 #include "modules/desktop_capture/win/screen_capture_utils.h"
21 #include "modules/desktop_capture/win/window_capture_utils.h"
22 #include "rtc_base/arraysize.h"
23 #include "rtc_base/logging.h" // For RTC_LOG_GLE
24 #include "rtc_base/string_utils.h"
25
26 namespace webrtc {
27 namespace {
28
29 // Utility function to verify that `window` has class name equal to `class_name`
CheckWindowClassName(HWND window,const wchar_t * class_name)30 bool CheckWindowClassName(HWND window, const wchar_t* class_name) {
31 const size_t classNameLength = wcslen(class_name);
32
33 // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassa
34 // says lpszClassName field in WNDCLASS is limited by 256 symbols, so we don't
35 // need to have a buffer bigger than that.
36 constexpr size_t kMaxClassNameLength = 256;
37 WCHAR buffer[kMaxClassNameLength];
38
39 const int length = ::GetClassNameW(window, buffer, kMaxClassNameLength);
40 if (length <= 0)
41 return false;
42
43 if (static_cast<size_t>(length) != classNameLength)
44 return false;
45 return wcsncmp(buffer, class_name, classNameLength) == 0;
46 }
47
WindowText(HWND window)48 std::string WindowText(HWND window) {
49 size_t len = ::GetWindowTextLength(window);
50 if (len == 0)
51 return std::string();
52
53 std::vector<wchar_t> buffer(len + 1, 0);
54 size_t copied = ::GetWindowTextW(window, buffer.data(), buffer.size());
55 if (copied == 0)
56 return std::string();
57 return rtc::ToUtf8(buffer.data(), copied);
58 }
59
WindowProcessId(HWND window)60 DWORD WindowProcessId(HWND window) {
61 DWORD dwProcessId = 0;
62 ::GetWindowThreadProcessId(window, &dwProcessId);
63 return dwProcessId;
64 }
65
FileNameFromPath(const std::wstring & path)66 std::wstring FileNameFromPath(const std::wstring& path) {
67 auto found = path.rfind(L"\\");
68 if (found == std::string::npos)
69 return path;
70 return path.substr(found + 1);
71 }
72
73 // Returns windows which belong to given process id
74 // `sources` is a full list of available windows
75 // `processId` is a process identifier (window owner)
76 // `window_to_exclude` is a window to be exluded from result
GetProcessWindows(const DesktopCapturer::SourceList & sources,DWORD processId,HWND window_to_exclude)77 DesktopCapturer::SourceList GetProcessWindows(
78 const DesktopCapturer::SourceList& sources,
79 DWORD processId,
80 HWND window_to_exclude) {
81 DesktopCapturer::SourceList result;
82 std::copy_if(sources.begin(), sources.end(), std::back_inserter(result),
83 [&](DesktopCapturer::Source source) {
84 const HWND source_hwnd = reinterpret_cast<HWND>(source.id);
85 return window_to_exclude != source_hwnd &&
86 WindowProcessId(source_hwnd) == processId;
87 });
88 return result;
89 }
90
91 class FullScreenPowerPointHandler : public FullScreenApplicationHandler {
92 public:
FullScreenPowerPointHandler(DesktopCapturer::SourceId sourceId)93 explicit FullScreenPowerPointHandler(DesktopCapturer::SourceId sourceId)
94 : FullScreenApplicationHandler(sourceId) {}
95
~FullScreenPowerPointHandler()96 ~FullScreenPowerPointHandler() override {}
97
FindFullScreenWindow(const DesktopCapturer::SourceList & window_list,int64_t timestamp) const98 DesktopCapturer::SourceId FindFullScreenWindow(
99 const DesktopCapturer::SourceList& window_list,
100 int64_t timestamp) const override {
101 if (window_list.empty())
102 return 0;
103
104 HWND original_window = reinterpret_cast<HWND>(GetSourceId());
105 DWORD process_id = WindowProcessId(original_window);
106
107 DesktopCapturer::SourceList powerpoint_windows =
108 GetProcessWindows(window_list, process_id, original_window);
109
110 if (powerpoint_windows.empty())
111 return 0;
112
113 if (GetWindowType(original_window) != WindowType::kEditor)
114 return 0;
115
116 const auto original_document = GetDocumentFromEditorTitle(original_window);
117
118 for (const auto& source : powerpoint_windows) {
119 HWND window = reinterpret_cast<HWND>(source.id);
120
121 // Looking for slide show window for the same document
122 if (GetWindowType(window) != WindowType::kSlideShow ||
123 GetDocumentFromSlideShowTitle(window) != original_document) {
124 continue;
125 }
126
127 return source.id;
128 }
129
130 return 0;
131 }
132
133 private:
134 enum class WindowType { kEditor, kSlideShow, kOther };
135
GetWindowType(HWND window) const136 WindowType GetWindowType(HWND window) const {
137 if (IsEditorWindow(window))
138 return WindowType::kEditor;
139 else if (IsSlideShowWindow(window))
140 return WindowType::kSlideShow;
141 else
142 return WindowType::kOther;
143 }
144
145 constexpr static char kDocumentTitleSeparator[] = " - ";
146
GetDocumentFromEditorTitle(HWND window) const147 std::string GetDocumentFromEditorTitle(HWND window) const {
148 std::string title = WindowText(window);
149 auto position = title.find(kDocumentTitleSeparator);
150 return std::string(absl::StripAsciiWhitespace(
151 absl::string_view(title).substr(0, position)));
152 }
153
GetDocumentFromSlideShowTitle(HWND window) const154 std::string GetDocumentFromSlideShowTitle(HWND window) const {
155 std::string title = WindowText(window);
156 auto left_pos = title.find(kDocumentTitleSeparator);
157 auto right_pos = title.rfind(kDocumentTitleSeparator);
158 constexpr size_t kSeparatorLength = arraysize(kDocumentTitleSeparator) - 1;
159 if (left_pos == std::string::npos || right_pos == std::string::npos)
160 return title;
161
162 if (right_pos > left_pos + kSeparatorLength) {
163 auto result_len = right_pos - left_pos - kSeparatorLength;
164 auto document = absl::string_view(title).substr(
165 left_pos + kSeparatorLength, result_len);
166 return std::string(absl::StripAsciiWhitespace(document));
167 } else {
168 auto document = absl::string_view(title).substr(
169 left_pos + kSeparatorLength, std::wstring::npos);
170 return std::string(absl::StripAsciiWhitespace(document));
171 }
172 }
173
IsEditorWindow(HWND window) const174 bool IsEditorWindow(HWND window) const {
175 return CheckWindowClassName(window, L"PPTFrameClass");
176 }
177
IsSlideShowWindow(HWND window) const178 bool IsSlideShowWindow(HWND window) const {
179 const LONG style = ::GetWindowLong(window, GWL_STYLE);
180 const bool min_box = WS_MINIMIZEBOX & style;
181 const bool max_box = WS_MAXIMIZEBOX & style;
182 return !min_box && !max_box;
183 }
184 };
185
186 class OpenOfficeApplicationHandler : public FullScreenApplicationHandler {
187 public:
OpenOfficeApplicationHandler(DesktopCapturer::SourceId sourceId)188 explicit OpenOfficeApplicationHandler(DesktopCapturer::SourceId sourceId)
189 : FullScreenApplicationHandler(sourceId) {}
190
FindFullScreenWindow(const DesktopCapturer::SourceList & window_list,int64_t timestamp) const191 DesktopCapturer::SourceId FindFullScreenWindow(
192 const DesktopCapturer::SourceList& window_list,
193 int64_t timestamp) const override {
194 if (window_list.empty())
195 return 0;
196
197 DWORD process_id = WindowProcessId(reinterpret_cast<HWND>(GetSourceId()));
198
199 DesktopCapturer::SourceList app_windows =
200 GetProcessWindows(window_list, process_id, nullptr);
201
202 DesktopCapturer::SourceList document_windows;
203 std::copy_if(
204 app_windows.begin(), app_windows.end(),
205 std::back_inserter(document_windows),
206 [this](const DesktopCapturer::Source& x) { return IsEditorWindow(x); });
207
208 // Check if we have only one document window, otherwise it's not possible
209 // to securely match a document window and a slide show window which has
210 // empty title.
211 if (document_windows.size() != 1) {
212 return 0;
213 }
214
215 // Check if document window has been selected as a source
216 if (document_windows.front().id != GetSourceId()) {
217 return 0;
218 }
219
220 // Check if we have a slide show window.
221 auto slide_show_window =
222 std::find_if(app_windows.begin(), app_windows.end(),
223 [this](const DesktopCapturer::Source& x) {
224 return IsSlideShowWindow(x);
225 });
226
227 if (slide_show_window == app_windows.end())
228 return 0;
229
230 return slide_show_window->id;
231 }
232
233 private:
IsEditorWindow(const DesktopCapturer::Source & source) const234 bool IsEditorWindow(const DesktopCapturer::Source& source) const {
235 if (source.title.empty()) {
236 return false;
237 }
238
239 return CheckWindowClassName(reinterpret_cast<HWND>(source.id), L"SALFRAME");
240 }
241
IsSlideShowWindow(const DesktopCapturer::Source & source) const242 bool IsSlideShowWindow(const DesktopCapturer::Source& source) const {
243 // Check title size to filter out a Presenter Control window which shares
244 // window class with Slide Show window but has non empty title.
245 if (!source.title.empty()) {
246 return false;
247 }
248
249 return CheckWindowClassName(reinterpret_cast<HWND>(source.id),
250 L"SALTMPSUBFRAME");
251 }
252 };
253
GetPathByWindowId(HWND window_id)254 std::wstring GetPathByWindowId(HWND window_id) {
255 DWORD process_id = WindowProcessId(window_id);
256 HANDLE process =
257 ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id);
258 if (process == NULL)
259 return L"";
260 DWORD path_len = MAX_PATH;
261 WCHAR path[MAX_PATH];
262 std::wstring result;
263 if (::QueryFullProcessImageNameW(process, 0, path, &path_len))
264 result = std::wstring(path, path_len);
265 else
266 RTC_LOG_GLE(LS_ERROR) << "QueryFullProcessImageName failed.";
267
268 ::CloseHandle(process);
269 return result;
270 }
271
272 } // namespace
273
274 std::unique_ptr<FullScreenApplicationHandler>
CreateFullScreenWinApplicationHandler(DesktopCapturer::SourceId source_id)275 CreateFullScreenWinApplicationHandler(DesktopCapturer::SourceId source_id) {
276 std::unique_ptr<FullScreenApplicationHandler> result;
277 HWND hwnd = reinterpret_cast<HWND>(source_id);
278 std::wstring exe_path = GetPathByWindowId(hwnd);
279 std::wstring file_name = FileNameFromPath(exe_path);
280 std::transform(file_name.begin(), file_name.end(), file_name.begin(),
281 std::towupper);
282
283 if (file_name == L"POWERPNT.EXE") {
284 result = std::make_unique<FullScreenPowerPointHandler>(source_id);
285 } else if (file_name == L"SOFFICE.BIN" &&
286 absl::EndsWith(WindowText(hwnd), "OpenOffice Impress")) {
287 result = std::make_unique<OpenOfficeApplicationHandler>(source_id);
288 }
289
290 return result;
291 }
292
293 } // namespace webrtc
294