xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
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, &current_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