diff options
Diffstat (limited to 'third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc')
-rw-r--r-- | third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc | 398 |
1 files changed, 398 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc new file mode 100644 index 0000000000..ce747e0141 --- /dev/null +++ b/third_party/libwebrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc @@ -0,0 +1,398 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "modules/desktop_capture/win/screen_capturer_win_magnifier.h" + +#include <utility> + +#include "modules/desktop_capture/desktop_capture_metrics_helper.h" +#include "modules/desktop_capture/desktop_capture_options.h" +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_frame.h" +#include "modules/desktop_capture/desktop_frame_win.h" +#include "modules/desktop_capture/desktop_region.h" +#include "modules/desktop_capture/mouse_cursor.h" +#include "modules/desktop_capture/win/cursor.h" +#include "modules/desktop_capture/win/desktop.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/time_utils.h" +#include "system_wrappers/include/metrics.h" + +namespace webrtc { + +namespace { +DWORD GetTlsIndex() { + static const DWORD tls_index = TlsAlloc(); + RTC_DCHECK(tls_index != TLS_OUT_OF_INDEXES); + return tls_index; +} + +} // namespace + +// kMagnifierWindowClass has to be "Magnifier" according to the Magnification +// API. The other strings can be anything. +static wchar_t kMagnifierHostClass[] = L"ScreenCapturerWinMagnifierHost"; +static wchar_t kHostWindowName[] = L"MagnifierHost"; +static wchar_t kMagnifierWindowClass[] = L"Magnifier"; +static wchar_t kMagnifierWindowName[] = L"MagnifierWindow"; + +ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier() = default; +ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() { + // DestroyWindow must be called before MagUninitialize. magnifier_window_ is + // destroyed automatically when host_window_ is destroyed. + if (host_window_) { + DestroyWindow(host_window_); + host_window_ = NULL; + } + + if (magnifier_initialized_) { + mag_uninitialize_func_(); + magnifier_initialized_ = false; + } + + if (mag_lib_handle_) { + FreeLibrary(mag_lib_handle_); + mag_lib_handle_ = NULL; + } + + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } +} + +void ScreenCapturerWinMagnifier::Start(Callback* callback) { + RTC_DCHECK(!callback_); + RTC_DCHECK(callback); + RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinMagnifier); + + callback_ = callback; + + if (!InitializeMagnifier()) { + RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed."; + } +} + +void ScreenCapturerWinMagnifier::SetSharedMemoryFactory( + std::unique_ptr<SharedMemoryFactory> shared_memory_factory) { + shared_memory_factory_ = std::move(shared_memory_factory); +} + +void ScreenCapturerWinMagnifier::CaptureFrame() { + RTC_DCHECK(callback_); + if (!magnifier_initialized_) { + RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + int64_t capture_start_time_nanos = rtc::TimeNanos(); + + // Switch to the desktop receiving user input if different from the current + // one. + std::unique_ptr<Desktop> input_desktop(Desktop::GetInputDesktop()); + if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { + // Release GDI resources otherwise SetThreadDesktop will fail. + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + // If SetThreadDesktop() fails, the thread is still assigned a desktop. + // So we can continue capture screen bits, just from the wrong desktop. + desktop_.SetThreadDesktop(input_desktop.release()); + } + + DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_); + queue_.MoveToNextFrame(); + CreateCurrentFrameIfNecessary(rect.size()); + // CaptureImage may fail in some situations, e.g. windows8 metro mode. So + // defer to the fallback capturer if magnifier capturer did not work. + if (!CaptureImage(rect)) { + RTC_LOG_F(LS_WARNING) << "Magnifier capturer failed to capture a frame."; + callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr); + return; + } + + // Emit the current frame. + std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share(); + frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX), + GetDeviceCaps(desktop_dc_, LOGPIXELSY))); + frame->mutable_updated_region()->SetRect( + DesktopRect::MakeSize(frame->size())); + + int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) / + rtc::kNumNanosecsPerMillisec; + RTC_HISTOGRAM_COUNTS_1000( + "WebRTC.DesktopCapture.Win.MagnifierCapturerFrameTime", capture_time_ms); + frame->set_capture_time_ms(capture_time_ms); + frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinMagnifier); + callback_->OnCaptureResult(Result::SUCCESS, std::move(frame)); +} + +bool ScreenCapturerWinMagnifier::GetSourceList(SourceList* sources) { + return webrtc::GetScreenList(sources); +} + +bool ScreenCapturerWinMagnifier::SelectSource(SourceId id) { + if (IsScreenValid(id, ¤t_device_key_)) { + current_screen_id_ = id; + return true; + } + + return false; +} + +void ScreenCapturerWinMagnifier::SetExcludedWindow(WindowId excluded_window) { + excluded_window_ = (HWND)excluded_window; + if (excluded_window_ && magnifier_initialized_) { + set_window_filter_list_func_(magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, + &excluded_window_); + } +} + +bool ScreenCapturerWinMagnifier::CaptureImage(const DesktopRect& rect) { + RTC_DCHECK(magnifier_initialized_); + + // Set the magnifier control to cover the captured rect. The content of the + // magnifier control will be the captured image. + BOOL result = SetWindowPos(magnifier_window_, NULL, rect.left(), rect.top(), + rect.width(), rect.height(), 0); + if (!result) { + RTC_LOG_F(LS_WARNING) << "Failed to call SetWindowPos: " << GetLastError() + << ". Rect = {" << rect.left() << ", " << rect.top() + << ", " << rect.right() << ", " << rect.bottom() + << "}"; + return false; + } + + magnifier_capture_succeeded_ = false; + + RECT native_rect = {rect.left(), rect.top(), rect.right(), rect.bottom()}; + + TlsSetValue(GetTlsIndex(), this); + // OnCaptured will be called via OnMagImageScalingCallback and fill in the + // frame before set_window_source_func_ returns. + result = set_window_source_func_(magnifier_window_, native_rect); + + if (!result) { + RTC_LOG_F(LS_WARNING) << "Failed to call MagSetWindowSource: " + << GetLastError() << ". Rect = {" << rect.left() + << ", " << rect.top() << ", " << rect.right() << ", " + << rect.bottom() << "}"; + return false; + } + + return magnifier_capture_succeeded_; +} + +BOOL ScreenCapturerWinMagnifier::OnMagImageScalingCallback( + HWND hwnd, + void* srcdata, + MAGIMAGEHEADER srcheader, + void* destdata, + MAGIMAGEHEADER destheader, + RECT unclipped, + RECT clipped, + HRGN dirty) { + ScreenCapturerWinMagnifier* owner = + reinterpret_cast<ScreenCapturerWinMagnifier*>(TlsGetValue(GetTlsIndex())); + TlsSetValue(GetTlsIndex(), nullptr); + owner->OnCaptured(srcdata, srcheader); + + return TRUE; +} + +// TODO(zijiehe): These functions are available on Windows Vista or upper, so we +// do not need to use LoadLibrary and GetProcAddress anymore. Use regular +// include and function calls instead of a dynamical loaded library. +bool ScreenCapturerWinMagnifier::InitializeMagnifier() { + RTC_DCHECK(!magnifier_initialized_); + + if (GetSystemMetrics(SM_CMONITORS) != 1) { + // Do not try to use the magnifier in multi-screen setup (where the API + // crashes sometimes). + RTC_LOG_F(LS_WARNING) << "Magnifier capturer cannot work on multi-screen " + "system."; + return false; + } + + desktop_dc_ = GetDC(nullptr); + + mag_lib_handle_ = LoadLibraryW(L"Magnification.dll"); + if (!mag_lib_handle_) + return false; + + // Initialize Magnification API function pointers. + mag_initialize_func_ = reinterpret_cast<MagInitializeFunc>( + GetProcAddress(mag_lib_handle_, "MagInitialize")); + mag_uninitialize_func_ = reinterpret_cast<MagUninitializeFunc>( + GetProcAddress(mag_lib_handle_, "MagUninitialize")); + set_window_source_func_ = reinterpret_cast<MagSetWindowSourceFunc>( + GetProcAddress(mag_lib_handle_, "MagSetWindowSource")); + set_window_filter_list_func_ = reinterpret_cast<MagSetWindowFilterListFunc>( + GetProcAddress(mag_lib_handle_, "MagSetWindowFilterList")); + set_image_scaling_callback_func_ = + reinterpret_cast<MagSetImageScalingCallbackFunc>( + GetProcAddress(mag_lib_handle_, "MagSetImageScalingCallback")); + + if (!mag_initialize_func_ || !mag_uninitialize_func_ || + !set_window_source_func_ || !set_window_filter_list_func_ || + !set_image_scaling_callback_func_) { + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "library functions missing."; + return false; + } + + BOOL result = mag_initialize_func_(); + if (!result) { + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from MagInitialize " + << GetLastError(); + return false; + } + + HMODULE hInstance = nullptr; + result = + GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<char*>(&DefWindowProc), &hInstance); + if (!result) { + mag_uninitialize_func_(); + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from GetModulehandleExA " + << GetLastError(); + return false; + } + + // Register the host window class. See the MSDN documentation of the + // Magnification API for more infomation. + WNDCLASSEXW wcex = {}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = &DefWindowProc; + wcex.hInstance = hInstance; + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.lpszClassName = kMagnifierHostClass; + + // Ignore the error which may happen when the class is already registered. + RegisterClassExW(&wcex); + + // Create the host window. + host_window_ = + CreateWindowExW(WS_EX_LAYERED, kMagnifierHostClass, kHostWindowName, 0, 0, + 0, 0, 0, nullptr, nullptr, hInstance, nullptr); + if (!host_window_) { + mag_uninitialize_func_(); + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from creating host window " + << GetLastError(); + return false; + } + + // Create the magnifier control. + magnifier_window_ = CreateWindowW(kMagnifierWindowClass, kMagnifierWindowName, + WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, + host_window_, nullptr, hInstance, nullptr); + if (!magnifier_window_) { + mag_uninitialize_func_(); + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from creating magnifier window " + << GetLastError(); + return false; + } + + // Hide the host window. + ShowWindow(host_window_, SW_HIDE); + + // Set the scaling callback to receive captured image. + result = set_image_scaling_callback_func_( + magnifier_window_, + &ScreenCapturerWinMagnifier::OnMagImageScalingCallback); + if (!result) { + mag_uninitialize_func_(); + RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from MagSetImageScalingCallback " + << GetLastError(); + return false; + } + + if (excluded_window_) { + result = set_window_filter_list_func_( + magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_); + if (!result) { + mag_uninitialize_func_(); + RTC_LOG_F(LS_WARNING) + << "Failed to initialize ScreenCapturerWinMagnifier: " + "error from MagSetWindowFilterList " + << GetLastError(); + return false; + } + } + + magnifier_initialized_ = true; + return true; +} + +void ScreenCapturerWinMagnifier::OnCaptured(void* data, + const MAGIMAGEHEADER& header) { + DesktopFrame* current_frame = queue_.current_frame(); + + // Verify the format. + // TODO(jiayl): support capturing sources with pixel formats other than RGBA. + int captured_bytes_per_pixel = header.cbSize / header.width / header.height; + if (header.format != GUID_WICPixelFormat32bppRGBA || + header.width != static_cast<UINT>(current_frame->size().width()) || + header.height != static_cast<UINT>(current_frame->size().height()) || + header.stride != static_cast<UINT>(current_frame->stride()) || + captured_bytes_per_pixel != DesktopFrame::kBytesPerPixel) { + RTC_LOG_F(LS_WARNING) + << "Output format does not match the captured format: " + "width = " + << header.width + << ", " + "height = " + << header.height + << ", " + "stride = " + << header.stride + << ", " + "bpp = " + << captured_bytes_per_pixel + << ", " + "pixel format RGBA ? " + << (header.format == GUID_WICPixelFormat32bppRGBA) << "."; + return; + } + + // Copy the data into the frame. + current_frame->CopyPixelsFrom( + reinterpret_cast<uint8_t*>(data), header.stride, + DesktopRect::MakeXYWH(0, 0, header.width, header.height)); + + magnifier_capture_succeeded_ = true; +} + +void ScreenCapturerWinMagnifier::CreateCurrentFrameIfNecessary( + const DesktopSize& size) { + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame() || !queue_.current_frame()->size().equals(size)) { + std::unique_ptr<DesktopFrame> frame = + shared_memory_factory_ + ? SharedMemoryDesktopFrame::Create(size, + shared_memory_factory_.get()) + : std::unique_ptr<DesktopFrame>(new BasicDesktopFrame(size)); + queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame))); + } +} + +} // namespace webrtc |