• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(), &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 
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, &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 
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