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