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