Home | History | Annotate | Download | only in desktop_capture
      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 
     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 
    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 
    116 WindowCapturerWin::~WindowCapturerWin() {
    117   if (dwmapi_library_)
    118     FreeLibrary(dwmapi_library_);
    119 }
    120 
    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 
    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 
    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 
    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 
    156 void WindowCapturerWin::Start(Callback* callback) {
    157   assert(!callback_);
    158   assert(callback);
    159 
    160   callback_ = callback;
    161 }
    162 
    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
    264 WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
    265   return new WindowCapturerWin();
    266 }
    267 
    268 }  // namespace webrtc
    269