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(), &region);
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, &current_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