Home | History | Annotate | Download | only in win
      1 /*
      2  *  Copyright (c) 2014 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/win/screen_capturer_win_gdi.h"
     12 
     13 #include <assert.h>
     14 
     15 #include "webrtc/modules/desktop_capture/desktop_capture_options.h"
     16 #include "webrtc/modules/desktop_capture/desktop_frame.h"
     17 #include "webrtc/modules/desktop_capture/desktop_frame_win.h"
     18 #include "webrtc/modules/desktop_capture/desktop_region.h"
     19 #include "webrtc/modules/desktop_capture/differ.h"
     20 #include "webrtc/modules/desktop_capture/mouse_cursor.h"
     21 #include "webrtc/modules/desktop_capture/win/cursor.h"
     22 #include "webrtc/modules/desktop_capture/win/desktop.h"
     23 #include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
     24 #include "webrtc/system_wrappers/interface/logging.h"
     25 #include "webrtc/system_wrappers/interface/tick_util.h"
     26 
     27 namespace webrtc {
     28 
     29 namespace {
     30 
     31 // Constants from dwmapi.h.
     32 const UINT DWM_EC_DISABLECOMPOSITION = 0;
     33 const UINT DWM_EC_ENABLECOMPOSITION = 1;
     34 
     35 const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
     36 
     37 }  // namespace
     38 
     39 ScreenCapturerWinGdi::ScreenCapturerWinGdi(const DesktopCaptureOptions& options)
     40     : callback_(NULL),
     41       mouse_shape_observer_(NULL),
     42       current_screen_id_(kFullDesktopScreenId),
     43       desktop_dc_(NULL),
     44       memory_dc_(NULL),
     45       dwmapi_library_(NULL),
     46       composition_func_(NULL),
     47       set_thread_execution_state_failed_(false) {
     48   if (options.disable_effects()) {
     49     // Load dwmapi.dll dynamically since it is not available on XP.
     50     if (!dwmapi_library_)
     51       dwmapi_library_ = LoadLibrary(kDwmapiLibraryName);
     52 
     53     if (dwmapi_library_) {
     54       composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
     55           GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
     56     }
     57   }
     58 }
     59 
     60 ScreenCapturerWinGdi::~ScreenCapturerWinGdi() {
     61   if (desktop_dc_)
     62     ReleaseDC(NULL, desktop_dc_);
     63   if (memory_dc_)
     64     DeleteDC(memory_dc_);
     65 
     66   // Restore Aero.
     67   if (composition_func_)
     68     (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
     69 
     70   if (dwmapi_library_)
     71     FreeLibrary(dwmapi_library_);
     72 }
     73 
     74 void ScreenCapturerWinGdi::Capture(const DesktopRegion& region) {
     75   TickTime capture_start_time = TickTime::Now();
     76 
     77   queue_.MoveToNextFrame();
     78 
     79   // Request that the system not power-down the system, or the display hardware.
     80   if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
     81     if (!set_thread_execution_state_failed_) {
     82       set_thread_execution_state_failed_ = true;
     83       LOG_F(LS_WARNING) << "Failed to make system & display power assertion: "
     84                         << GetLastError();
     85     }
     86   }
     87 
     88   // Make sure the GDI capture resources are up-to-date.
     89   PrepareCaptureResources();
     90 
     91   if (!CaptureImage()) {
     92     callback_->OnCaptureCompleted(NULL);
     93     return;
     94   }
     95 
     96   const DesktopFrame* current_frame = queue_.current_frame();
     97   const DesktopFrame* last_frame = queue_.previous_frame();
     98   if (last_frame && last_frame->size().equals(current_frame->size())) {
     99     // Make sure the differencer is set up correctly for these previous and
    100     // current screens.
    101     if (!differ_.get() ||
    102         (differ_->width() != current_frame->size().width()) ||
    103         (differ_->height() != current_frame->size().height()) ||
    104         (differ_->bytes_per_row() != current_frame->stride())) {
    105       differ_.reset(new Differ(current_frame->size().width(),
    106                                current_frame->size().height(),
    107                                DesktopFrame::kBytesPerPixel,
    108                                current_frame->stride()));
    109     }
    110 
    111     // Calculate difference between the two last captured frames.
    112     DesktopRegion region;
    113     differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(),
    114                              &region);
    115     helper_.InvalidateRegion(region);
    116   } else {
    117     // No previous frame is available, or the screen is resized. Invalidate the
    118     // whole screen.
    119     helper_.InvalidateScreen(current_frame->size());
    120   }
    121 
    122   helper_.set_size_most_recent(current_frame->size());
    123 
    124   // Emit the current frame.
    125   DesktopFrame* frame = queue_.current_frame()->Share();
    126   frame->set_dpi(DesktopVector(
    127       GetDeviceCaps(desktop_dc_, LOGPIXELSX),
    128       GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
    129   frame->mutable_updated_region()->Clear();
    130   helper_.TakeInvalidRegion(frame->mutable_updated_region());
    131   frame->set_capture_time_ms(
    132       (TickTime::Now() - capture_start_time).Milliseconds());
    133   callback_->OnCaptureCompleted(frame);
    134 
    135   // Check for cursor shape update.
    136   CaptureCursor();
    137 }
    138 
    139 void ScreenCapturerWinGdi::SetMouseShapeObserver(
    140     MouseShapeObserver* mouse_shape_observer) {
    141   assert(!mouse_shape_observer_);
    142   assert(mouse_shape_observer);
    143 
    144   mouse_shape_observer_ = mouse_shape_observer;
    145 }
    146 
    147 bool ScreenCapturerWinGdi::GetScreenList(ScreenList* screens) {
    148   return webrtc::GetScreenList(screens);
    149 }
    150 
    151 bool ScreenCapturerWinGdi::SelectScreen(ScreenId id) {
    152   bool valid = IsScreenValid(id, &current_device_key_);
    153   if (valid)
    154     current_screen_id_ = id;
    155   return valid;
    156 }
    157 
    158 void ScreenCapturerWinGdi::Start(Callback* callback) {
    159   assert(!callback_);
    160   assert(callback);
    161 
    162   callback_ = callback;
    163 
    164   // Vote to disable Aero composited desktop effects while capturing. Windows
    165   // will restore Aero automatically if the process exits. This has no effect
    166   // under Windows 8 or higher.  See crbug.com/124018.
    167   if (composition_func_)
    168     (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
    169 }
    170 
    171 void ScreenCapturerWinGdi::PrepareCaptureResources() {
    172   // Switch to the desktop receiving user input if different from the current
    173   // one.
    174   scoped_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
    175   if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
    176     // Release GDI resources otherwise SetThreadDesktop will fail.
    177     if (desktop_dc_) {
    178       ReleaseDC(NULL, desktop_dc_);
    179       desktop_dc_ = NULL;
    180     }
    181 
    182     if (memory_dc_) {
    183       DeleteDC(memory_dc_);
    184       memory_dc_ = NULL;
    185     }
    186 
    187     // If SetThreadDesktop() fails, the thread is still assigned a desktop.
    188     // So we can continue capture screen bits, just from the wrong desktop.
    189     desktop_.SetThreadDesktop(input_desktop.release());
    190 
    191     // Re-assert our vote to disable Aero.
    192     // See crbug.com/124018 and crbug.com/129906.
    193     if (composition_func_ != NULL) {
    194       (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
    195     }
    196   }
    197 
    198   // If the display bounds have changed then recreate GDI resources.
    199   // TODO(wez): Also check for pixel format changes.
    200   DesktopRect screen_rect(DesktopRect::MakeXYWH(
    201       GetSystemMetrics(SM_XVIRTUALSCREEN),
    202       GetSystemMetrics(SM_YVIRTUALSCREEN),
    203       GetSystemMetrics(SM_CXVIRTUALSCREEN),
    204       GetSystemMetrics(SM_CYVIRTUALSCREEN)));
    205   if (!screen_rect.equals(desktop_dc_rect_)) {
    206     if (desktop_dc_) {
    207       ReleaseDC(NULL, desktop_dc_);
    208       desktop_dc_ = NULL;
    209     }
    210     if (memory_dc_) {
    211       DeleteDC(memory_dc_);
    212       memory_dc_ = NULL;
    213     }
    214     desktop_dc_rect_ = DesktopRect();
    215   }
    216 
    217   if (desktop_dc_ == NULL) {
    218     assert(memory_dc_ == NULL);
    219 
    220     // Create GDI device contexts to capture from the desktop into memory.
    221     desktop_dc_ = GetDC(NULL);
    222     if (!desktop_dc_)
    223       abort();
    224     memory_dc_ = CreateCompatibleDC(desktop_dc_);
    225     if (!memory_dc_)
    226       abort();
    227 
    228     desktop_dc_rect_ = screen_rect;
    229 
    230     // Make sure the frame buffers will be reallocated.
    231     queue_.Reset();
    232 
    233     helper_.ClearInvalidRegion();
    234   }
    235 }
    236 
    237 bool ScreenCapturerWinGdi::CaptureImage() {
    238   DesktopRect screen_rect =
    239       GetScreenRect(current_screen_id_, current_device_key_);
    240   if (screen_rect.is_empty())
    241     return false;
    242 
    243   DesktopSize size = screen_rect.size();
    244   // If the current buffer is from an older generation then allocate a new one.
    245   // Note that we can't reallocate other buffers at this point, since the caller
    246   // may still be reading from them.
    247   if (!queue_.current_frame() ||
    248       !queue_.current_frame()->size().equals(screen_rect.size())) {
    249     assert(desktop_dc_ != NULL);
    250     assert(memory_dc_ != NULL);
    251 
    252     size_t buffer_size = size.width() * size.height() *
    253         DesktopFrame::kBytesPerPixel;
    254     SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size);
    255 
    256     scoped_ptr<DesktopFrame> buffer;
    257     buffer.reset(
    258         DesktopFrameWin::Create(size, shared_memory, desktop_dc_));
    259     queue_.ReplaceCurrentFrame(buffer.release());
    260   }
    261 
    262   // Select the target bitmap into the memory dc and copy the rect from desktop
    263   // to memory.
    264   DesktopFrameWin* current = static_cast<DesktopFrameWin*>(
    265       queue_.current_frame()->GetUnderlyingFrame());
    266   HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
    267   if (previous_object != NULL) {
    268     BitBlt(memory_dc_,
    269            0, 0, screen_rect.width(), screen_rect.height(),
    270            desktop_dc_,
    271            screen_rect.left(), screen_rect.top(),
    272            SRCCOPY | CAPTUREBLT);
    273 
    274     // Select back the previously selected object to that the device contect
    275     // could be destroyed independently of the bitmap if needed.
    276     SelectObject(memory_dc_, previous_object);
    277   }
    278   return true;
    279 }
    280 
    281 void ScreenCapturerWinGdi::CaptureCursor() {
    282   CURSORINFO cursor_info;
    283   cursor_info.cbSize = sizeof(CURSORINFO);
    284   if (!GetCursorInfo(&cursor_info)) {
    285     LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError();
    286     return;
    287   }
    288 
    289   // Note that |cursor_info.hCursor| does not need to be freed.
    290   scoped_ptr<MouseCursor> cursor_image(
    291       CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor));
    292   if (!cursor_image.get())
    293     return;
    294 
    295   scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape);
    296   cursor->hotspot = cursor_image->hotspot();
    297   cursor->size = cursor_image->image()->size();
    298   uint8_t* current_row = cursor_image->image()->data();
    299   for (int y = 0; y < cursor_image->image()->size().height(); ++y) {
    300     cursor->data.append(current_row,
    301                         current_row + cursor_image->image()->size().width() *
    302                                         DesktopFrame::kBytesPerPixel);
    303     current_row += cursor_image->image()->stride();
    304   }
    305 
    306   // Compare the current cursor with the last one we sent to the client. If
    307   // they're the same, then don't bother sending the cursor again.
    308   if (last_cursor_.size.equals(cursor->size) &&
    309       last_cursor_.hotspot.equals(cursor->hotspot) &&
    310       last_cursor_.data == cursor->data) {
    311     return;
    312   }
    313 
    314   LOG(LS_VERBOSE) << "Sending updated cursor: " << cursor->size.width() << "x"
    315                   << cursor->size.height();
    316 
    317   // Record the last cursor image that we sent to the client.
    318   last_cursor_ = *cursor;
    319 
    320   if (mouse_shape_observer_)
    321     mouse_shape_observer_->OnCursorShapeChanged(cursor.release());
    322 }
    323 
    324 }  // namespace webrtc
    325