1 /*
2 * Copyright (c) 2014 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 "webrtc/modules/desktop_capture/cropping_window_capturer.h"
12
13 #include "webrtc/base/win32.h"
14 #include "webrtc/modules/desktop_capture/win/scoped_gdi_object.h"
15 #include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
16 #include "webrtc/modules/desktop_capture/win/window_capture_utils.h"
17 #include "webrtc/system_wrappers/include/logging.h"
18
19 namespace webrtc {
20
21 namespace {
22
23 // Used to pass input/output data during the EnumWindow call for verifying if
24 // the selected window is on top.
25 struct TopWindowVerifierContext {
TopWindowVerifierContextwebrtc::__anone9714f000111::TopWindowVerifierContext26 TopWindowVerifierContext(HWND selected_window, HWND excluded_window)
27 : selected_window(selected_window),
28 excluded_window(excluded_window),
29 is_top_window(false),
30 selected_window_process_id(0) {}
31
32 HWND selected_window;
33 HWND excluded_window;
34 bool is_top_window;
35 DWORD selected_window_process_id;
36 DesktopRect selected_window_rect;
37 };
38
39 // The function is called during EnumWindow for every window enumerated and is
40 // responsible for verifying if the selected window is on top.
TopWindowVerifier(HWND hwnd,LPARAM param)41 BOOL CALLBACK TopWindowVerifier(HWND hwnd, LPARAM param) {
42 TopWindowVerifierContext* context =
43 reinterpret_cast<TopWindowVerifierContext*>(param);
44
45 if (hwnd == context->selected_window) {
46 context->is_top_window = true;
47 return FALSE;
48 }
49
50 // Ignore the excluded window.
51 if (hwnd == context->excluded_window) {
52 return TRUE;
53 }
54
55 // Ignore hidden or minimized window.
56 if (IsIconic(hwnd) || !IsWindowVisible(hwnd)) {
57 return TRUE;
58 }
59
60 // Ignore descendant/owned windows since we want to capture them.
61 // This check does not work for tooltips and context menus. Drop down menus
62 // and popup windows are fine.
63 if (GetAncestor(hwnd, GA_ROOTOWNER) == context->selected_window) {
64 return TRUE;
65 }
66
67 // If |hwnd| has no title and belongs to the same process, assume it's a
68 // tooltip or context menu from the selected window and ignore it.
69 const size_t kTitleLength = 32;
70 WCHAR window_title[kTitleLength];
71 GetWindowText(hwnd, window_title, kTitleLength);
72 if (wcsnlen_s(window_title, kTitleLength) == 0) {
73 DWORD enumerated_process;
74 GetWindowThreadProcessId(hwnd, &enumerated_process);
75 if (!context->selected_window_process_id) {
76 GetWindowThreadProcessId(context->selected_window,
77 &context->selected_window_process_id);
78 }
79 if (context->selected_window_process_id == enumerated_process) {
80 return TRUE;
81 }
82 }
83
84 // Check if the enumerated window intersects with the selected window.
85 RECT enumerated_rect;
86 if (!GetWindowRect(hwnd, &enumerated_rect)) {
87 // Bail out if failed to get the window area.
88 context->is_top_window = false;
89 return FALSE;
90 }
91
92 DesktopRect intersect_rect = context->selected_window_rect;
93 DesktopRect enumerated_desktop_rect =
94 DesktopRect::MakeLTRB(enumerated_rect.left,
95 enumerated_rect.top,
96 enumerated_rect.right,
97 enumerated_rect.bottom);
98 intersect_rect.IntersectWith(enumerated_desktop_rect);
99
100 // If intersection is not empty, the selected window is not on top.
101 if (!intersect_rect.is_empty()) {
102 context->is_top_window = false;
103 return FALSE;
104 }
105 // Otherwise, keep enumerating.
106 return TRUE;
107 }
108
109 class CroppingWindowCapturerWin : public CroppingWindowCapturer {
110 public:
CroppingWindowCapturerWin(const DesktopCaptureOptions & options)111 CroppingWindowCapturerWin(
112 const DesktopCaptureOptions& options)
113 : CroppingWindowCapturer(options) {}
114
115 private:
116 bool ShouldUseScreenCapturer() override;
117 DesktopRect GetWindowRectInVirtualScreen() override;
118
119 // The region from GetWindowRgn in the desktop coordinate if the region is
120 // rectangular, or the rect from GetWindowRect if the region is not set.
121 DesktopRect window_region_rect_;
122
123 AeroChecker aero_checker_;
124 };
125
ShouldUseScreenCapturer()126 bool CroppingWindowCapturerWin::ShouldUseScreenCapturer() {
127 if (!rtc::IsWindows8OrLater() && aero_checker_.IsAeroEnabled())
128 return false;
129
130 // Check if the window is a translucent layered window.
131 HWND selected = reinterpret_cast<HWND>(selected_window());
132 LONG window_ex_style = GetWindowLong(selected, GWL_EXSTYLE);
133 if (window_ex_style & WS_EX_LAYERED) {
134 COLORREF color_ref_key = 0;
135 BYTE alpha = 0;
136 DWORD flags = 0;
137
138 // GetLayeredWindowAttributes fails if the window was setup with
139 // UpdateLayeredWindow. We have no way to know the opacity of the window in
140 // that case. This happens for Stiky Note (crbug/412726).
141 if (!GetLayeredWindowAttributes(selected, &color_ref_key, &alpha, &flags))
142 return false;
143
144 // UpdateLayeredWindow is the only way to set per-pixel alpha and will cause
145 // the previous GetLayeredWindowAttributes to fail. So we only need to check
146 // the window wide color key or alpha.
147 if ((flags & LWA_COLORKEY) || ((flags & LWA_ALPHA) && (alpha < 255)))
148 return false;
149 }
150
151 TopWindowVerifierContext context(
152 selected, reinterpret_cast<HWND>(excluded_window()));
153
154 RECT selected_window_rect;
155 if (!GetWindowRect(selected, &selected_window_rect)) {
156 return false;
157 }
158 context.selected_window_rect = DesktopRect::MakeLTRB(
159 selected_window_rect.left,
160 selected_window_rect.top,
161 selected_window_rect.right,
162 selected_window_rect.bottom);
163
164 // Get the window region and check if it is rectangular.
165 win::ScopedGDIObject<HRGN, win::DeleteObjectTraits<HRGN> >
166 scoped_hrgn(CreateRectRgn(0, 0, 0, 0));
167 int region_type = GetWindowRgn(selected, scoped_hrgn.Get());
168
169 // Do not use the screen capturer if the region is empty or not rectangular.
170 if (region_type == COMPLEXREGION || region_type == NULLREGION)
171 return false;
172
173 if (region_type == SIMPLEREGION) {
174 RECT region_rect;
175 GetRgnBox(scoped_hrgn.Get(), ®ion_rect);
176 DesktopRect rgn_rect =
177 DesktopRect::MakeLTRB(region_rect.left,
178 region_rect.top,
179 region_rect.right,
180 region_rect.bottom);
181 rgn_rect.Translate(context.selected_window_rect.left(),
182 context.selected_window_rect.top());
183 context.selected_window_rect.IntersectWith(rgn_rect);
184 }
185 window_region_rect_ = context.selected_window_rect;
186
187 // Check if the window is occluded by any other window, excluding the child
188 // windows, context menus, and |excluded_window_|.
189 EnumWindows(&TopWindowVerifier, reinterpret_cast<LPARAM>(&context));
190 return context.is_top_window;
191 }
192
GetWindowRectInVirtualScreen()193 DesktopRect CroppingWindowCapturerWin::GetWindowRectInVirtualScreen() {
194 DesktopRect original_rect;
195 DesktopRect window_rect;
196 HWND hwnd = reinterpret_cast<HWND>(selected_window());
197 if (!GetCroppedWindowRect(hwnd, &window_rect, &original_rect)) {
198 LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
199 return window_rect;
200 }
201 window_rect.IntersectWith(window_region_rect_);
202
203 // Convert |window_rect| to be relative to the top-left of the virtual screen.
204 DesktopRect screen_rect(GetScreenRect(kFullDesktopScreenId, L""));
205 window_rect.IntersectWith(screen_rect);
206 window_rect.Translate(-screen_rect.left(), -screen_rect.top());
207 return window_rect;
208 }
209
210 } // namespace
211
212 // static
213 WindowCapturer*
Create(const DesktopCaptureOptions & options)214 CroppingWindowCapturer::Create(const DesktopCaptureOptions& options) {
215 return new CroppingWindowCapturerWin(options);
216 }
217
218 } // namespace webrtc
219