xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/win/full_screen_win_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/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