xref: /aosp_15_r20/external/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.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_gdi.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 "rtc_base/trace_event.h"
29 #include "system_wrappers/include/metrics.h"
30 
31 namespace webrtc {
32 
33 namespace {
34 
35 // Constants from dwmapi.h.
36 const UINT DWM_EC_DISABLECOMPOSITION = 0;
37 const UINT DWM_EC_ENABLECOMPOSITION = 1;
38 
39 const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
40 
41 }  // namespace
42 
ScreenCapturerWinGdi(const DesktopCaptureOptions & options)43 ScreenCapturerWinGdi::ScreenCapturerWinGdi(
44     const DesktopCaptureOptions& options) {
45   if (options.disable_effects()) {
46     // Load dwmapi.dll dynamically since it is not available on XP.
47     if (!dwmapi_library_)
48       dwmapi_library_ = LoadLibraryW(kDwmapiLibraryName);
49 
50     if (dwmapi_library_) {
51       composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
52           GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
53     }
54   }
55 }
56 
~ScreenCapturerWinGdi()57 ScreenCapturerWinGdi::~ScreenCapturerWinGdi() {
58   if (desktop_dc_)
59     ReleaseDC(NULL, desktop_dc_);
60   if (memory_dc_)
61     DeleteDC(memory_dc_);
62 
63   // Restore Aero.
64   if (composition_func_)
65     (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
66 
67   if (dwmapi_library_)
68     FreeLibrary(dwmapi_library_);
69 }
70 
SetSharedMemoryFactory(std::unique_ptr<SharedMemoryFactory> shared_memory_factory)71 void ScreenCapturerWinGdi::SetSharedMemoryFactory(
72     std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
73   shared_memory_factory_ = std::move(shared_memory_factory);
74 }
75 
CaptureFrame()76 void ScreenCapturerWinGdi::CaptureFrame() {
77   TRACE_EVENT0("webrtc", "ScreenCapturerWinGdi::CaptureFrame");
78   int64_t capture_start_time_nanos = rtc::TimeNanos();
79 
80   queue_.MoveToNextFrame();
81   if (queue_.current_frame() && queue_.current_frame()->IsShared()) {
82     RTC_DLOG(LS_WARNING) << "Overwriting frame that is still shared.";
83   }
84 
85   // Make sure the GDI capture resources are up-to-date.
86   PrepareCaptureResources();
87 
88   if (!CaptureImage()) {
89     RTC_LOG(LS_WARNING) << "Failed to capture screen by GDI.";
90     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
91     return;
92   }
93 
94   // Emit the current frame.
95   std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share();
96   frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX),
97                                GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
98   frame->mutable_updated_region()->SetRect(
99       DesktopRect::MakeSize(frame->size()));
100 
101   int capture_time_ms = (rtc::TimeNanos() - capture_start_time_nanos) /
102                         rtc::kNumNanosecsPerMillisec;
103   RTC_HISTOGRAM_COUNTS_1000(
104       "WebRTC.DesktopCapture.Win.ScreenGdiCapturerFrameTime", capture_time_ms);
105   frame->set_capture_time_ms(capture_time_ms);
106   frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinGdi);
107   callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
108 }
109 
GetSourceList(SourceList * sources)110 bool ScreenCapturerWinGdi::GetSourceList(SourceList* sources) {
111   return webrtc::GetScreenList(sources);
112 }
113 
SelectSource(SourceId id)114 bool ScreenCapturerWinGdi::SelectSource(SourceId id) {
115   bool valid = IsScreenValid(id, &current_device_key_);
116   if (valid)
117     current_screen_id_ = id;
118   return valid;
119 }
120 
Start(Callback * callback)121 void ScreenCapturerWinGdi::Start(Callback* callback) {
122   RTC_DCHECK(!callback_);
123   RTC_DCHECK(callback);
124   RecordCapturerImpl(DesktopCapturerId::kScreenCapturerWinGdi);
125 
126   callback_ = callback;
127 
128   // Vote to disable Aero composited desktop effects while capturing. Windows
129   // will restore Aero automatically if the process exits. This has no effect
130   // under Windows 8 or higher.  See crbug.com/124018.
131   if (composition_func_)
132     (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
133 }
134 
PrepareCaptureResources()135 void ScreenCapturerWinGdi::PrepareCaptureResources() {
136   // Switch to the desktop receiving user input if different from the current
137   // one.
138   std::unique_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
139   if (input_desktop && !desktop_.IsSame(*input_desktop)) {
140     // Release GDI resources otherwise SetThreadDesktop will fail.
141     if (desktop_dc_) {
142       ReleaseDC(NULL, desktop_dc_);
143       desktop_dc_ = nullptr;
144     }
145 
146     if (memory_dc_) {
147       DeleteDC(memory_dc_);
148       memory_dc_ = nullptr;
149     }
150 
151     // If SetThreadDesktop() fails, the thread is still assigned a desktop.
152     // So we can continue capture screen bits, just from the wrong desktop.
153     desktop_.SetThreadDesktop(input_desktop.release());
154 
155     // Re-assert our vote to disable Aero.
156     // See crbug.com/124018 and crbug.com/129906.
157     if (composition_func_) {
158       (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
159     }
160   }
161 
162   // If the display configurations have changed then recreate GDI resources.
163   if (display_configuration_monitor_.IsChanged()) {
164     if (desktop_dc_) {
165       ReleaseDC(NULL, desktop_dc_);
166       desktop_dc_ = nullptr;
167     }
168     if (memory_dc_) {
169       DeleteDC(memory_dc_);
170       memory_dc_ = nullptr;
171     }
172   }
173 
174   if (!desktop_dc_) {
175     RTC_DCHECK(!memory_dc_);
176 
177     // Create GDI device contexts to capture from the desktop into memory.
178     desktop_dc_ = GetDC(nullptr);
179     RTC_CHECK(desktop_dc_);
180     memory_dc_ = CreateCompatibleDC(desktop_dc_);
181     RTC_CHECK(memory_dc_);
182 
183     // Make sure the frame buffers will be reallocated.
184     queue_.Reset();
185   }
186 }
187 
CaptureImage()188 bool ScreenCapturerWinGdi::CaptureImage() {
189   DesktopRect screen_rect =
190       GetScreenRect(current_screen_id_, current_device_key_);
191   if (screen_rect.is_empty()) {
192     RTC_LOG(LS_WARNING) << "Failed to get screen rect.";
193     return false;
194   }
195 
196   DesktopSize size = screen_rect.size();
197   // If the current buffer is from an older generation then allocate a new one.
198   // Note that we can't reallocate other buffers at this point, since the caller
199   // may still be reading from them.
200   if (!queue_.current_frame() ||
201       !queue_.current_frame()->size().equals(screen_rect.size())) {
202     RTC_DCHECK(desktop_dc_);
203     RTC_DCHECK(memory_dc_);
204 
205     std::unique_ptr<DesktopFrame> buffer = DesktopFrameWin::Create(
206         size, shared_memory_factory_.get(), desktop_dc_);
207     if (!buffer) {
208       RTC_LOG(LS_WARNING) << "Failed to create frame buffer.";
209       return false;
210     }
211     queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(buffer)));
212   }
213   queue_.current_frame()->set_top_left(
214       screen_rect.top_left().subtract(GetFullscreenRect().top_left()));
215 
216   // Select the target bitmap into the memory dc and copy the rect from desktop
217   // to memory.
218   DesktopFrameWin* current = static_cast<DesktopFrameWin*>(
219       queue_.current_frame()->GetUnderlyingFrame());
220   HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
221   if (!previous_object || previous_object == HGDI_ERROR) {
222     RTC_LOG(LS_WARNING) << "Failed to select current bitmap into memery dc.";
223     return false;
224   }
225 
226   bool result = (BitBlt(memory_dc_, 0, 0, screen_rect.width(),
227                         screen_rect.height(), desktop_dc_, screen_rect.left(),
228                         screen_rect.top(), SRCCOPY | CAPTUREBLT) != FALSE);
229   if (!result) {
230     RTC_LOG_GLE(LS_WARNING) << "BitBlt failed";
231   }
232 
233   // Select back the previously selected object to that the device contect
234   // could be destroyed independently of the bitmap if needed.
235   SelectObject(memory_dc_, previous_object);
236 
237   return result;
238 }
239 
240 }  // namespace webrtc
241