2 * Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
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.
11 #include "webrtc/modules/desktop_capture/screen_capturer.h"
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/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"
24 #include "webrtc/modules/desktop_capture/win/cursor.h"
25 #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"
35 // Constants from dwmapi.h.
36 const UINT DWM_EC_DISABLECOMPOSITION = 0;
37 const UINT DWM_EC_ENABLECOMPOSITION = 1;
39 typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT);
41 const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
43 // ScreenCapturerWin captures 32bit RGB using GDI.
45 // ScreenCapturerWin is double-buffered as required by ScreenCapturer.
46 class ScreenCapturerWin : public ScreenCapturer {
48 ScreenCapturerWin(const DesktopCaptureOptions& options);
49 virtual ~ScreenCapturerWin();
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 virtual bool GetScreenList(ScreenList* screens) OVERRIDE;
57 virtual bool SelectScreen(ScreenId id) OVERRIDE;
60 // Make sure that the device contexts match the screen configuration.
61 void PrepareCaptureResources();
63 // Captures the current screen contents into the current buffer. Returns true
67 // Capture the current cursor shape.
70 // Get the rect of the currently selected screen. If the screen is disabled
71 // or disconnected, or any error happens, an empty rect is returned.
72 DesktopRect GetScreenRect();
75 MouseShapeObserver* mouse_shape_observer_;
76 ScreenId current_screen_id_;
77 std::wstring current_device_key_;
79 // A thread-safe list of invalid rectangles, and the size of the most
80 // recently captured screen.
81 ScreenCapturerHelper helper_;
83 // Snapshot of the last cursor bitmap we sent to the client. This is used
84 // to diff against the current cursor so we only send a cursor-change
85 // message when the shape has changed.
86 MouseCursorShape last_cursor_;
88 ScopedThreadDesktop desktop_;
90 // GDI resources used for screen capture.
94 // Queue of the frames buffers.
95 ScreenCaptureFrameQueue queue_;
97 // Rectangle describing the bounds of the desktop device context.
98 DesktopRect desktop_dc_rect_;
100 // Class to calculate the difference between two screen bitmaps.
101 scoped_ptr<Differ> differ_;
103 HMODULE dwmapi_library_;
104 DwmEnableCompositionFunc composition_func_;
106 // Used to suppress duplicate logging of SetThreadExecutionState errors.
107 bool set_thread_execution_state_failed_;
109 DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWin);
112 ScreenCapturerWin::ScreenCapturerWin(const DesktopCaptureOptions& options)
114 mouse_shape_observer_(NULL),
115 current_screen_id_(kFullDesktopScreenId),
118 dwmapi_library_(NULL),
119 composition_func_(NULL),
120 set_thread_execution_state_failed_(false) {
121 if (options.disable_effects()) {
122 // Load dwmapi.dll dynamically since it is not available on XP.
123 if (!dwmapi_library_)
124 dwmapi_library_ = LoadLibrary(kDwmapiLibraryName);
126 if (dwmapi_library_) {
127 composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
128 GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
133 ScreenCapturerWin::~ScreenCapturerWin() {
135 ReleaseDC(NULL, desktop_dc_);
137 DeleteDC(memory_dc_);
140 if (composition_func_)
141 (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
144 FreeLibrary(dwmapi_library_);
147 void ScreenCapturerWin::Capture(const DesktopRegion& region) {
148 TickTime capture_start_time = TickTime::Now();
150 queue_.MoveToNextFrame();
152 // Request that the system not power-down the system, or the display hardware.
153 if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
154 if (!set_thread_execution_state_failed_) {
155 set_thread_execution_state_failed_ = true;
156 LOG_F(LS_WARNING) << "Failed to make system & display power assertion: "
161 // Make sure the GDI capture resources are up-to-date.
162 PrepareCaptureResources();
164 // Copy screen bits to the current buffer.
165 if (!CaptureImage()) {
166 callback_->OnCaptureCompleted(NULL);
170 const DesktopFrame* current_frame = queue_.current_frame();
171 const DesktopFrame* last_frame = queue_.previous_frame();
172 if (last_frame && last_frame->size().equals(current_frame->size())) {
173 // Make sure the differencer is set up correctly for these previous and
175 if (!differ_.get() ||
176 (differ_->width() != current_frame->size().width()) ||
177 (differ_->height() != current_frame->size().height()) ||
178 (differ_->bytes_per_row() != current_frame->stride())) {
179 differ_.reset(new Differ(current_frame->size().width(),
180 current_frame->size().height(),
181 DesktopFrame::kBytesPerPixel,
182 current_frame->stride()));
185 // Calculate difference between the two last captured frames.
186 DesktopRegion region;
187 differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(),
189 helper_.InvalidateRegion(region);
191 // No previous frame is available. Invalidate the whole screen.
192 helper_.InvalidateScreen(current_frame->size());
195 helper_.set_size_most_recent(current_frame->size());
197 // Emit the current frame.
198 DesktopFrame* frame = queue_.current_frame()->Share();
199 frame->set_dpi(DesktopVector(
200 GetDeviceCaps(desktop_dc_, LOGPIXELSX),
201 GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
202 frame->mutable_updated_region()->Clear();
203 helper_.TakeInvalidRegion(frame->mutable_updated_region());
204 frame->set_capture_time_ms(
205 (TickTime::Now() - capture_start_time).Milliseconds());
206 callback_->OnCaptureCompleted(frame);
208 // Check for cursor shape update.
212 void ScreenCapturerWin::SetMouseShapeObserver(
213 MouseShapeObserver* mouse_shape_observer) {
214 assert(!mouse_shape_observer_);
215 assert(mouse_shape_observer);
217 mouse_shape_observer_ = mouse_shape_observer;
220 bool ScreenCapturerWin::GetScreenList(ScreenList* screens) {
221 assert(screens->size() == 0);
222 BOOL enum_result = TRUE;
223 for (int device_index = 0; ; ++device_index) {
224 DISPLAY_DEVICE device;
225 device.cb = sizeof(device);
226 enum_result = EnumDisplayDevices(NULL, device_index, &device, 0);
227 // |enum_result| is 0 if we have enumerated all devices.
231 // We only care about active displays.
232 if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE))
235 screen.id = device_index;
236 screens->push_back(screen);
241 bool ScreenCapturerWin::SelectScreen(ScreenId id) {
242 if (id == kFullDesktopScreenId) {
243 current_screen_id_ = id;
246 DISPLAY_DEVICE device;
247 device.cb = sizeof(device);
248 BOOL enum_result = EnumDisplayDevices(NULL, id, &device, 0);
252 current_device_key_ = device.DeviceKey;
253 current_screen_id_ = id;
257 void ScreenCapturerWin::Start(Callback* callback) {
261 callback_ = callback;
263 // Vote to disable Aero composited desktop effects while capturing. Windows
264 // will restore Aero automatically if the process exits. This has no effect
265 // under Windows 8 or higher. See crbug.com/124018.
266 if (composition_func_)
267 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
270 void ScreenCapturerWin::PrepareCaptureResources() {
271 // Switch to the desktop receiving user input if different from the current
273 scoped_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
274 if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
275 // Release GDI resources otherwise SetThreadDesktop will fail.
277 ReleaseDC(NULL, desktop_dc_);
282 DeleteDC(memory_dc_);
286 // If SetThreadDesktop() fails, the thread is still assigned a desktop.
287 // So we can continue capture screen bits, just from the wrong desktop.
288 desktop_.SetThreadDesktop(input_desktop.release());
290 // Re-assert our vote to disable Aero.
291 // See crbug.com/124018 and crbug.com/129906.
292 if (composition_func_ != NULL) {
293 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
297 // If the display bounds have changed then recreate GDI resources.
298 // TODO(wez): Also check for pixel format changes.
299 DesktopRect screen_rect(DesktopRect::MakeXYWH(
300 GetSystemMetrics(SM_XVIRTUALSCREEN),
301 GetSystemMetrics(SM_YVIRTUALSCREEN),
302 GetSystemMetrics(SM_CXVIRTUALSCREEN),
303 GetSystemMetrics(SM_CYVIRTUALSCREEN)));
304 if (!screen_rect.equals(desktop_dc_rect_)) {
306 ReleaseDC(NULL, desktop_dc_);
310 DeleteDC(memory_dc_);
313 desktop_dc_rect_ = DesktopRect();
316 if (desktop_dc_ == NULL) {
317 assert(memory_dc_ == NULL);
319 // Create GDI device contexts to capture from the desktop into memory.
320 desktop_dc_ = GetDC(NULL);
323 memory_dc_ = CreateCompatibleDC(desktop_dc_);
326 desktop_dc_rect_ = screen_rect;
328 // Make sure the frame buffers will be reallocated.
331 helper_.ClearInvalidRegion();
335 bool ScreenCapturerWin::CaptureImage() {
336 DesktopRect screen_rect = GetScreenRect();
337 if (screen_rect.is_empty())
339 DesktopSize size = screen_rect.size();
340 // If the current buffer is from an older generation then allocate a new one.
341 // Note that we can't reallocate other buffers at this point, since the caller
342 // may still be reading from them.
343 if (!queue_.current_frame() ||
344 !queue_.current_frame()->size().equals(size)) {
345 assert(desktop_dc_ != NULL);
346 assert(memory_dc_ != NULL);
348 size_t buffer_size = size.width() * size.height() *
349 DesktopFrame::kBytesPerPixel;
350 SharedMemory* shared_memory =
351 callback_->CreateSharedMemory(buffer_size);
352 scoped_ptr<DesktopFrameWin> buffer(
353 DesktopFrameWin::Create(size, shared_memory, desktop_dc_));
354 queue_.ReplaceCurrentFrame(buffer.release());
357 // Select the target bitmap into the memory dc and copy the rect from desktop
359 DesktopFrameWin* current = static_cast<DesktopFrameWin*>(
360 queue_.current_frame()->GetUnderlyingFrame());
361 HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
362 if (previous_object != NULL) {
364 0, 0, screen_rect.width(), screen_rect.height(),
366 screen_rect.left(), screen_rect.top(),
367 SRCCOPY | CAPTUREBLT);
369 // Select back the previously selected object to that the device contect
370 // could be destroyed independently of the bitmap if needed.
371 SelectObject(memory_dc_, previous_object);
376 void ScreenCapturerWin::CaptureCursor() {
377 CURSORINFO cursor_info;
378 cursor_info.cbSize = sizeof(CURSORINFO);
379 if (!GetCursorInfo(&cursor_info)) {
380 LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError();
384 // Note that |cursor_info.hCursor| does not need to be freed.
385 scoped_ptr<MouseCursor> cursor_image(
386 CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor));
387 if (!cursor_image.get())
390 scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape);
391 cursor->hotspot = cursor_image->hotspot();
392 cursor->size = cursor_image->image()->size();
393 uint8_t* current_row = cursor_image->image()->data();
394 for (int y = 0; y < cursor_image->image()->size().height(); ++y) {
395 cursor->data.append(current_row,
396 current_row + cursor_image->image()->size().width() *
397 DesktopFrame::kBytesPerPixel);
398 current_row += cursor_image->image()->stride();
401 // Compare the current cursor with the last one we sent to the client. If
402 // they're the same, then don't bother sending the cursor again.
403 if (last_cursor_.size.equals(cursor->size) &&
404 last_cursor_.hotspot.equals(cursor->hotspot) &&
405 last_cursor_.data == cursor->data) {
409 LOG(LS_VERBOSE) << "Sending updated cursor: " << cursor->size.width() << "x"
410 << cursor->size.height();
412 // Record the last cursor image that we sent to the client.
413 last_cursor_ = *cursor;
415 if (mouse_shape_observer_)
416 mouse_shape_observer_->OnCursorShapeChanged(cursor.release());
419 DesktopRect ScreenCapturerWin::GetScreenRect() {
420 DesktopRect rect = desktop_dc_rect_;
421 if (current_screen_id_ == kFullDesktopScreenId)
424 DISPLAY_DEVICE device;
425 device.cb = sizeof(device);
426 BOOL result = EnumDisplayDevices(NULL, current_screen_id_, &device, 0);
428 return DesktopRect();
430 // Verifies the device index still maps to the same display device. DeviceKey
431 // is documented as reserved, but it actually contains the registry key for
432 // the device and is unique for each monitor, while DeviceID is not.
433 if (current_device_key_ != device.DeviceKey)
434 return DesktopRect();
437 device_mode.dmSize = sizeof(device_mode);
438 device_mode.dmDriverExtra = 0;
439 result = EnumDisplaySettingsEx(
440 device.DeviceName, ENUM_CURRENT_SETTINGS, &device_mode, 0);
442 return DesktopRect();
444 rect = DesktopRect::MakeXYWH(
445 rect.left() + device_mode.dmPosition.x,
446 rect.top() + device_mode.dmPosition.y,
447 device_mode.dmPelsWidth,
448 device_mode.dmPelsHeight);
454 ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) {
455 return new ScreenCapturerWin(options);
458 } // namespace webrtc