• 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 "modules/desktop_capture/win/screen_capturer_win_magnifier.h"
12 
13 #include <utility>
14 
15 #include "modules/desktop_capture/desktop_capture_options.h"
16 #include "modules/desktop_capture/desktop_frame.h"
17 #include "modules/desktop_capture/desktop_frame_win.h"
18 #include "modules/desktop_capture/desktop_region.h"
19 #include "modules/desktop_capture/mouse_cursor.h"
20 #include "modules/desktop_capture/win/cursor.h"
21 #include "modules/desktop_capture/win/desktop.h"
22 #include "modules/desktop_capture/win/screen_capture_utils.h"
23 #include "rtc_base/checks.h"
24 #include "rtc_base/logging.h"
25 #include "rtc_base/time_utils.h"
26 
27 namespace webrtc {
28 
29 namespace {
GetTlsIndex()30 DWORD GetTlsIndex() {
31   static const DWORD tls_index = TlsAlloc();
32   RTC_DCHECK(tls_index != TLS_OUT_OF_INDEXES);
33   return tls_index;
34 }
35 
36 }  // namespace
37 
38 // kMagnifierWindowClass has to be "Magnifier" according to the Magnification
39 // API. The other strings can be anything.
40 static wchar_t kMagnifierHostClass[] = L"ScreenCapturerWinMagnifierHost";
41 static wchar_t kHostWindowName[] = L"MagnifierHost";
42 static wchar_t kMagnifierWindowClass[] = L"Magnifier";
43 static wchar_t kMagnifierWindowName[] = L"MagnifierWindow";
44 
45 ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier() = default;
~ScreenCapturerWinMagnifier()46 ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() {
47   // DestroyWindow must be called before MagUninitialize. magnifier_window_ is
48   // destroyed automatically when host_window_ is destroyed.
49   if (host_window_)
50     DestroyWindow(host_window_);
51 
52   if (magnifier_initialized_)
53     mag_uninitialize_func_();
54 
55   if (mag_lib_handle_)
56     FreeLibrary(mag_lib_handle_);
57 
58   if (desktop_dc_)
59     ReleaseDC(NULL, desktop_dc_);
60 }
61 
Start(Callback * callback)62 void ScreenCapturerWinMagnifier::Start(Callback* callback) {
63   RTC_DCHECK(!callback_);
64   RTC_DCHECK(callback);
65   callback_ = callback;
66 
67   if (!InitializeMagnifier()) {
68     RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed.";
69   }
70 }
71 
SetSharedMemoryFactory(std::unique_ptr<SharedMemoryFactory> shared_memory_factory)72 void ScreenCapturerWinMagnifier::SetSharedMemoryFactory(
73     std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
74   shared_memory_factory_ = std::move(shared_memory_factory);
75 }
76 
CaptureFrame()77 void ScreenCapturerWinMagnifier::CaptureFrame() {
78   RTC_DCHECK(callback_);
79   if (!magnifier_initialized_) {
80     RTC_LOG_F(LS_WARNING) << "Magnifier initialization failed.";
81     callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
82     return;
83   }
84 
85   int64_t capture_start_time_nanos = rtc::TimeNanos();
86 
87   // Switch to the desktop receiving user input if different from the current
88   // one.
89   std::unique_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
90   if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
91     // Release GDI resources otherwise SetThreadDesktop will fail.
92     if (desktop_dc_) {
93       ReleaseDC(NULL, desktop_dc_);
94       desktop_dc_ = NULL;
95     }
96     // If SetThreadDesktop() fails, the thread is still assigned a desktop.
97     // So we can continue capture screen bits, just from the wrong desktop.
98     desktop_.SetThreadDesktop(input_desktop.release());
99   }
100 
101   DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_);
102   queue_.MoveToNextFrame();
103   CreateCurrentFrameIfNecessary(rect.size());
104   // CaptureImage may fail in some situations, e.g. windows8 metro mode. So
105   // defer to the fallback capturer if magnifier capturer did not work.
106   if (!CaptureImage(rect)) {
107     RTC_LOG_F(LS_WARNING) << "Magnifier capturer failed to capture a frame.";
108     callback_->OnCaptureResult(Result::ERROR_PERMANENT, nullptr);
109     return;
110   }
111 
112   // Emit the current frame.
113   std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share();
114   frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX),
115                                GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
116   frame->mutable_updated_region()->SetRect(
117       DesktopRect::MakeSize(frame->size()));
118   frame->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) /
119                              rtc::kNumNanosecsPerMillisec);
120   callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
121 }
122 
GetSourceList(SourceList * sources)123 bool ScreenCapturerWinMagnifier::GetSourceList(SourceList* sources) {
124   return webrtc::GetScreenList(sources);
125 }
126 
SelectSource(SourceId id)127 bool ScreenCapturerWinMagnifier::SelectSource(SourceId id) {
128   if (IsScreenValid(id, &current_device_key_)) {
129     current_screen_id_ = id;
130     return true;
131   }
132 
133   return false;
134 }
135 
SetExcludedWindow(WindowId excluded_window)136 void ScreenCapturerWinMagnifier::SetExcludedWindow(WindowId excluded_window) {
137   excluded_window_ = (HWND)excluded_window;
138   if (excluded_window_ && magnifier_initialized_) {
139     set_window_filter_list_func_(magnifier_window_, MW_FILTERMODE_EXCLUDE, 1,
140                                  &excluded_window_);
141   }
142 }
143 
CaptureImage(const DesktopRect & rect)144 bool ScreenCapturerWinMagnifier::CaptureImage(const DesktopRect& rect) {
145   RTC_DCHECK(magnifier_initialized_);
146 
147   // Set the magnifier control to cover the captured rect. The content of the
148   // magnifier control will be the captured image.
149   BOOL result = SetWindowPos(magnifier_window_, NULL, rect.left(), rect.top(),
150                              rect.width(), rect.height(), 0);
151   if (!result) {
152     RTC_LOG_F(LS_WARNING) << "Failed to call SetWindowPos: " << GetLastError()
153                           << ". Rect = {" << rect.left() << ", " << rect.top()
154                           << ", " << rect.right() << ", " << rect.bottom()
155                           << "}";
156     return false;
157   }
158 
159   magnifier_capture_succeeded_ = false;
160 
161   RECT native_rect = {rect.left(), rect.top(), rect.right(), rect.bottom()};
162 
163   TlsSetValue(GetTlsIndex(), this);
164   // OnCaptured will be called via OnMagImageScalingCallback and fill in the
165   // frame before set_window_source_func_ returns.
166   result = set_window_source_func_(magnifier_window_, native_rect);
167 
168   if (!result) {
169     RTC_LOG_F(LS_WARNING) << "Failed to call MagSetWindowSource: "
170                           << GetLastError() << ". Rect = {" << rect.left()
171                           << ", " << rect.top() << ", " << rect.right() << ", "
172                           << rect.bottom() << "}";
173     return false;
174   }
175 
176   return magnifier_capture_succeeded_;
177 }
178 
OnMagImageScalingCallback(HWND hwnd,void * srcdata,MAGIMAGEHEADER srcheader,void * destdata,MAGIMAGEHEADER destheader,RECT unclipped,RECT clipped,HRGN dirty)179 BOOL ScreenCapturerWinMagnifier::OnMagImageScalingCallback(
180     HWND hwnd,
181     void* srcdata,
182     MAGIMAGEHEADER srcheader,
183     void* destdata,
184     MAGIMAGEHEADER destheader,
185     RECT unclipped,
186     RECT clipped,
187     HRGN dirty) {
188   ScreenCapturerWinMagnifier* owner =
189       reinterpret_cast<ScreenCapturerWinMagnifier*>(TlsGetValue(GetTlsIndex()));
190   TlsSetValue(GetTlsIndex(), nullptr);
191   owner->OnCaptured(srcdata, srcheader);
192 
193   return TRUE;
194 }
195 
196 // TODO(zijiehe): These functions are available on Windows Vista or upper, so we
197 // do not need to use LoadLibrary and GetProcAddress anymore. Use regular
198 // include and function calls instead of a dynamical loaded library.
InitializeMagnifier()199 bool ScreenCapturerWinMagnifier::InitializeMagnifier() {
200   RTC_DCHECK(!magnifier_initialized_);
201 
202   if (GetSystemMetrics(SM_CMONITORS) != 1) {
203     // Do not try to use the magnifier in multi-screen setup (where the API
204     // crashes sometimes).
205     RTC_LOG_F(LS_WARNING) << "Magnifier capturer cannot work on multi-screen "
206                              "system.";
207     return false;
208   }
209 
210   desktop_dc_ = GetDC(nullptr);
211 
212   mag_lib_handle_ = LoadLibraryW(L"Magnification.dll");
213   if (!mag_lib_handle_)
214     return false;
215 
216   // Initialize Magnification API function pointers.
217   mag_initialize_func_ = reinterpret_cast<MagInitializeFunc>(
218       GetProcAddress(mag_lib_handle_, "MagInitialize"));
219   mag_uninitialize_func_ = reinterpret_cast<MagUninitializeFunc>(
220       GetProcAddress(mag_lib_handle_, "MagUninitialize"));
221   set_window_source_func_ = reinterpret_cast<MagSetWindowSourceFunc>(
222       GetProcAddress(mag_lib_handle_, "MagSetWindowSource"));
223   set_window_filter_list_func_ = reinterpret_cast<MagSetWindowFilterListFunc>(
224       GetProcAddress(mag_lib_handle_, "MagSetWindowFilterList"));
225   set_image_scaling_callback_func_ =
226       reinterpret_cast<MagSetImageScalingCallbackFunc>(
227           GetProcAddress(mag_lib_handle_, "MagSetImageScalingCallback"));
228 
229   if (!mag_initialize_func_ || !mag_uninitialize_func_ ||
230       !set_window_source_func_ || !set_window_filter_list_func_ ||
231       !set_image_scaling_callback_func_) {
232     RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
233                              "library functions missing.";
234     return false;
235   }
236 
237   BOOL result = mag_initialize_func_();
238   if (!result) {
239     RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
240                              "error from MagInitialize "
241                           << GetLastError();
242     return false;
243   }
244 
245   HMODULE hInstance = nullptr;
246   result =
247       GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
248                              GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
249                          reinterpret_cast<char*>(&DefWindowProc), &hInstance);
250   if (!result) {
251     mag_uninitialize_func_();
252     RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
253                              "error from GetModulehandleExA "
254                           << GetLastError();
255     return false;
256   }
257 
258   // Register the host window class. See the MSDN documentation of the
259   // Magnification API for more infomation.
260   WNDCLASSEXW wcex = {};
261   wcex.cbSize = sizeof(WNDCLASSEX);
262   wcex.lpfnWndProc = &DefWindowProc;
263   wcex.hInstance = hInstance;
264   wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
265   wcex.lpszClassName = kMagnifierHostClass;
266 
267   // Ignore the error which may happen when the class is already registered.
268   RegisterClassExW(&wcex);
269 
270   // Create the host window.
271   host_window_ =
272       CreateWindowExW(WS_EX_LAYERED, kMagnifierHostClass, kHostWindowName, 0, 0,
273                       0, 0, 0, nullptr, nullptr, hInstance, nullptr);
274   if (!host_window_) {
275     mag_uninitialize_func_();
276     RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
277                              "error from creating host window "
278                           << GetLastError();
279     return false;
280   }
281 
282   // Create the magnifier control.
283   magnifier_window_ = CreateWindowW(kMagnifierWindowClass, kMagnifierWindowName,
284                                     WS_CHILD | WS_VISIBLE, 0, 0, 0, 0,
285                                     host_window_, nullptr, hInstance, nullptr);
286   if (!magnifier_window_) {
287     mag_uninitialize_func_();
288     RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
289                              "error from creating magnifier window "
290                           << GetLastError();
291     return false;
292   }
293 
294   // Hide the host window.
295   ShowWindow(host_window_, SW_HIDE);
296 
297   // Set the scaling callback to receive captured image.
298   result = set_image_scaling_callback_func_(
299       magnifier_window_,
300       &ScreenCapturerWinMagnifier::OnMagImageScalingCallback);
301   if (!result) {
302     mag_uninitialize_func_();
303     RTC_LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: "
304                              "error from MagSetImageScalingCallback "
305                           << GetLastError();
306     return false;
307   }
308 
309   if (excluded_window_) {
310     result = set_window_filter_list_func_(
311         magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_);
312     if (!result) {
313       mag_uninitialize_func_();
314       RTC_LOG_F(LS_WARNING)
315           << "Failed to initialize ScreenCapturerWinMagnifier: "
316              "error from MagSetWindowFilterList "
317           << GetLastError();
318       return false;
319     }
320   }
321 
322   magnifier_initialized_ = true;
323   return true;
324 }
325 
OnCaptured(void * data,const MAGIMAGEHEADER & header)326 void ScreenCapturerWinMagnifier::OnCaptured(void* data,
327                                             const MAGIMAGEHEADER& header) {
328   DesktopFrame* current_frame = queue_.current_frame();
329 
330   // Verify the format.
331   // TODO(jiayl): support capturing sources with pixel formats other than RGBA.
332   int captured_bytes_per_pixel = header.cbSize / header.width / header.height;
333   if (header.format != GUID_WICPixelFormat32bppRGBA ||
334       header.width != static_cast<UINT>(current_frame->size().width()) ||
335       header.height != static_cast<UINT>(current_frame->size().height()) ||
336       header.stride != static_cast<UINT>(current_frame->stride()) ||
337       captured_bytes_per_pixel != DesktopFrame::kBytesPerPixel) {
338     RTC_LOG_F(LS_WARNING)
339         << "Output format does not match the captured format: "
340            "width = "
341         << header.width
342         << ", "
343            "height = "
344         << header.height
345         << ", "
346            "stride = "
347         << header.stride
348         << ", "
349            "bpp = "
350         << captured_bytes_per_pixel
351         << ", "
352            "pixel format RGBA ? "
353         << (header.format == GUID_WICPixelFormat32bppRGBA) << ".";
354     return;
355   }
356 
357   // Copy the data into the frame.
358   current_frame->CopyPixelsFrom(
359       reinterpret_cast<uint8_t*>(data), header.stride,
360       DesktopRect::MakeXYWH(0, 0, header.width, header.height));
361 
362   magnifier_capture_succeeded_ = true;
363 }
364 
CreateCurrentFrameIfNecessary(const DesktopSize & size)365 void ScreenCapturerWinMagnifier::CreateCurrentFrameIfNecessary(
366     const DesktopSize& size) {
367   // If the current buffer is from an older generation then allocate a new one.
368   // Note that we can't reallocate other buffers at this point, since the caller
369   // may still be reading from them.
370   if (!queue_.current_frame() || !queue_.current_frame()->size().equals(size)) {
371     std::unique_ptr<DesktopFrame> frame =
372         shared_memory_factory_
373             ? SharedMemoryDesktopFrame::Create(size,
374                                                shared_memory_factory_.get())
375             : std::unique_ptr<DesktopFrame>(new BasicDesktopFrame(size));
376     queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(frame)));
377   }
378 }
379 
380 }  // namespace webrtc
381