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