blob: 42a71922e15940afa57bb291e7c0ca01f255c8cd [file] [log] [blame]
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +00001/*
2 * Copyright (c) 2013 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/screen_capturer.h"
12
13#include <windows.h>
14
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +000015#include "webrtc/modules/desktop_capture/desktop_capture_options.h"
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +000016#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"
sergeyu@chromium.org2873c4c2013-10-17 19:47:18 +000020#include "webrtc/modules/desktop_capture/mouse_cursor.h"
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +000021#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h"
22#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h"
23#include "webrtc/modules/desktop_capture/screen_capturer_helper.h"
alexeypa@chromium.orge8c9ecd2013-06-10 22:29:17 +000024#include "webrtc/modules/desktop_capture/win/cursor.h"
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +000025#include "webrtc/modules/desktop_capture/win/desktop.h"
26#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h"
27#include "webrtc/system_wrappers/interface/logging.h"
28#include "webrtc/system_wrappers/interface/scoped_ptr.h"
29#include "webrtc/system_wrappers/interface/tick_util.h"
30
31namespace webrtc {
32
33namespace {
34
35// Constants from dwmapi.h.
36const UINT DWM_EC_DISABLECOMPOSITION = 0;
37const UINT DWM_EC_ENABLECOMPOSITION = 1;
38
39typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT);
40
41const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
42
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +000043// ScreenCapturerWin captures 32bit RGB using GDI.
44//
45// ScreenCapturerWin is double-buffered as required by ScreenCapturer.
46class ScreenCapturerWin : public ScreenCapturer {
47 public:
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +000048 ScreenCapturerWin(const DesktopCaptureOptions& options);
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +000049 virtual ~ScreenCapturerWin();
50
51 // Overridden from ScreenCapturer:
52 virtual void Start(Callback* callback) OVERRIDE;
53 virtual void Capture(const DesktopRegion& region) OVERRIDE;
54 virtual void SetMouseShapeObserver(
55 MouseShapeObserver* mouse_shape_observer) OVERRIDE;
56
57 private:
58 // Make sure that the device contexts match the screen configuration.
59 void PrepareCaptureResources();
60
61 // Captures the current screen contents into the current buffer.
62 void CaptureImage();
63
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +000064 // Capture the current cursor shape.
65 void CaptureCursor();
66
67 Callback* callback_;
68 MouseShapeObserver* mouse_shape_observer_;
69
70 // A thread-safe list of invalid rectangles, and the size of the most
71 // recently captured screen.
72 ScreenCapturerHelper helper_;
73
74 // Snapshot of the last cursor bitmap we sent to the client. This is used
75 // to diff against the current cursor so we only send a cursor-change
76 // message when the shape has changed.
77 MouseCursorShape last_cursor_;
78
79 ScopedThreadDesktop desktop_;
80
81 // GDI resources used for screen capture.
82 HDC desktop_dc_;
83 HDC memory_dc_;
84
85 // Queue of the frames buffers.
86 ScreenCaptureFrameQueue queue_;
87
88 // Rectangle describing the bounds of the desktop device context.
89 DesktopRect desktop_dc_rect_;
90
91 // Class to calculate the difference between two screen bitmaps.
92 scoped_ptr<Differ> differ_;
93
94 HMODULE dwmapi_library_;
95 DwmEnableCompositionFunc composition_func_;
96
97 // Used to suppress duplicate logging of SetThreadExecutionState errors.
98 bool set_thread_execution_state_failed_;
99
100 DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWin);
101};
102
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000103ScreenCapturerWin::ScreenCapturerWin(const DesktopCaptureOptions& options)
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +0000104 : callback_(NULL),
105 mouse_shape_observer_(NULL),
106 desktop_dc_(NULL),
107 memory_dc_(NULL),
108 dwmapi_library_(NULL),
109 composition_func_(NULL),
110 set_thread_execution_state_failed_(false) {
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000111 if (options.disable_effects()) {
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +0000112 // Load dwmapi.dll dynamically since it is not available on XP.
113 if (!dwmapi_library_)
114 dwmapi_library_ = LoadLibrary(kDwmapiLibraryName);
115
116 if (dwmapi_library_) {
117 composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
118 GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
119 }
120 }
121}
122
123ScreenCapturerWin::~ScreenCapturerWin() {
124 if (desktop_dc_)
125 ReleaseDC(NULL, desktop_dc_);
126 if (memory_dc_)
127 DeleteDC(memory_dc_);
128
129 // Restore Aero.
130 if (composition_func_)
131 (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
132
133 if (dwmapi_library_)
134 FreeLibrary(dwmapi_library_);
135}
136
137void ScreenCapturerWin::Capture(const DesktopRegion& region) {
138 TickTime capture_start_time = TickTime::Now();
139
140 queue_.MoveToNextFrame();
141
142 // Request that the system not power-down the system, or the display hardware.
143 if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
144 if (!set_thread_execution_state_failed_) {
145 set_thread_execution_state_failed_ = true;
146 LOG_F(LS_WARNING) << "Failed to make system & display power assertion: "
147 << GetLastError();
148 }
149 }
150
151 // Make sure the GDI capture resources are up-to-date.
152 PrepareCaptureResources();
153
154 // Copy screen bits to the current buffer.
155 CaptureImage();
156
157 const DesktopFrame* current_frame = queue_.current_frame();
158 const DesktopFrame* last_frame = queue_.previous_frame();
159 if (last_frame) {
160 // Make sure the differencer is set up correctly for these previous and
161 // current screens.
162 if (!differ_.get() ||
163 (differ_->width() != current_frame->size().width()) ||
164 (differ_->height() != current_frame->size().height()) ||
165 (differ_->bytes_per_row() != current_frame->stride())) {
166 differ_.reset(new Differ(current_frame->size().width(),
167 current_frame->size().height(),
168 DesktopFrame::kBytesPerPixel,
169 current_frame->stride()));
170 }
171
172 // Calculate difference between the two last captured frames.
173 DesktopRegion region;
174 differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(),
175 &region);
176 helper_.InvalidateRegion(region);
177 } else {
178 // No previous frame is available. Invalidate the whole screen.
179 helper_.InvalidateScreen(current_frame->size());
180 }
181
182 helper_.set_size_most_recent(current_frame->size());
183
184 // Emit the current frame.
185 DesktopFrame* frame = queue_.current_frame()->Share();
186 frame->set_dpi(DesktopVector(
187 GetDeviceCaps(desktop_dc_, LOGPIXELSX),
188 GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
189 frame->mutable_updated_region()->Clear();
190 helper_.TakeInvalidRegion(frame->mutable_updated_region());
191 frame->set_capture_time_ms(
192 (TickTime::Now() - capture_start_time).Milliseconds());
193 callback_->OnCaptureCompleted(frame);
194
195 // Check for cursor shape update.
196 CaptureCursor();
197}
198
199void ScreenCapturerWin::SetMouseShapeObserver(
200 MouseShapeObserver* mouse_shape_observer) {
201 assert(!mouse_shape_observer_);
202 assert(mouse_shape_observer);
203
204 mouse_shape_observer_ = mouse_shape_observer;
205}
206
207void ScreenCapturerWin::Start(Callback* callback) {
208 assert(!callback_);
209 assert(callback);
210
211 callback_ = callback;
212
213 // Vote to disable Aero composited desktop effects while capturing. Windows
214 // will restore Aero automatically if the process exits. This has no effect
215 // under Windows 8 or higher. See crbug.com/124018.
216 if (composition_func_)
217 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
218}
219
220void ScreenCapturerWin::PrepareCaptureResources() {
221 // Switch to the desktop receiving user input if different from the current
222 // one.
223 scoped_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
224 if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
225 // Release GDI resources otherwise SetThreadDesktop will fail.
226 if (desktop_dc_) {
227 ReleaseDC(NULL, desktop_dc_);
228 desktop_dc_ = NULL;
229 }
230
231 if (memory_dc_) {
232 DeleteDC(memory_dc_);
233 memory_dc_ = NULL;
234 }
235
236 // If SetThreadDesktop() fails, the thread is still assigned a desktop.
237 // So we can continue capture screen bits, just from the wrong desktop.
238 desktop_.SetThreadDesktop(input_desktop.release());
239
240 // Re-assert our vote to disable Aero.
241 // See crbug.com/124018 and crbug.com/129906.
242 if (composition_func_ != NULL) {
243 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
244 }
245 }
246
247 // If the display bounds have changed then recreate GDI resources.
248 // TODO(wez): Also check for pixel format changes.
249 DesktopRect screen_rect(DesktopRect::MakeXYWH(
250 GetSystemMetrics(SM_XVIRTUALSCREEN),
251 GetSystemMetrics(SM_YVIRTUALSCREEN),
252 GetSystemMetrics(SM_CXVIRTUALSCREEN),
253 GetSystemMetrics(SM_CYVIRTUALSCREEN)));
254 if (!screen_rect.equals(desktop_dc_rect_)) {
255 if (desktop_dc_) {
256 ReleaseDC(NULL, desktop_dc_);
257 desktop_dc_ = NULL;
258 }
259 if (memory_dc_) {
260 DeleteDC(memory_dc_);
261 memory_dc_ = NULL;
262 }
263 desktop_dc_rect_ = DesktopRect();
264 }
265
266 if (desktop_dc_ == NULL) {
267 assert(memory_dc_ == NULL);
268
269 // Create GDI device contexts to capture from the desktop into memory.
270 desktop_dc_ = GetDC(NULL);
271 if (!desktop_dc_)
272 abort();
273 memory_dc_ = CreateCompatibleDC(desktop_dc_);
274 if (!memory_dc_)
275 abort();
276 desktop_dc_rect_ = screen_rect;
277
278 // Make sure the frame buffers will be reallocated.
279 queue_.Reset();
280
281 helper_.ClearInvalidRegion();
282 }
283}
284
285void ScreenCapturerWin::CaptureImage() {
286 // If the current buffer is from an older generation then allocate a new one.
287 // Note that we can't reallocate other buffers at this point, since the caller
288 // may still be reading from them.
289 if (!queue_.current_frame()) {
290 assert(desktop_dc_ != NULL);
291 assert(memory_dc_ != NULL);
292
293 DesktopSize size = DesktopSize(
294 desktop_dc_rect_.width(), desktop_dc_rect_.height());
295
296 size_t buffer_size = size.width() * size.height() *
297 DesktopFrame::kBytesPerPixel;
298 SharedMemory* shared_memory =
299 callback_->CreateSharedMemory(buffer_size);
300 scoped_ptr<DesktopFrameWin> buffer(
301 DesktopFrameWin::Create(size, shared_memory, desktop_dc_));
302 queue_.ReplaceCurrentFrame(buffer.release());
303 }
304
305 // Select the target bitmap into the memory dc and copy the rect from desktop
306 // to memory.
307 DesktopFrameWin* current = static_cast<DesktopFrameWin*>(
308 queue_.current_frame()->GetUnderlyingFrame());
309 HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
310 if (previous_object != NULL) {
311 BitBlt(memory_dc_,
312 0, 0, desktop_dc_rect_.width(), desktop_dc_rect_.height(),
313 desktop_dc_,
314 desktop_dc_rect_.left(), desktop_dc_rect_.top(),
315 SRCCOPY | CAPTUREBLT);
316
317 // Select back the previously selected object to that the device contect
318 // could be destroyed independently of the bitmap if needed.
319 SelectObject(memory_dc_, previous_object);
320 }
321}
322
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +0000323void ScreenCapturerWin::CaptureCursor() {
324 CURSORINFO cursor_info;
325 cursor_info.cbSize = sizeof(CURSORINFO);
326 if (!GetCursorInfo(&cursor_info)) {
alexeypa@chromium.orge8c9ecd2013-06-10 22:29:17 +0000327 LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError();
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +0000328 return;
329 }
330
alexeypa@chromium.orge8c9ecd2013-06-10 22:29:17 +0000331 // Note that |cursor_info.hCursor| does not need to be freed.
sergeyu@chromium.org2873c4c2013-10-17 19:47:18 +0000332 scoped_ptr<MouseCursor> cursor_image(
333 CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor));
334 if (!cursor_image.get())
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +0000335 return;
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +0000336
sergeyu@chromium.org2873c4c2013-10-17 19:47:18 +0000337 scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape);
338 cursor->hotspot = cursor_image->hotspot();
339 cursor->size = cursor_image->image().size();
340 cursor->data.assign(
341 cursor_image->image().data(),
342 cursor_image->image().data() +
343 cursor_image->image().stride() * DesktopFrame::kBytesPerPixel);
344
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +0000345 // Compare the current cursor with the last one we sent to the client. If
346 // they're the same, then don't bother sending the cursor again.
347 if (last_cursor_.size.equals(cursor->size) &&
348 last_cursor_.hotspot.equals(cursor->hotspot) &&
349 last_cursor_.data == cursor->data) {
350 return;
351 }
352
alexeypa@chromium.orge8c9ecd2013-06-10 22:29:17 +0000353 LOG(LS_VERBOSE) << "Sending updated cursor: " << cursor->size.width() << "x"
354 << cursor->size.height();
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +0000355
356 // Record the last cursor image that we sent to the client.
357 last_cursor_ = *cursor;
358
359 if (mouse_shape_observer_)
360 mouse_shape_observer_->OnCursorShapeChanged(cursor.release());
361}
362
363} // namespace
364
365// static
sergeyu@chromium.org91685dc2013-10-12 22:40:05 +0000366ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) {
367 return new ScreenCapturerWin(options);
sergeyu@chromium.org6c82a7e2013-06-04 18:51:23 +0000368}
369
370} // namespace webrtc