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
27 namespace webrtc {
28
29 // kMagnifierWindowClass has to be "Magnifier" according to the Magnification
30 // API. The other strings can be anything.
31 static LPCTSTR kMagnifierHostClass = L"ScreenCapturerWinMagnifierHost";
32 static LPCTSTR kHostWindowName = L"MagnifierHost";
33 static LPCTSTR kMagnifierWindowClass = L"Magnifier";
34 static LPCTSTR kMagnifierWindowName = L"MagnifierWindow";
35
36 Atomic32 ScreenCapturerWinMagnifier::tls_index_(TLS_OUT_OF_INDEXES);
37
ScreenCapturerWinMagnifier(scoped_ptr<ScreenCapturer> fallback_capturer)38 ScreenCapturerWinMagnifier::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
~ScreenCapturerWinMagnifier()59 ScreenCapturerWinMagnifier::~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
Start(Callback * callback)75 void ScreenCapturerWinMagnifier::Start(Callback* callback) {
76 assert(!callback_);
77 assert(callback);
78 callback_ = callback;
79
80 InitializeMagnifier();
81 }
82
Capture(const DesktopRegion & region)83 void 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
SetMouseShapeObserver(MouseShapeObserver * mouse_shape_observer)169 void ScreenCapturerWinMagnifier::SetMouseShapeObserver(
170 MouseShapeObserver* mouse_shape_observer) {
171 assert(false); // NOTREACHED();
172 }
173
GetScreenList(ScreenList * screens)174 bool ScreenCapturerWinMagnifier::GetScreenList(ScreenList* screens) {
175 return webrtc::GetScreenList(screens);
176 }
177
SelectScreen(ScreenId id)178 bool 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
SetExcludedWindow(WindowId excluded_window)192 void 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
CaptureImage(const DesktopRect & rect)200 bool 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
OnMagImageScalingCallback(HWND hwnd,void * srcdata,MAGIMAGEHEADER srcheader,void * destdata,MAGIMAGEHEADER destheader,RECT unclipped,RECT clipped,HRGN dirty)235 BOOL 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
InitializeMagnifier()255 bool 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
OnCaptured(void * data,const MAGIMAGEHEADER & header)392 void 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
CreateCurrentFrameIfNecessary(const DesktopSize & size)423 void 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
IsCapturingPrimaryScreenOnly() const444 bool 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
StartFallbackCapturer()451 void 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