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
29 namespace webrtc {
30
31 // kMagnifierWindowClass has to be "Magnifier" according to the Magnification
32 // API. The other strings can be anything.
33 static LPCTSTR kMagnifierHostClass = L"ScreenCapturerWinMagnifierHost";
34 static LPCTSTR kHostWindowName = L"MagnifierHost";
35 static LPCTSTR kMagnifierWindowClass = L"Magnifier";
36 static LPCTSTR kMagnifierWindowName = L"MagnifierWindow";
37
38 Atomic32 ScreenCapturerWinMagnifier::tls_index_(TLS_OUT_OF_INDEXES);
39
ScreenCapturerWinMagnifier(rtc::scoped_ptr<ScreenCapturer> fallback_capturer)40 ScreenCapturerWinMagnifier::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
~ScreenCapturerWinMagnifier()60 ScreenCapturerWinMagnifier::~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
Start(Callback * callback)76 void ScreenCapturerWinMagnifier::Start(Callback* callback) {
77 assert(!callback_);
78 assert(callback);
79 callback_ = callback;
80
81 InitializeMagnifier();
82 }
83
Capture(const DesktopRegion & region)84 void 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
GetScreenList(ScreenList * screens)170 bool ScreenCapturerWinMagnifier::GetScreenList(ScreenList* screens) {
171 return webrtc::GetScreenList(screens);
172 }
173
SelectScreen(ScreenId id)174 bool 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
SetExcludedWindow(WindowId excluded_window)188 void 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
CaptureImage(const DesktopRect & rect)196 bool 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
OnMagImageScalingCallback(HWND hwnd,void * srcdata,MAGIMAGEHEADER srcheader,void * destdata,MAGIMAGEHEADER destheader,RECT unclipped,RECT clipped,HRGN dirty)231 BOOL 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
InitializeMagnifier()251 bool 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
OnCaptured(void * data,const MAGIMAGEHEADER & header)388 void 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
CreateCurrentFrameIfNecessary(const DesktopSize & size)419 void 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
StartFallbackCapturer()440 void 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