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 21namespace webrtc { 22 23namespace { 24 25typedef HRESULT (WINAPI *DwmIsCompositionEnabledFunc)(BOOL* enabled); 26 27BOOL 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 69class 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 101WindowCapturerWin::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 116WindowCapturerWin::~WindowCapturerWin() { 117 if (dwmapi_library_) 118 FreeLibrary(dwmapi_library_); 119} 120 121bool WindowCapturerWin::IsAeroEnabled() { 122 BOOL result = FALSE; 123 if (is_composition_enabled_func_) 124 is_composition_enabled_func_(&result); 125 return result != FALSE; 126} 127 128bool 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 137bool 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 146bool 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 156void WindowCapturerWin::Start(Callback* callback) { 157 assert(!callback_); 158 assert(callback); 159 160 callback_ = callback; 161} 162 163void 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 264WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) { 265 return new WindowCapturerWin(); 266} 267 268} // namespace webrtc 269