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(), &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
170bool ScreenCapturerWinMagnifier::GetScreenList(ScreenList* screens) {
171  return webrtc::GetScreenList(screens);
172}
173
174bool 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
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