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