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 "webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h"
12
13 #include <assert.h>
14
15 #include "webrtc/modules/desktop_capture/desktop_capture_options.h"
16 #include "webrtc/modules/desktop_capture/desktop_frame.h"
17 #include "webrtc/modules/desktop_capture/desktop_frame_win.h"
18 #include "webrtc/modules/desktop_capture/desktop_region.h"
19 #include "webrtc/modules/desktop_capture/differ.h"
20 #include "webrtc/modules/desktop_capture/mouse_cursor.h"
21 #include "webrtc/modules/desktop_capture/win/cursor.h"
22 #include "webrtc/modules/desktop_capture/win/desktop.h"
23 #include "webrtc/modules/desktop_capture/win/screen_capture_utils.h"
24 #include "webrtc/system_wrappers/include/logging.h"
25 #include "webrtc/system_wrappers/include/tick_util.h"
26
27 namespace webrtc {
28
29 namespace {
30
31 // Constants from dwmapi.h.
32 const UINT DWM_EC_DISABLECOMPOSITION = 0;
33 const UINT DWM_EC_ENABLECOMPOSITION = 1;
34
35 const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
36
37 } // namespace
38
ScreenCapturerWinGdi(const DesktopCaptureOptions & options)39 ScreenCapturerWinGdi::ScreenCapturerWinGdi(const DesktopCaptureOptions& options)
40 : callback_(NULL),
41 current_screen_id_(kFullDesktopScreenId),
42 desktop_dc_(NULL),
43 memory_dc_(NULL),
44 dwmapi_library_(NULL),
45 composition_func_(NULL),
46 set_thread_execution_state_failed_(false) {
47 if (options.disable_effects()) {
48 // Load dwmapi.dll dynamically since it is not available on XP.
49 if (!dwmapi_library_)
50 dwmapi_library_ = LoadLibrary(kDwmapiLibraryName);
51
52 if (dwmapi_library_) {
53 composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
54 GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
55 }
56 }
57 }
58
~ScreenCapturerWinGdi()59 ScreenCapturerWinGdi::~ScreenCapturerWinGdi() {
60 if (desktop_dc_)
61 ReleaseDC(NULL, desktop_dc_);
62 if (memory_dc_)
63 DeleteDC(memory_dc_);
64
65 // Restore Aero.
66 if (composition_func_)
67 (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
68
69 if (dwmapi_library_)
70 FreeLibrary(dwmapi_library_);
71 }
72
Capture(const DesktopRegion & region)73 void ScreenCapturerWinGdi::Capture(const DesktopRegion& region) {
74 TickTime capture_start_time = TickTime::Now();
75
76 queue_.MoveToNextFrame();
77
78 // Request that the system not power-down the system, or the display hardware.
79 if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
80 if (!set_thread_execution_state_failed_) {
81 set_thread_execution_state_failed_ = true;
82 LOG_F(LS_WARNING) << "Failed to make system & display power assertion: "
83 << GetLastError();
84 }
85 }
86
87 // Make sure the GDI capture resources are up-to-date.
88 PrepareCaptureResources();
89
90 if (!CaptureImage()) {
91 callback_->OnCaptureCompleted(NULL);
92 return;
93 }
94
95 const DesktopFrame* current_frame = queue_.current_frame();
96 const DesktopFrame* last_frame = queue_.previous_frame();
97 if (last_frame && last_frame->size().equals(current_frame->size())) {
98 // Make sure the differencer is set up correctly for these previous and
99 // current screens.
100 if (!differ_.get() ||
101 (differ_->width() != current_frame->size().width()) ||
102 (differ_->height() != current_frame->size().height()) ||
103 (differ_->bytes_per_row() != current_frame->stride())) {
104 differ_.reset(new Differ(current_frame->size().width(),
105 current_frame->size().height(),
106 DesktopFrame::kBytesPerPixel,
107 current_frame->stride()));
108 }
109
110 // Calculate difference between the two last captured frames.
111 DesktopRegion region;
112 differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(),
113 ®ion);
114 helper_.InvalidateRegion(region);
115 } else {
116 // No previous frame is available, or the screen is resized. Invalidate the
117 // whole screen.
118 helper_.InvalidateScreen(current_frame->size());
119 }
120
121 helper_.set_size_most_recent(current_frame->size());
122
123 // Emit the current frame.
124 DesktopFrame* frame = queue_.current_frame()->Share();
125 frame->set_dpi(DesktopVector(
126 GetDeviceCaps(desktop_dc_, LOGPIXELSX),
127 GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
128 frame->mutable_updated_region()->Clear();
129 helper_.TakeInvalidRegion(frame->mutable_updated_region());
130 frame->set_capture_time_ms(
131 (TickTime::Now() - capture_start_time).Milliseconds());
132 callback_->OnCaptureCompleted(frame);
133 }
134
GetScreenList(ScreenList * screens)135 bool ScreenCapturerWinGdi::GetScreenList(ScreenList* screens) {
136 return webrtc::GetScreenList(screens);
137 }
138
SelectScreen(ScreenId id)139 bool ScreenCapturerWinGdi::SelectScreen(ScreenId id) {
140 bool valid = IsScreenValid(id, ¤t_device_key_);
141 if (valid)
142 current_screen_id_ = id;
143 return valid;
144 }
145
Start(Callback * callback)146 void ScreenCapturerWinGdi::Start(Callback* callback) {
147 assert(!callback_);
148 assert(callback);
149
150 callback_ = callback;
151
152 // Vote to disable Aero composited desktop effects while capturing. Windows
153 // will restore Aero automatically if the process exits. This has no effect
154 // under Windows 8 or higher. See crbug.com/124018.
155 if (composition_func_)
156 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
157 }
158
PrepareCaptureResources()159 void ScreenCapturerWinGdi::PrepareCaptureResources() {
160 // Switch to the desktop receiving user input if different from the current
161 // one.
162 rtc::scoped_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
163 if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
164 // Release GDI resources otherwise SetThreadDesktop will fail.
165 if (desktop_dc_) {
166 ReleaseDC(NULL, desktop_dc_);
167 desktop_dc_ = NULL;
168 }
169
170 if (memory_dc_) {
171 DeleteDC(memory_dc_);
172 memory_dc_ = NULL;
173 }
174
175 // If SetThreadDesktop() fails, the thread is still assigned a desktop.
176 // So we can continue capture screen bits, just from the wrong desktop.
177 desktop_.SetThreadDesktop(input_desktop.release());
178
179 // Re-assert our vote to disable Aero.
180 // See crbug.com/124018 and crbug.com/129906.
181 if (composition_func_ != NULL) {
182 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
183 }
184 }
185
186 // If the display bounds have changed then recreate GDI resources.
187 // TODO(wez): Also check for pixel format changes.
188 DesktopRect screen_rect(DesktopRect::MakeXYWH(
189 GetSystemMetrics(SM_XVIRTUALSCREEN),
190 GetSystemMetrics(SM_YVIRTUALSCREEN),
191 GetSystemMetrics(SM_CXVIRTUALSCREEN),
192 GetSystemMetrics(SM_CYVIRTUALSCREEN)));
193 if (!screen_rect.equals(desktop_dc_rect_)) {
194 if (desktop_dc_) {
195 ReleaseDC(NULL, desktop_dc_);
196 desktop_dc_ = NULL;
197 }
198 if (memory_dc_) {
199 DeleteDC(memory_dc_);
200 memory_dc_ = NULL;
201 }
202 desktop_dc_rect_ = DesktopRect();
203 }
204
205 if (desktop_dc_ == NULL) {
206 assert(memory_dc_ == NULL);
207
208 // Create GDI device contexts to capture from the desktop into memory.
209 desktop_dc_ = GetDC(NULL);
210 if (!desktop_dc_)
211 abort();
212 memory_dc_ = CreateCompatibleDC(desktop_dc_);
213 if (!memory_dc_)
214 abort();
215
216 desktop_dc_rect_ = screen_rect;
217
218 // Make sure the frame buffers will be reallocated.
219 queue_.Reset();
220
221 helper_.ClearInvalidRegion();
222 }
223 }
224
CaptureImage()225 bool ScreenCapturerWinGdi::CaptureImage() {
226 DesktopRect screen_rect =
227 GetScreenRect(current_screen_id_, current_device_key_);
228 if (screen_rect.is_empty())
229 return false;
230
231 DesktopSize size = screen_rect.size();
232 // If the current buffer is from an older generation then allocate a new one.
233 // Note that we can't reallocate other buffers at this point, since the caller
234 // may still be reading from them.
235 if (!queue_.current_frame() ||
236 !queue_.current_frame()->size().equals(screen_rect.size())) {
237 assert(desktop_dc_ != NULL);
238 assert(memory_dc_ != NULL);
239
240 size_t buffer_size = size.width() * size.height() *
241 DesktopFrame::kBytesPerPixel;
242 SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size);
243
244 rtc::scoped_ptr<DesktopFrame> buffer(
245 DesktopFrameWin::Create(size, shared_memory, desktop_dc_));
246 if (!buffer.get())
247 return false;
248 queue_.ReplaceCurrentFrame(buffer.release());
249 }
250
251 // Select the target bitmap into the memory dc and copy the rect from desktop
252 // to memory.
253 DesktopFrameWin* current = static_cast<DesktopFrameWin*>(
254 queue_.current_frame()->GetUnderlyingFrame());
255 HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
256 if (previous_object != NULL) {
257 BitBlt(memory_dc_,
258 0, 0, screen_rect.width(), screen_rect.height(),
259 desktop_dc_,
260 screen_rect.left(), screen_rect.top(),
261 SRCCOPY | CAPTUREBLT);
262
263 // Select back the previously selected object to that the device contect
264 // could be destroyed independently of the bitmap if needed.
265 SelectObject(memory_dc_, previous_object);
266 }
267 return true;
268 }
269
270 } // namespace webrtc
271