tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

screen_capturer_win_magnifier.cc (14774B)


      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 "modules/desktop_capture/win/screen_capturer_win_magnifier.h"
     12 
     13 #include <cstddef>
     14 #include <cstdint>
     15 #include <memory>
     16 #include <optional>
     17 #include <string>
     18 #include <utility>
     19 
     20 #include "modules/desktop_capture/desktop_capture_metrics_helper.h"
     21 #include "modules/desktop_capture/desktop_capture_types.h"
     22 #include "modules/desktop_capture/desktop_frame.h"
     23 #include "modules/desktop_capture/desktop_geometry.h"
     24 #include "modules/desktop_capture/desktop_region.h"
     25 #include "modules/desktop_capture/shared_desktop_frame.h"
     26 #include "modules/desktop_capture/shared_memory.h"
     27 #include "modules/desktop_capture/win/desktop.h"
     28 #include "modules/desktop_capture/win/screen_capture_utils.h"
     29 #include "rtc_base/checks.h"
     30 #include "rtc_base/logging.h"
     31 #include "rtc_base/time_utils.h"
     32 #include "system_wrappers/include/metrics.h"
     33 
     34 namespace webrtc {
     35 
     36 namespace {
     37 DWORD GetTlsIndex() {
     38  static const DWORD tls_index = TlsAlloc();
     39  RTC_DCHECK(tls_index != TLS_OUT_OF_INDEXES);
     40  return tls_index;
     41 }
     42 
     43 }  // namespace
     44 
     45 // kMagnifierWindowClass has to be "Magnifier" according to the Magnification
     46 // API. The other strings can be anything.
     47 static wchar_t kMagnifierHostClass[] = L"ScreenCapturerWinMagnifierHost";
     48 static wchar_t kHostWindowName[] = L"MagnifierHost";
     49 static wchar_t kMagnifierWindowClass[] = L"Magnifier";
     50 static wchar_t kMagnifierWindowName[] = L"MagnifierWindow";
     51 
     52 ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier() = default;
     53 ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() {
     54  // DestroyWindow must be called before MagUninitialize. magnifier_window_ is
     55  // destroyed automatically when host_window_ is destroyed.
     56  if (host_window_)
     57    DestroyWindow(host_window_);
     58 
     59  if (magnifier_initialized_)
     60    mag_uninitialize_func_();
     61 
     62  if (mag_lib_handle_)
     63    FreeLibrary(mag_lib_handle_);
     64 
     65  if (desktop_dc_)
     66    ReleaseDC(NULL, desktop_dc_);
     67 }
     68 
     69 void ScreenCapturerWinMagnifier::Start(Callback* callback) {
     70  RTC_DCHECK(!callback_);
     71  RTC_DCHECK(callback);
     72  RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinMagnifier);
     73 
     74  callback_ = callback;
     75 
     76  if (!InitializeMagnifier()) {
     77    RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed.";
     78  }
     79 }
     80 
     81 void ScreenCapturerWinMagnifier::SetSharedMemoryFactory(
     82    std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
     83  shared_memory_factory_ = std::move(shared_memory_factory);
     84 }
     85 
     86 void ScreenCapturerWinMagnifier::CaptureFrame() {
     87  RTC_DCHECK(callback_);
     88  if (!magnifier_initialized_) {
     89    RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed.";
     90    callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
     91    return;
     92  }
     93 
     94  int64_t capture_start_time_nanos = TimeNanos();
     95 
     96  // Switch to the desktop receiving user input if different from the current
     97  // one.
     98  std::unique_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  DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_);
    111  queue_.MoveToNextFrame();
    112  CreateCurrentFrameIfNecessary(rect.size());
    113  // CaptureImage may fail in some situations, e.g. windows8 metro mode. So
    114  // defer to the fallback capturer if magnifier capturer did not work.
    115  if (!CaptureImage(rect)) {
    116    RTC_LOG_F(LS_WARNING) << "Magnifier capturer failed to capture a frame.";
    117    callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
    118    return;
    119  }
    120 
    121  // Emit the current frame.
    122  std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share();
    123  frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX),
    124                               GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
    125  frame->mutable_updated_region()->SetRect(
    126      DesktopRect::MakeSize(frame->size()));
    127 
    128  int capture_time_ms =
    129      (TimeNanos() - capture_start_time_nanos) / kNumNanosecsPerMillisec;
    130  RTC_HISTOGRAM_COUNTS_1000(
    131      "WebRTC.DesktopCapture.Win.MagnifierCapturerFrameTime", capture_time_ms);
    132  frame->set_capture_time_ms(capture_time_ms);
    133  frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinMagnifier);
    134  callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
    135 }
    136 
    137 bool ScreenCapturerWinMagnifier::GetSourceList(SourceList* sources) {
    138  return GetScreenList(sources);
    139 }
    140 
    141 bool ScreenCapturerWinMagnifier::SelectSource(SourceId id) {
    142  std::wstring device_key;
    143  bool valid = IsScreenValid(id, &device_key);
    144  if (valid) {
    145    current_screen_id_ = id;
    146    current_device_key_ = device_key;
    147  } else {
    148    current_screen_id_ = kFullDesktopScreenId;
    149    current_device_key_ = std::nullopt;
    150  }
    151  return valid;
    152 }
    153 
    154 void ScreenCapturerWinMagnifier::SetExcludedWindow(WindowId excluded_window) {
    155  excluded_window_ = (HWND)excluded_window;
    156  if (excluded_window_ && magnifier_initialized_) {
    157    set_window_filter_list_func_(magnifier_window_, MW_FILTERMODE_EXCLUDE, 1,
    158                                 &excluded_window_);
    159  }
    160 }
    161 
    162 bool ScreenCapturerWinMagnifier::CaptureImage(const DesktopRect& rect) {
    163  RTC_DCHECK(magnifier_initialized_);
    164 
    165  // Set the magnifier control to cover the captured rect. The content of the
    166  // magnifier control will be the captured image.
    167  BOOL result = SetWindowPos(magnifier_window_, NULL, rect.left(), rect.top(),
    168                             rect.width(), rect.height(), 0);
    169  if (!result) {
    170    RTC_LOG_F(LS_WARNING) << "Failed to call SetWindowPos: " << GetLastError()
    171                          << ". Rect = {" << rect.left() << ", " << rect.top()
    172                          << ", " << rect.right() << ", " << rect.bottom()
    173                          << "}";
    174    return false;
    175  }
    176 
    177  magnifier_capture_succeeded_ = false;
    178 
    179  RECT native_rect = {rect.left(), rect.top(), rect.right(), rect.bottom()};
    180 
    181  TlsSetValue(GetTlsIndex(), this);
    182  // OnCaptured will be called via OnMagImageScalingCallback and fill in the
    183  // frame before set_window_source_func_ returns.
    184  result = set_window_source_func_(magnifier_window_, native_rect);
    185 
    186  if (!result) {
    187    RTC_LOG_F(LS_WARNING) << "Failed to call MagSetWindowSource: "
    188                          << GetLastError() << ". Rect = {" << rect.left()
    189                          << ", " << rect.top() << ", " << rect.right() << ", "
    190                          << rect.bottom() << "}";
    191    return false;
    192  }
    193 
    194  return magnifier_capture_succeeded_;
    195 }
    196 
    197 BOOL ScreenCapturerWinMagnifier::OnMagImageScalingCallback(
    198    HWND hwnd,
    199    void* srcdata,
    200    MAGIMAGEHEADER srcheader,
    201    void* destdata,
    202    MAGIMAGEHEADER destheader,
    203    RECT unclipped,
    204    RECT clipped,
    205    HRGN dirty) {
    206  ScreenCapturerWinMagnifier* owner =
    207      reinterpret_cast<ScreenCapturerWinMagnifier*>(TlsGetValue(GetTlsIndex()));
    208  TlsSetValue(GetTlsIndex(), nullptr);
    209  owner->OnCaptured(srcdata, srcheader);
    210 
    211  return TRUE;
    212 }
    213 
    214 // TODO(zijiehe): These functions are available on Windows Vista or upper, so we
    215 // do not need to use LoadLibrary and GetProcAddress anymore. Use regular
    216 // include and function calls instead of a dynamical loaded library.
    217 bool ScreenCapturerWinMagnifier::InitializeMagnifier() {
    218  RTC_DCHECK(!magnifier_initialized_);
    219 
    220  if (GetSystemMetrics(SM_CMONITORS) != 1) {
    221    // Do not try to use the magnifier in multi-screen setup (where the API
    222    // crashes sometimes).
    223    RTC_LOG_F(LS_WARNING) << "Magnifier capturer cannot work on multi-screen "
    224                             "system.";
    225    return false;
    226  }
    227 
    228  desktop_dc_ = GetDC(nullptr);
    229 
    230  mag_lib_handle_ = LoadLibraryW(L"Magnification.dll");
    231  if (!mag_lib_handle_)
    232    return false;
    233 
    234  // Initialize Magnification API function pointers.
    235  mag_initialize_func_ = reinterpret_cast<MagInitializeFunc>(
    236      GetProcAddress(mag_lib_handle_, "MagInitialize"));
    237  mag_uninitialize_func_ = reinterpret_cast<MagUninitializeFunc>(
    238      GetProcAddress(mag_lib_handle_, "MagUninitialize"));
    239  set_window_source_func_ = reinterpret_cast<MagSetWindowSourceFunc>(
    240      GetProcAddress(mag_lib_handle_, "MagSetWindowSource"));
    241  set_window_filter_list_func_ = reinterpret_cast<MagSetWindowFilterListFunc>(
    242      GetProcAddress(mag_lib_handle_, "MagSetWindowFilterList"));
    243  set_image_scaling_callback_func_ =
    244      reinterpret_cast<MagSetImageScalingCallbackFunc>(
    245          GetProcAddress(mag_lib_handle_, "MagSetImageScalingCallback"));
    246 
    247  if (!mag_initialize_func_ || !mag_uninitialize_func_ ||
    248      !set_window_source_func_ || !set_window_filter_list_func_ ||
    249      !set_image_scaling_callback_func_) {
    250    RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    251                             "library functions missing.";
    252    return false;
    253  }
    254 
    255  BOOL result = mag_initialize_func_();
    256  if (!result) {
    257    RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    258                             "error from MagInitialize "
    259                          << GetLastError();
    260    return false;
    261  }
    262 
    263  HMODULE hInstance = nullptr;
    264  result =
    265      GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
    266                             GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
    267                         reinterpret_cast<char*>(&DefWindowProc), &hInstance);
    268  if (!result) {
    269    mag_uninitialize_func_();
    270    RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    271                             "error from GetModulehandleExA "
    272                          << GetLastError();
    273    return false;
    274  }
    275 
    276  // Register the host window class. See the MSDN documentation of the
    277  // Magnification API for more infomation.
    278  WNDCLASSEXW wcex = {};
    279  wcex.cbSize = sizeof(WNDCLASSEX);
    280  wcex.lpfnWndProc = &DefWindowProc;
    281  wcex.hInstance = hInstance;
    282  wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    283  wcex.lpszClassName = kMagnifierHostClass;
    284 
    285  // Ignore the error which may happen when the class is already registered.
    286  RegisterClassExW(&wcex);
    287 
    288  // Create the host window.
    289  host_window_ =
    290      CreateWindowExW(WS_EX_LAYERED, kMagnifierHostClass, kHostWindowName, 0, 0,
    291                      0, 0, 0, nullptr, nullptr, hInstance, nullptr);
    292  if (!host_window_) {
    293    mag_uninitialize_func_();
    294    RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    295                             "error from creating host window "
    296                          << GetLastError();
    297    return false;
    298  }
    299 
    300  // Create the magnifier control.
    301  magnifier_window_ = CreateWindowW(kMagnifierWindowClass, kMagnifierWindowName,
    302                                    WS_CHILD | WS_VISIBLE, 0, 0, 0, 0,
    303                                    host_window_, nullptr, hInstance, nullptr);
    304  if (!magnifier_window_) {
    305    mag_uninitialize_func_();
    306    RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    307                             "error from creating magnifier window "
    308                          << GetLastError();
    309    return false;
    310  }
    311 
    312  // Hide the host window.
    313  ShowWindow(host_window_, SW_HIDE);
    314 
    315  // Set the scaling callback to receive captured image.
    316  result = set_image_scaling_callback_func_(
    317      magnifier_window_,
    318      &ScreenCapturerWinMagnifier::OnMagImageScalingCallback);
    319  if (!result) {
    320    mag_uninitialize_func_();
    321    RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
    322                             "error from MagSetImageScalingCallback "
    323                          << GetLastError();
    324    return false;
    325  }
    326 
    327  if (excluded_window_) {
    328    result = set_window_filter_list_func_(
    329        magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_);
    330    if (!result) {
    331      mag_uninitialize_func_();
    332      RTC_LOG_F(LS_WARNING)
    333          << "Failed to initialize ScreenCapturerWinMagnifier: "
    334             "error from MagSetWindowFilterList "
    335          << GetLastError();
    336      return false;
    337    }
    338  }
    339 
    340  magnifier_initialized_ = true;
    341  return true;
    342 }
    343 
    344 void ScreenCapturerWinMagnifier::OnCaptured(void* data,
    345                                            const MAGIMAGEHEADER& header) {
    346  DesktopFrame* current_frame = queue_.current_frame();
    347 
    348  // Verify the format.
    349  // TODO(bugs.webrtc.org/436974448): support capturing sources with pixel
    350  // formats other than RGBA.
    351  int captured_bytes_per_pixel = header.cbSize / header.width / header.height;
    352  if (header.format != GUID_WICPixelFormat32bppRGBA ||
    353      header.width != static_cast<UINT>(current_frame->size().width()) ||
    354      header.height != static_cast<UINT>(current_frame->size().height()) ||
    355      header.stride != static_cast<UINT>(current_frame->stride()) ||
    356      captured_bytes_per_pixel != DesktopFrame::kBytesPerPixel) {
    357    RTC_LOG_F(LS_WARNING)
    358        << "Output format does not match the captured format: "
    359           "width = "
    360        << header.width
    361        << ", "
    362           "height = "
    363        << header.height
    364        << ", "
    365           "stride = "
    366        << header.stride
    367        << ", "
    368           "bpp = "
    369        << captured_bytes_per_pixel
    370        << ", "
    371           "pixel format RGBA ? "
    372        << (header.format == GUID_WICPixelFormat32bppRGBA) << ".";
    373    return;
    374  }
    375 
    376  // Copy the data into the frame.
    377  current_frame->CopyPixelsFrom(
    378      reinterpret_cast<uint8_t*>(data), header.stride,
    379      DesktopRect::MakeXYWH(0, 0, header.width, header.height));
    380 
    381  magnifier_capture_succeeded_ = true;
    382 }
    383 
    384 void ScreenCapturerWinMagnifier::CreateCurrentFrameIfNecessary(
    385    const DesktopSize& size) {
    386  // If the current buffer is from an older generation then allocate a new one.
    387  // Note that we can't reallocate other buffers at this point, since the caller
    388  // may still be reading from them.
    389  if (!queue_.current_frame() || !queue_.current_frame()->size().equals(size)) {
    390    std::unique_ptr<DesktopFrame> frame =
    391        shared_memory_factory_
    392            ? SharedMemoryDesktopFrame::Create(size, FOURCC_ARGB,
    393                                               shared_memory_factory_.get())
    394            : std::unique_ptr<DesktopFrame>(
    395                  new BasicDesktopFrame(size, FOURCC_ARGB));
    396    queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame)));
    397  }
    398 }
    399 
    400 }  // namespace webrtc