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