blob: 8fd380b3717f1479c25947653395e6edf8794cf0 [file] [log] [blame]
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +00001/*
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
pbos@webrtc.org12dc1a32013-08-05 16:22:53 +000013#include <assert.h>
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000014#include <windows.h>
15
16#include "webrtc/modules/desktop_capture/desktop_frame_win.h"
17#include "webrtc/system_wrappers/interface/logging.h"
18#include "webrtc/system_wrappers/interface/scoped_ptr.h"
19
20namespace webrtc {
21
22namespace {
23
24typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL* enabled);
25
26// Coverts a zero-terminated UTF-16 string to UTF-8. Returns an empty string if
27// error occurs.
28std::string Utf16ToUtf8(const WCHAR* str) {
29 int len_utf8 = WideCharToMultiByte(CP_UTF8, 0, str, -1,
30 NULL, 0, NULL, NULL);
31 if (len_utf8 <= 0)
32 return std::string();
33 std::string result(len_utf8, '\0');
34 int rv = WideCharToMultiByte(CP_UTF8, 0, str, -1,
35 &*(result.begin()), len_utf8, NULL, NULL);
36 if (rv != len_utf8)
37 assert(false);
38
39 return result;
40}
41
42BOOL CALLBACK WindowsEnumerationHandler(HWND hwnd, LPARAM param) {
43 WindowCapturer::WindowList* list =
44 reinterpret_cast<WindowCapturer::WindowList*>(param);
45
46 // Skip windows that are invisible, minimized, have no title, or are owned,
47 // unless they have the app window style set.
48 int len = GetWindowTextLength(hwnd);
49 HWND owner = GetWindow(hwnd, GW_OWNER);
50 LONG exstyle = GetWindowLong(hwnd, GWL_EXSTYLE);
51 if (len == 0 || IsIconic(hwnd) || !IsWindowVisible(hwnd) ||
52 (owner && !(exstyle & WS_EX_APPWINDOW))) {
53 return TRUE;
54 }
55
56 // Skip the Program Manager window and the Start button.
57 const size_t kClassLength = 256;
58 WCHAR class_name[kClassLength];
59 GetClassName(hwnd, class_name, kClassLength);
60 // Skip Program Manager window and the Start button. This is the same logic
61 // that's used in Win32WindowPicker in libjingle. Consider filtering other
62 // windows as well (e.g. toolbars).
63 if (wcscmp(class_name, L"Progman") == 0 || wcscmp(class_name, L"Button") == 0)
64 return TRUE;
65
66 WindowCapturer::Window window;
sergeyu@chromium.orga590b412013-06-17 20:02:21 +000067 window.id = reinterpret_cast<WindowCapturer::WindowId>(hwnd);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +000068
69 const size_t kTitleLength = 500;
70 WCHAR window_title[kTitleLength];
71 // Truncate the title if it's longer than kTitleLength.
72 GetWindowText(hwnd, window_title, kTitleLength);
73 window.title = Utf16ToUtf8(window_title);
74
75 // Skip windows when we failed to convert the title or it is empty.
76 if (window.title.empty())
77 return TRUE;
78
79 list->push_back(window);
80
81 return TRUE;
82}
83
84class WindowCapturerWin : public WindowCapturer {
85 public:
86 WindowCapturerWin();
87 virtual ~WindowCapturerWin();
88
89 // WindowCapturer interface.
90 virtual bool GetWindowList(WindowList* windows) OVERRIDE;
91 virtual bool SelectWindow(WindowId id) OVERRIDE;
92
93 // DesktopCapturer interface.
94 virtual void Start(Callback* callback) OVERRIDE;
95 virtual void Capture(const DesktopRegion& region) OVERRIDE;
96
97 private:
98 bool IsAeroEnabled();
99
100 Callback* callback_;
101
102 // HWND and HDC for the currently selected window or NULL if window is not
103 // selected.
104 HWND window_;
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000105
106 // dwmapi.dll is used to determine if desktop compositing is enabled.
107 HMODULE dwmapi_library_;
108 DwmIsCompositionEnabledFunc is_composition_enabled_func_;
109
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000110 DesktopSize previous_size_;
111
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000112 DISALLOW_COPY_AND_ASSIGN(WindowCapturerWin);
113};
114
115WindowCapturerWin::WindowCapturerWin()
116 : callback_(NULL),
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000117 window_(NULL) {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000118 // Try to load dwmapi.dll dynamically since it is not available on XP.
119 dwmapi_library_ = LoadLibrary(L"dwmapi.dll");
120 if (dwmapi_library_) {
121 is_composition_enabled_func_ =
122 reinterpret_cast<DwmIsCompositionEnabledFunc>(
123 GetProcAddress(dwmapi_library_, "DwmIsCompositionEnabled"));
124 assert(is_composition_enabled_func_);
125 } else {
126 is_composition_enabled_func_ = NULL;
127 }
128}
129
130WindowCapturerWin::~WindowCapturerWin() {
131 if (dwmapi_library_)
132 FreeLibrary(dwmapi_library_);
133}
134
135bool WindowCapturerWin::IsAeroEnabled() {
136 BOOL result = FALSE;
137 if (is_composition_enabled_func_)
138 is_composition_enabled_func_(&result);
139 return result != FALSE;
140}
141
142bool WindowCapturerWin::GetWindowList(WindowList* windows) {
143 WindowList result;
144 LPARAM param = reinterpret_cast<LPARAM>(&result);
145 if (!EnumWindows(&WindowsEnumerationHandler, param))
146 return false;
147 windows->swap(result);
148 return true;
149}
150
151bool WindowCapturerWin::SelectWindow(WindowId id) {
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000152 HWND window = reinterpret_cast<HWND>(id);
153 if (!IsWindow(window) || !IsWindowVisible(window) || IsIconic(window))
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000154 return false;
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000155 window_ = window;
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000156 previous_size_.set(0, 0);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000157 return true;
158}
159
160void WindowCapturerWin::Start(Callback* callback) {
161 assert(!callback_);
162 assert(callback);
163
164 callback_ = callback;
165}
166
167void WindowCapturerWin::Capture(const DesktopRegion& region) {
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000168 if (!window_) {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000169 LOG(LS_ERROR) << "Window hasn't been selected: " << GetLastError();
170 callback_->OnCaptureCompleted(NULL);
171 return;
172 }
173
sergeyu@chromium.org958cdf62013-09-24 22:10:13 +0000174 // Stop capturing if the window has been minimized or hidden.
175 if (IsIconic(window_) || !IsWindowVisible(window_)) {
176 callback_->OnCaptureCompleted(NULL);
177 return;
178 }
179
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000180 RECT rect;
181 if (!GetWindowRect(window_, &rect)) {
182 LOG(LS_WARNING) << "Failed to get window size: " << GetLastError();
183 callback_->OnCaptureCompleted(NULL);
184 return;
185 }
186
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000187 HDC window_dc = GetWindowDC(window_);
188 if (!window_dc) {
189 LOG(LS_WARNING) << "Failed to get window DC: " << GetLastError();
sergeyu@chromium.org6a5cc9d2013-09-12 19:17:26 +0000190 callback_->OnCaptureCompleted(NULL);
191 return;
192 }
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000193
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000194 scoped_ptr<DesktopFrameWin> frame(DesktopFrameWin::Create(
195 DesktopSize(rect.right - rect.left, rect.bottom - rect.top),
196 NULL, window_dc));
197 if (!frame.get()) {
198 ReleaseDC(window_, window_dc);
199 callback_->OnCaptureCompleted(NULL);
200 return;
201 }
202
203 HDC mem_dc = CreateCompatibleDC(window_dc);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000204 SelectObject(mem_dc, frame->bitmap());
205 BOOL result = FALSE;
206
207 // When desktop composition (Aero) is enabled each window is rendered to a
208 // private buffer allowing BitBlt() to get the window content even if the
209 // window is occluded. PrintWindow() is slower but lets rendering the window
210 // contents to an off-screen device context when Aero is not available.
211 // PrintWindow() is not supported by some applications.
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000212 //
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000213 // If Aero is enabled, we prefer BitBlt() because it's faster and avoids
214 // window flickering. Otherwise, we prefer PrintWindow() because BitBlt() may
215 // render occluding windows on top of the desired window.
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000216 //
217 // When composition is enabled the DC returned by GetWindowDC() doesn't always
218 // have window frame rendered correctly. Windows renders it only once and then
219 // caches the result between captures. We hack it around by calling
220 // PrintWindow() whenever window size changes - it somehow affects what we
221 // get from BitBlt() on the subsequent captures.
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000222
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000223 if (!IsAeroEnabled() ||
224 (!previous_size_.is_empty() && !previous_size_.equals(frame->size()))) {
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000225 result = PrintWindow(window_, mem_dc, 0);
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000226 }
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000227
228 // Aero is enabled or PrintWindow() failed, use BitBlt.
229 if (!result) {
230 result = BitBlt(mem_dc, 0, 0, frame->size().width(), frame->size().height(),
sergeyu@chromium.org8d757ac2013-09-24 23:13:51 +0000231 window_dc, 0, 0, SRCCOPY);
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000232 }
233
sergeyu@chromium.orgbecbefa2013-09-25 22:56:59 +0000234 SelectObject(mem_dc, NULL);
235 DeleteDC(mem_dc);
236 ReleaseDC(window_, window_dc);
237
238 previous_size_ = frame->size();
239
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000240 if (!result) {
241 LOG(LS_ERROR) << "Both PrintWindow() and BitBlt() failed.";
242 frame.reset();
243 }
244
sergeyu@chromium.orgb10ccbe2013-05-19 07:02:48 +0000245 callback_->OnCaptureCompleted(frame.release());
246}
247
248} // namespace
249
250// static
251WindowCapturer* WindowCapturer::Create() {
252 return new WindowCapturerWin();
253}
254
255} // namespace webrtc