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/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 
     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 
    109 WindowCapturerWin::WindowCapturerWin()
    110     : callback_(NULL),
    111       window_(NULL) {
    112 }
    113 
    114 WindowCapturerWin::~WindowCapturerWin() {
    115 }
    116 
    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 
    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 
    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 
    145 void WindowCapturerWin::Start(Callback* callback) {
    146   assert(!callback_);
    147   assert(callback);
    148 
    149   callback_ = callback;
    150 }
    151 
    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
    254 WindowCapturer* WindowCapturer::Create(const DesktopCaptureOptions& options) {
    255   return new WindowCapturerWin();
    256 }
    257 
    258 }  // namespace webrtc
    259