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_magnifier.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 // kMagnifierWindowClass has to be "Magnifier" according to the Magnification
     30 // API. The other strings can be anything.
     31 static LPCTSTR kMagnifierHostClass = L"ScreenCapturerWinMagnifierHost";
     32 static LPCTSTR kHostWindowName = L"MagnifierHost";
     33 static LPCTSTR kMagnifierWindowClass = L"Magnifier";
     34 static LPCTSTR kMagnifierWindowName = L"MagnifierWindow";
     35 
     36 Atomic32 ScreenCapturerWinMagnifier::tls_index_(TLS_OUT_OF_INDEXES);
     37 
     38 ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier(
     39     scoped_ptr<ScreenCapturer> fallback_capturer)
     40     : fallback_capturer_(fallback_capturer.Pass()),
     41       fallback_capturer_started_(false),
     42       callback_(NULL),
     43       current_screen_id_(kFullDesktopScreenId),
     44       excluded_window_(NULL),
     45       set_thread_execution_state_failed_(false),
     46       desktop_dc_(NULL),
     47       mag_lib_handle_(NULL),
     48       mag_initialize_func_(NULL),
     49       mag_uninitialize_func_(NULL),
     50       set_window_source_func_(NULL),
     51       set_window_filter_list_func_(NULL),
     52       set_image_scaling_callback_func_(NULL),
     53       host_window_(NULL),
     54       magnifier_window_(NULL),
     55       magnifier_initialized_(false),
     56       magnifier_capture_succeeded_(true) {
     57 }
     58 
     59 ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() {
     60   // DestroyWindow must be called before MagUninitialize. magnifier_window_ is
     61   // destroyed automatically when host_window_ is destroyed.
     62   if (host_window_)
     63     DestroyWindow(host_window_);
     64 
     65   if (magnifier_initialized_)
     66     mag_uninitialize_func_();
     67 
     68   if (mag_lib_handle_)
     69     FreeLibrary(mag_lib_handle_);
     70 
     71   if (desktop_dc_)
     72     ReleaseDC(NULL, desktop_dc_);
     73 }
     74 
     75 void ScreenCapturerWinMagnifier::Start(Callback* callback) {
     76   assert(!callback_);
     77   assert(callback);
     78   callback_ = callback;
     79 
     80   InitializeMagnifier();
     81 }
     82 
     83 void ScreenCapturerWinMagnifier::Capture(const DesktopRegion& region) {
     84   TickTime capture_start_time = TickTime::Now();
     85 
     86   queue_.MoveToNextFrame();
     87 
     88   // Request that the system not power-down the system, or the display hardware.
     89   if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
     90     if (!set_thread_execution_state_failed_) {
     91       set_thread_execution_state_failed_ = true;
     92       LOG_F(LS_WARNING) << "Failed to make system & display power assertion: "
     93                         << GetLastError();
     94     }
     95   }
     96   // Switch to the desktop receiving user input if different from the current
     97   // one.
     98   scoped_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
     99   if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
    100     // Release GDI resources otherwise SetThreadDesktop will fail.
    101     if (desktop_dc_) {
    102       ReleaseDC(NULL, desktop_dc_);
    103       desktop_dc_ = NULL;
    104     }
    105     // If SetThreadDesktop() fails, the thread is still assigned a desktop.
    106     // So we can continue capture screen bits, just from the wrong desktop.
    107     desktop_.SetThreadDesktop(input_desktop.release());
    108   }
    109 
    110   bool succeeded = false;
    111 
    112   // Do not try to use the magnfiier if it's capturing non-primary screen, or it
    113   // failed before.
    114   if (magnifier_initialized_ && IsCapturingPrimaryScreenOnly() &&
    115       magnifier_capture_succeeded_) {
    116     DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_);
    117     CreateCurrentFrameIfNecessary(rect.size());
    118 
    119     // CaptureImage may fail in some situations, e.g. windows8 metro mode.
    120     succeeded = CaptureImage(rect);
    121   }
    122 
    123   // Defer to the fallback capturer if magnifier capturer did not work.
    124   if (!succeeded) {
    125     LOG_F(LS_WARNING) << "Switching to the fallback screen capturer.";
    126     StartFallbackCapturer();
    127     fallback_capturer_->Capture(region);
    128     return;
    129   }
    130 
    131   const DesktopFrame* current_frame = queue_.current_frame();
    132   const DesktopFrame* last_frame = queue_.previous_frame();
    133   if (last_frame && last_frame->size().equals(current_frame->size())) {
    134     // Make sure the differencer is set up correctly for these previous and
    135     // current screens.
    136     if (!differ_.get() || (differ_->width() != current_frame->size().width()) ||
    137         (differ_->height() != current_frame->size().height()) ||
    138         (differ_->bytes_per_row() != current_frame->stride())) {
    139       differ_.reset(new Differ(current_frame->size().width(),
    140                                current_frame->size().height(),
    141                                DesktopFrame::kBytesPerPixel,
    142                                current_frame->stride()));
    143     }
    144 
    145     // Calculate difference between the two last captured frames.
    146     DesktopRegion region;
    147     differ_->CalcDirtyRegion(
    148         last_frame->data(), current_frame->data(), &region);
    149     helper_.InvalidateRegion(region);
    150   } else {
    151     // No previous frame is available, or the screen is resized. Invalidate the
    152     // whole screen.
    153     helper_.InvalidateScreen(current_frame->size());
    154   }
    155 
    156   helper_.set_size_most_recent(current_frame->size());
    157 
    158   // Emit the current frame.
    159   DesktopFrame* frame = queue_.current_frame()->Share();
    160   frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX),
    161                                GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
    162   frame->mutable_updated_region()->Clear();
    163   helper_.TakeInvalidRegion(frame->mutable_updated_region());
    164   frame->set_capture_time_ms(
    165       (TickTime::Now() - capture_start_time).Milliseconds());
    166   callback_->OnCaptureCompleted(frame);
    167 }
    168 
    169 void ScreenCapturerWinMagnifier::SetMouseShapeObserver(
    170     MouseShapeObserver* mouse_shape_observer) {
    171   assert(false);  // NOTREACHED();
    172 }
    173 
    174 bool ScreenCapturerWinMagnifier::GetScreenList(ScreenList* screens) {
    175   return webrtc::GetScreenList(screens);
    176 }
    177 
    178 bool ScreenCapturerWinMagnifier::SelectScreen(ScreenId id) {
    179   bool valid = IsScreenValid(id, &current_device_key_);
    180 
    181   // Set current_screen_id_ even if the fallback capturer is being used, so we
    182   // can switch back to the magnifier when possible.
    183   if (valid)
    184     current_screen_id_ = id;
    185 
    186   if (fallback_capturer_started_)
    187     fallback_capturer_->SelectScreen(id);
    188 
    189   return valid;
    190 }
    191 
    192 void ScreenCapturerWinMagnifier::SetExcludedWindow(WindowId excluded_window) {
    193   excluded_window_ = (HWND)excluded_window;
    194   if (excluded_window_ && magnifier_initialized_) {
    195     set_window_filter_list_func_(
    196         magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_);
    197   }
    198 }
    199 
    200 bool ScreenCapturerWinMagnifier::CaptureImage(const DesktopRect& rect) {
    201   assert(magnifier_initialized_);
    202 
    203   // Set the magnifier control to cover the captured rect. The content of the
    204   // magnifier control will be the captured image.
    205   BOOL result = SetWindowPos(magnifier_window_,
    206                              NULL,
    207                              rect.left(), rect.top(),
    208                              rect.width(), rect.height(),
    209                              0);
    210   if (!result) {
    211     LOG_F(LS_WARNING) << "Failed to call SetWindowPos: " << GetLastError()
    212                       << ". Rect = {" << rect.left() << ", " << rect.top()
    213                       << ", " << rect.right() << ", " << rect.bottom() << "}";
    214     return false;
    215   }
    216 
    217   magnifier_capture_succeeded_ = false;
    218 
    219   RECT native_rect = {rect.left(), rect.top(), rect.right(), rect.bottom()};
    220 
    221   // OnCaptured will be called via OnMagImageScalingCallback and fill in the
    222   // frame before set_window_source_func_ returns.
    223   result = set_window_source_func_(magnifier_window_, native_rect);
    224 
    225   if (!result) {
    226     LOG_F(LS_WARNING) << "Failed to call MagSetWindowSource: " << GetLastError()
    227                       << ". Rect = {" << rect.left() << ", " << rect.top()
    228                       << ", " << rect.right() << ", " << rect.bottom() << "}";
    229     return false;
    230   }
    231 
    232   return magnifier_capture_succeeded_;
    233 }
    234 
    235 BOOL ScreenCapturerWinMagnifier::OnMagImageScalingCallback(
    236     HWND hwnd,
    237     void* srcdata,
    238     MAGIMAGEHEADER srcheader,
    239     void* destdata,
    240     MAGIMAGEHEADER destheader,
    241     RECT unclipped,
    242     RECT clipped,
    243     HRGN dirty) {
    244   assert(tls_index_.Value() != TLS_OUT_OF_INDEXES);
    245 
    246   ScreenCapturerWinMagnifier* owner =
    247       reinterpret_cast<ScreenCapturerWinMagnifier*>(
    248           TlsGetValue(tls_index_.Value()));
    249 
    250   owner->OnCaptured(srcdata, srcheader);
    251 
    252   return TRUE;
    253 }
    254 
    255 bool ScreenCapturerWinMagnifier::InitializeMagnifier() {
    256   assert(!magnifier_initialized_);
    257 
    258   desktop_dc_ = GetDC(NULL);
    259 
    260   mag_lib_handle_ = LoadLibrary(L"Magnification.dll");
    261   if (!mag_lib_handle_)
    262     return false;
    263 
    264   // Initialize Magnification API function pointers.
    265   mag_initialize_func_ = reinterpret_cast<MagInitializeFunc>(
    266       GetProcAddress(mag_lib_handle_, "MagInitialize"));
    267   mag_uninitialize_func_ = reinterpret_cast<MagUninitializeFunc>(
    268       GetProcAddress(mag_lib_handle_, "MagUninitialize"));
    269   set_window_source_func_ = reinterpret_cast<MagSetWindowSourceFunc>(
    270       GetProcAddress(mag_lib_handle_, "MagSetWindowSource"));
    271   set_window_filter_list_func_ = reinterpret_cast<MagSetWindowFilterListFunc>(
    272       GetProcAddress(mag_lib_handle_, "MagSetWindowFilterList"));
    273   set_image_scaling_callback_func_ =
    274       reinterpret_cast<MagSetImageScalingCallbackFunc>(
    275           GetProcAddress(mag_lib_handle_, "MagSetImageScalingCallback"));
    276 
    277   if (!mag_initialize_func_ || !mag_uninitialize_func_ ||
    278       !set_window_source_func_ || !set_window_filter_list_func_ ||
    279       !set_image_scaling_callback_func_) {
    280     LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    281                       << "library functions missing.";
    282     return false;
    283   }
    284 
    285   BOOL result = mag_initialize_func_();
    286   if (!result) {
    287     LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    288                       << "error from MagInitialize " << GetLastError();
    289     return false;
    290   }
    291 
    292   HMODULE hInstance = NULL;
    293   result = GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
    294                                   GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
    295                               reinterpret_cast<char*>(&DefWindowProc),
    296                               &hInstance);
    297   if (!result) {
    298     mag_uninitialize_func_();
    299     LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    300                       << "error from GetModulehandleExA " << GetLastError();
    301     return false;
    302   }
    303 
    304   // Register the host window class. See the MSDN documentation of the
    305   // Magnification API for more infomation.
    306   WNDCLASSEX wcex = {};
    307   wcex.cbSize = sizeof(WNDCLASSEX);
    308   wcex.lpfnWndProc = &DefWindowProc;
    309   wcex.hInstance = hInstance;
    310   wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    311   wcex.lpszClassName = kMagnifierHostClass;
    312 
    313   // Ignore the error which may happen when the class is already registered.
    314   RegisterClassEx(&wcex);
    315 
    316   // Create the host window.
    317   host_window_ = CreateWindowEx(WS_EX_LAYERED,
    318                                 kMagnifierHostClass,
    319                                 kHostWindowName,
    320                                 0,
    321                                 0, 0, 0, 0,
    322                                 NULL,
    323                                 NULL,
    324                                 hInstance,
    325                                 NULL);
    326   if (!host_window_) {
    327     mag_uninitialize_func_();
    328     LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    329                       << "error from creating host window " << GetLastError();
    330     return false;
    331   }
    332 
    333   // Create the magnifier control.
    334   magnifier_window_ = CreateWindow(kMagnifierWindowClass,
    335                                    kMagnifierWindowName,
    336                                    WS_CHILD | WS_VISIBLE,
    337                                    0, 0, 0, 0,
    338                                    host_window_,
    339                                    NULL,
    340                                    hInstance,
    341                                    NULL);
    342   if (!magnifier_window_) {
    343     mag_uninitialize_func_();
    344     LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    345                       << "error from creating magnifier window "
    346                       << GetLastError();
    347     return false;
    348   }
    349 
    350   // Hide the host window.
    351   ShowWindow(host_window_, SW_HIDE);
    352 
    353   // Set the scaling callback to receive captured image.
    354   result = set_image_scaling_callback_func_(
    355       magnifier_window_,
    356       &ScreenCapturerWinMagnifier::OnMagImageScalingCallback);
    357   if (!result) {
    358     mag_uninitialize_func_();
    359     LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    360                       << "error from MagSetImageScalingCallback "
    361                       << GetLastError();
    362     return false;
    363   }
    364 
    365   if (excluded_window_) {
    366     result = set_window_filter_list_func_(
    367         magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_);
    368     if (!result) {
    369       mag_uninitialize_func_();
    370       LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    371                         << "error from MagSetWindowFilterList "
    372                         << GetLastError();
    373       return false;
    374     }
    375   }
    376 
    377   if (tls_index_.Value() == TLS_OUT_OF_INDEXES) {
    378     // More than one threads may get here at the same time, but only one will
    379     // write to tls_index_ using CompareExchange.
    380     DWORD new_tls_index = TlsAlloc();
    381     if (!tls_index_.CompareExchange(new_tls_index, TLS_OUT_OF_INDEXES))
    382       TlsFree(new_tls_index);
    383   }
    384 
    385   assert(tls_index_.Value() != TLS_OUT_OF_INDEXES);
    386   TlsSetValue(tls_index_.Value(), this);
    387 
    388   magnifier_initialized_ = true;
    389   return true;
    390 }
    391 
    392 void ScreenCapturerWinMagnifier::OnCaptured(void* data,
    393                                             const MAGIMAGEHEADER& header) {
    394   DesktopFrame* current_frame = queue_.current_frame();
    395 
    396   // Verify the format.
    397   // TODO(jiayl): support capturing sources with pixel formats other than RGBA.
    398   int captured_bytes_per_pixel = header.cbSize / header.width / header.height;
    399   if (header.format != GUID_WICPixelFormat32bppRGBA ||
    400       header.width != static_cast<UINT>(current_frame->size().width()) ||
    401       header.height != static_cast<UINT>(current_frame->size().height()) ||
    402       header.stride != static_cast<UINT>(current_frame->stride()) ||
    403       captured_bytes_per_pixel != DesktopFrame::kBytesPerPixel) {
    404     LOG_F(LS_WARNING) << "Output format does not match the captured format: "
    405                       << "width = " << header.width << ", "
    406                       << "height = " << header.height << ", "
    407                       << "stride = " << header.stride << ", "
    408                       << "bpp = " << captured_bytes_per_pixel << ", "
    409                       << "pixel format RGBA ? "
    410                       << (header.format == GUID_WICPixelFormat32bppRGBA) << ".";
    411     return;
    412   }
    413 
    414   // Copy the data into the frame.
    415   current_frame->CopyPixelsFrom(
    416       reinterpret_cast<uint8_t*>(data),
    417       header.stride,
    418       DesktopRect::MakeXYWH(0, 0, header.width, header.height));
    419 
    420   magnifier_capture_succeeded_ = true;
    421 }
    422 
    423 void ScreenCapturerWinMagnifier::CreateCurrentFrameIfNecessary(
    424     const DesktopSize& size) {
    425   // If the current buffer is from an older generation then allocate a new one.
    426   // Note that we can't reallocate other buffers at this point, since the caller
    427   // may still be reading from them.
    428   if (!queue_.current_frame() || !queue_.current_frame()->size().equals(size)) {
    429     size_t buffer_size =
    430         size.width() * size.height() * DesktopFrame::kBytesPerPixel;
    431     SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size);
    432 
    433     scoped_ptr<DesktopFrame> buffer;
    434     if (shared_memory) {
    435       buffer.reset(new SharedMemoryDesktopFrame(
    436           size, size.width() * DesktopFrame::kBytesPerPixel, shared_memory));
    437     } else {
    438       buffer.reset(new BasicDesktopFrame(size));
    439     }
    440     queue_.ReplaceCurrentFrame(buffer.release());
    441   }
    442 }
    443 
    444 bool ScreenCapturerWinMagnifier::IsCapturingPrimaryScreenOnly() const {
    445   if (current_screen_id_ != kFullDesktopScreenId)
    446     return current_screen_id_ == 0;  // the primary screen is always '0'.
    447 
    448   return GetSystemMetrics(SM_CMONITORS) == 1;
    449 }
    450 
    451 void ScreenCapturerWinMagnifier::StartFallbackCapturer() {
    452   assert(fallback_capturer_);
    453   if (!fallback_capturer_started_) {
    454     fallback_capturer_started_ = true;
    455 
    456     fallback_capturer_->Start(callback_);
    457     fallback_capturer_->SelectScreen(current_screen_id_);
    458   }
    459 }
    460 
    461 }  // namespace webrtc
    462