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, ¤t_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