1 /*
2  *  Copyright (c) 2013 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/window_capturer.h"
12 
13 #include <assert.h>
14 
15 #include "webrtc/base/scoped_ptr.h"
16 #include "webrtc/base/checks.h"
17 #include "webrtc/base/win32.h"
18 #include "webrtc/modules/desktop_capture/desktop_frame_win.h"
19 #include "webrtc/modules/desktop_capture/win/window_capture_utils.h"
20 #include "webrtc/system_wrappers/include/logging.h"
21 
22 namespace webrtc {
23 
24 namespace {
25 
WindowsEnumerationHandler(HWND hwnd,LPARAM param)26 BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) {
27   WindowCapturer::WindowList* list =
28       reinterpret_cast<WindowCapturer::WindowList*>(param);
29 
30   // Skip windows that are invisible, minimized, have no title, or are owned,
31   // unless they have the app window style set.
32   int len = GetWindowTextLength(hwnd);
33   HWND owner = GetWindow(hwnd, GW_OWNER);
34   LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
35   if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) ||
36       (owner && !(exstyle & WS_EX_APPWINDOW))) {
37     return TRUE;
38   }
39 
40   // Skip the Program Manager window and the Start button.
41   const size_t kClassLength = 256;
42   WCHAR class_name[kClassLength];
43   const int class_name_length = GetClassName(hwnd, class_name, kClassLength);
44   RTC_DCHECK(class_name_length)
45       << "Error retrieving the application's class name";
46 
47   // Skip Program Manager window and the Start button. This is the same logic
48   // that's used in Win32WindowPicker in libjingle. Consider filtering other
49   // windows as well (e.g. toolbars).
50   if (wcscmp(class_name, L"Progman") == 0 || wcscmp(class_name, L"Button") == 0)
51     return TRUE;
52 
53   // Windows 8 introduced a "Modern App" identified by their class name being
54   // either ApplicationFrameWindow or windows.UI.Core.coreWindow. The
55   // associated windows cannot be captured, so we skip them.
56   // http://crbug.com/526883.
57   if (rtc::IsWindows8OrLater() &&
58       (wcscmp(class_name, L"ApplicationFrameWindow") == 0 ||
59        wcscmp(class_name, L"Windows.UI.Core.CoreWindow") == 0)) {
60     return TRUE;
61   }
62 
63   WindowCapturer::Window window;
64   window.id = reinterpret_cast<WindowCapturer::WindowId>(hwnd);
65 
66   const size_t kTitleLength = 500;
67   WCHAR window_title[kTitleLength];
68   // Truncate the title if it's longer than kTitleLength.
69   GetWindowText(hwnd, window_title, kTitleLength);
70   window.title = rtc::ToUtf8(window_title);
71 
72   // Skip windows when we failed to convert the title or it is empty.
73   if (window.title.empty())
74     return TRUE;
75 
76   list->push_back(window);
77 
78   return TRUE;
79 }
80 
81 class WindowCapturerWin : public WindowCapturer {
82  public:
83   WindowCapturerWin();
84   virtual ~WindowCapturerWin();
85 
86   // WindowCapturer interface.
87   bool GetWindowList(WindowList* windows) override;
88   bool SelectWindow(WindowId id) override;
89   bool BringSelectedWindowToFront() override;
90 
91   // DesktopCapturer interface.
92   void Start(Callback* callback) override;
93   void Capture(const DesktopRegion& region) override;
94 
95  private:
96   Callback* callback_;
97 
98   // HWND and HDC for the currently selected window or NULL if window is not
99   // selected.
100   HWND window_;
101 
102   DesktopSize previous_size_;
103 
104   AeroChecker aero_checker_;
105 
106   RTC_DISALLOW_COPY_AND_ASSIGN(WindowCapturerWin);
107 };
108 
WindowCapturerWin()109 WindowCapturerWin::WindowCapturerWin()
110     : callback_(NULL),
111       window_(NULL) {
112 }
113 
~WindowCapturerWin()114 WindowCapturerWin::~WindowCapturerWin() {
115 }
116 
GetWindowList(WindowList * windows)117 bool WindowCapturerWin::GetWindowList(WindowList* windows) {
118   WindowList result;
119   LPARAM param = reinterpret_cast<LPARAM>(&result);
120   if (!EnumWindows(&WindowsEnumerationHandler, param))
121     return false;
122   windows->swap(result);
123   return true;
124 }
125 
SelectWindow(WindowId id)126 bool WindowCapturerWin::SelectWindow(WindowId id) {
127   HWND window = reinterpret_cast<HWND>(id);
128   if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window))
129     return false;
130   window_ = window;
131   previous_size_.set(0, 0);
132   return true;
133 }
134 
BringSelectedWindowToFront()135 bool WindowCapturerWin::BringSelectedWindowToFront() {
136   if (!window_)
137     return false;
138 
139   if (!IsWindow(window_) || !IsWindowVisible(window_) || IsIconic(window_))
140     return false;
141 
142   return SetForegroundWindow(window_) != 0;
143 }
144 
Start(Callback * callback)145 void WindowCapturerWin::Start(Callback* callback) {
146   assert(!callback_);
147   assert(callback);
148 
149   callback_ = callback;
150 }
151 
Capture(const DesktopRegion & region)152 void WindowCapturerWin::Capture(const DesktopRegion& region) {
153   if (!window_) {
154     LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError();
155     callback_->OnCaptureCompleted(NULL);
156     return;
157   }
158 
159   // Stop capturing if the window has been closed.
160   if (!IsWindow(window_)) {
161     callback_->OnCaptureCompleted(NULL);
162     return;
163   }
164 
165   // Return a 1x1 black frame if the window is minimized or invisible, to match
166   // behavior on mace. Window can be temporarily invisible during the
167   // transition of full screen mode on/off.
168   if (IsIconic(window_) || !IsWindowVisible(window_)) {
169     BasicDesktopFrame* frame = new BasicDesktopFrame(DesktopSize(1, 1));
170     memset(frame->data(), 0, frame->stride() * frame->size().height());
171 
172     previous_size_ = frame->size();
173     callback_->OnCaptureCompleted(frame);
174     return;
175   }
176 
177   DesktopRect original_rect;
178   DesktopRect cropped_rect;
179   if (!GetCroppedWindowRect(window_, &cropped_rect, &original_rect)) {
180     LOG(LS_WARNING) << "Failed to get window info: " << GetLastError();
181     callback_->OnCaptureCompleted(NULL);
182     return;
183   }
184 
185   HDC window_dc = GetWindowDC(window_);
186   if (!window_dc) {
187     LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError();
188     callback_->OnCaptureCompleted(NULL);
189     return;
190   }
191 
192   rtc::scoped_ptr<DesktopFrameWin> frame(
193       DesktopFrameWin::Create(cropped_rect.size(), NULL, window_dc));
194   if (!frame.get()) {
195     ReleaseDC(window_, window_dc);
196     callback_->OnCaptureCompleted(NULL);
197     return;
198   }
199 
200   HDC mem_dc = CreateCompatibleDC(window_dc);
201   HGDIOBJ previous_object = SelectObject(mem_dc, frame->bitmap());
202   BOOL result = FALSE;
203 
204   // When desktop composition (Aero) is enabled each window is rendered to a
205   // private buffer allowing BitBlt() to get the window content even if the
206   // window is occluded. PrintWindow() is slower but lets rendering the window
207   // contents to an off-screen device context when Aero is not available.
208   // PrintWindow() is not supported by some applications.
209   //
210   // If Aero is enabled, we prefer BitBlt() because it's faster and avoids
211   // window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may
212   // render occluding windows on top of the desired window.
213   //
214   // When composition is enabled the DC returned by GetWindowDC() doesn't always
215   // have window frame rendered correctly. Windows renders it only once and then
216   // caches the result between captures. We hack it around by calling
217   // PrintWindow() whenever window size changes, including the first time of
218   // capturing - it somehow affects what we get from BitBlt() on the subsequent
219   // captures.
220 
221   if (!aero_checker_.IsAeroEnabled() || !previous_size_.equals(frame->size())) {
222     result = PrintWindow(window_, mem_dc, 0);
223   }
224 
225   // Aero is enabled or PrintWindow() failed, use BitBlt.
226   if (!result) {
227     result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(),
228                     window_dc,
229                     cropped_rect.left() - original_rect.left(),
230                     cropped_rect.top() - original_rect.top(),
231                     SRCCOPY);
232   }
233 
234   SelectObject(mem_dc, previous_object);
235   DeleteDC(mem_dc);
236   ReleaseDC(window_, window_dc);
237 
238   previous_size_ = frame->size();
239 
240   frame->mutable_updated_region()->SetRect(
241       DesktopRect::MakeSize(frame->size()));
242 
243   if (!result) {
244     LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed.";
245     frame.reset();
246   }
247 
248   callback_->OnCaptureCompleted(frame.release());
249 }
250 
251 }  // namespace
252 
253 // static
Create(const DesktopCaptureOptions & options)254 WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
255   return new WindowCapturerWin();
256 }
257 
258 }  // namespace webrtc
259