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(), &region_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