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