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 #include <algorithm>
13 #include <cwctype>
14 #include <memory>
15 #include <string>
16 #include <vector>
17 #include "rtc_base/arraysize.h"
18 #include "rtc_base/logging.h"  // For RTC_LOG_GLE
19 #include "rtc_base/string_utils.h"
20 
21 namespace webrtc {
22 namespace {
23 
WindowText(HWND window)24 std::string WindowText(HWND window) {
25   size_t len = ::GetWindowTextLength(window);
26   if (len == 0)
27     return std::string();
28 
29   std::vector<wchar_t> buffer(len + 1, 0);
30   size_t copied = ::GetWindowTextW(window, buffer.data(), buffer.size());
31   if (copied == 0)
32     return std::string();
33   return rtc::ToUtf8(buffer.data(), copied);
34 }
35 
WindowProcessId(HWND window)36 DWORD WindowProcessId(HWND window) {
37   DWORD dwProcessId = 0;
38   ::GetWindowThreadProcessId(window, &dwProcessId);
39   return dwProcessId;
40 }
41 
FileNameFromPath(const std::wstring & path)42 std::wstring FileNameFromPath(const std::wstring& path) {
43   auto found = path.rfind(L"\\");
44   if (found == std::string::npos)
45     return path;
46   return path.substr(found + 1);
47 }
48 
49 // Returns windows which belong to given process id
50 // |sources| is a full list of available windows
51 // |processId| is a process identifier (window owner)
52 // |window_to_exclude| is a window to be exluded from result
GetProcessWindows(const DesktopCapturer::SourceList & sources,DWORD processId,HWND window_to_exclude)53 DesktopCapturer::SourceList GetProcessWindows(
54     const DesktopCapturer::SourceList& sources,
55     DWORD processId,
56     HWND window_to_exclude) {
57   DesktopCapturer::SourceList result;
58   std::copy_if(sources.begin(), sources.end(), std::back_inserter(result),
59                [&](DesktopCapturer::Source source) {
60                  const HWND source_hwnd = reinterpret_cast<HWND>(source.id);
61                  return window_to_exclude != source_hwnd &&
62                         WindowProcessId(source_hwnd) == processId;
63                });
64   return result;
65 }
66 
67 class FullScreenPowerPointHandler : public FullScreenApplicationHandler {
68  public:
FullScreenPowerPointHandler(DesktopCapturer::SourceId sourceId)69   explicit FullScreenPowerPointHandler(DesktopCapturer::SourceId sourceId)
70       : FullScreenApplicationHandler(sourceId) {}
71 
~FullScreenPowerPointHandler()72   ~FullScreenPowerPointHandler() override {}
73 
FindFullScreenWindow(const DesktopCapturer::SourceList & window_list,int64_t timestamp) const74   DesktopCapturer::SourceId FindFullScreenWindow(
75       const DesktopCapturer::SourceList& window_list,
76       int64_t timestamp) const override {
77     if (window_list.empty())
78       return 0;
79 
80     HWND original_window = reinterpret_cast<HWND>(GetSourceId());
81     DWORD process_id = WindowProcessId(original_window);
82 
83     DesktopCapturer::SourceList powerpoint_windows =
84         GetProcessWindows(window_list, process_id, original_window);
85 
86     if (powerpoint_windows.empty())
87       return 0;
88 
89     if (GetWindowType(original_window) != WindowType::kEditor)
90       return 0;
91 
92     const auto original_document = GetDocumentFromEditorTitle(original_window);
93 
94     for (const auto& source : powerpoint_windows) {
95       HWND window = reinterpret_cast<HWND>(source.id);
96 
97       // Looking for slide show window for the same document
98       if (GetWindowType(window) != WindowType::kSlideShow ||
99           GetDocumentFromSlideShowTitle(window) != original_document) {
100         continue;
101       }
102 
103       return source.id;
104     }
105 
106     return 0;
107   }
108 
109  private:
110   enum class WindowType { kEditor, kSlideShow, kOther };
111 
GetWindowType(HWND window) const112   WindowType GetWindowType(HWND window) const {
113     if (IsEditorWindow(window))
114       return WindowType::kEditor;
115     else if (IsSlideShowWindow(window))
116       return WindowType::kSlideShow;
117     else
118       return WindowType::kOther;
119   }
120 
121   constexpr static char kDocumentTitleSeparator[] = " - ";
122 
GetDocumentFromEditorTitle(HWND window) const123   std::string GetDocumentFromEditorTitle(HWND window) const {
124     std::string title = WindowText(window);
125     auto position = title.find(kDocumentTitleSeparator);
126     return rtc::string_trim(title.substr(0, position));
127   }
128 
GetDocumentFromSlideShowTitle(HWND window) const129   std::string GetDocumentFromSlideShowTitle(HWND window) const {
130     std::string title = WindowText(window);
131     auto left_pos = title.find(kDocumentTitleSeparator);
132     auto right_pos = title.rfind(kDocumentTitleSeparator);
133     constexpr size_t kSeparatorLength = arraysize(kDocumentTitleSeparator) - 1;
134     if (left_pos == std::string::npos || right_pos == std::string::npos)
135       return title;
136 
137     if (right_pos > left_pos + kSeparatorLength) {
138       auto result_len = right_pos - left_pos - kSeparatorLength;
139       auto document = title.substr(left_pos + kSeparatorLength, result_len);
140       return rtc::string_trim(document);
141     } else {
142       auto document =
143           title.substr(left_pos + kSeparatorLength, std::wstring::npos);
144       return rtc::string_trim(document);
145     }
146   }
147 
IsEditorWindow(HWND window) const148   bool IsEditorWindow(HWND window) const {
149     constexpr WCHAR kScreenClassName[] = L"PPTFrameClass";
150     constexpr size_t kScreenClassNameLength = arraysize(kScreenClassName) - 1;
151 
152     // We need to verify that window class is equal to |kScreenClassName|.
153     // To do that we need a buffer large enough to include a null terminated
154     // string one code point bigger than |kScreenClassName|. It will help us to
155     // check that size of class name string returned by GetClassNameW is equal
156     // to |kScreenClassNameLength| not being limited by size of buffer (case
157     // when |kScreenClassName| is a prefix for class name string).
158     WCHAR buffer[arraysize(kScreenClassName) + 3];
159     const int length = ::GetClassNameW(window, buffer, arraysize(buffer));
160     if (length != kScreenClassNameLength)
161       return false;
162     return wcsncmp(buffer, kScreenClassName, kScreenClassNameLength) == 0;
163   }
164 
IsSlideShowWindow(HWND window) const165   bool IsSlideShowWindow(HWND window) const {
166     const LONG style = ::GetWindowLong(window, GWL_STYLE);
167     const bool min_box = WS_MINIMIZEBOX & style;
168     const bool max_box = WS_MAXIMIZEBOX & style;
169     return !min_box && !max_box;
170   }
171 };
172 
GetPathByWindowId(HWND window_id)173 std::wstring GetPathByWindowId(HWND window_id) {
174   DWORD process_id = WindowProcessId(window_id);
175   HANDLE process =
176       ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id);
177   if (process == NULL)
178     return L"";
179   DWORD path_len = MAX_PATH;
180   WCHAR path[MAX_PATH];
181   std::wstring result;
182   if (::QueryFullProcessImageNameW(process, 0, path, &path_len))
183     result = std::wstring(path, path_len);
184   else
185     RTC_LOG_GLE(LS_ERROR) << "QueryFullProcessImageName failed.";
186 
187   ::CloseHandle(process);
188   return result;
189 }
190 
191 }  // namespace
192 
193 std::unique_ptr<FullScreenApplicationHandler>
CreateFullScreenWinApplicationHandler(DesktopCapturer::SourceId source_id)194 CreateFullScreenWinApplicationHandler(DesktopCapturer::SourceId source_id) {
195   std::unique_ptr<FullScreenApplicationHandler> result;
196   std::wstring exe_path = GetPathByWindowId(reinterpret_cast<HWND>(source_id));
197   std::wstring file_name = FileNameFromPath(exe_path);
198   std::transform(file_name.begin(), file_name.end(), file_name.begin(),
199                  std::towupper);
200 
201   if (file_name == L"POWERPNT.EXE") {
202     result = std::make_unique<FullScreenPowerPointHandler>(source_id);
203   }
204 
205   return result;
206 }
207 
208 }  // namespace webrtc
209