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 27namespace webrtc { 28 29// kMagnifierWindowClass has to be "Magnifier" according to the Magnification 30// API. The other strings can be anything. 31static LPCTSTR kMagnifierHostClass = L"ScreenCapturerWinMagnifierHost"; 32static LPCTSTR kHostWindowName = L"MagnifierHost"; 33static LPCTSTR kMagnifierWindowClass = L"Magnifier"; 34static LPCTSTR kMagnifierWindowName = L"MagnifierWindow"; 35 36Atomic32 ScreenCapturerWinMagnifier::tls_index_(TLS_OUT_OF_INDEXES); 37 38ScreenCapturerWinMagnifier::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 59ScreenCapturerWinMagnifier::~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 75void ScreenCapturerWinMagnifier::Start(Callback* callback) { 76 assert(!callback_); 77 assert(callback); 78 callback_ = callback; 79 80 InitializeMagnifier(); 81} 82 83void 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(), ®ion); 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 169void ScreenCapturerWinMagnifier::SetMouseShapeObserver( 170 MouseShapeObserver* mouse_shape_observer) { 171 assert(false); // NOTREACHED(); 172} 173 174bool ScreenCapturerWinMagnifier::GetScreenList(ScreenList* screens) { 175 return webrtc::GetScreenList(screens); 176} 177 178bool ScreenCapturerWinMagnifier::SelectScreen(ScreenId id) { 179 bool valid = IsScreenValid(id, ¤t_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 192void 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 200bool 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 235BOOL 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 255bool 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 392void 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 423void 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 444bool 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 451void 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