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