2 * Copyright (c) 2014 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/win/screen_capturer_win_gdi.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/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/interface/logging.h"
25 #include "webrtc/system_wrappers/interface/tick_util.h"
31 // Constants from dwmapi.h.
32 const UINT DWM_EC_DISABLECOMPOSITION = 0;
33 const UINT DWM_EC_ENABLECOMPOSITION = 1;
35 const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
39 ScreenCapturerWinGdi::ScreenCapturerWinGdi(const DesktopCaptureOptions& options)
41 mouse_shape_observer_(NULL),
42 current_screen_id_(kFullDesktopScreenId),
45 dwmapi_library_(NULL),
46 composition_func_(NULL),
47 set_thread_execution_state_failed_(false) {
48 if (options.disable_effects()) {
49 // Load dwmapi.dll dynamically since it is not available on XP.
51 dwmapi_library_ = LoadLibrary(kDwmapiLibraryName);
53 if (dwmapi_library_) {
54 composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
55 GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
60 ScreenCapturerWinGdi::~ScreenCapturerWinGdi() {
62 ReleaseDC(NULL, desktop_dc_);
67 if (composition_func_)
68 (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
71 FreeLibrary(dwmapi_library_);
74 void ScreenCapturerWinGdi::Capture(const DesktopRegion& region) {
75 TickTime capture_start_time = TickTime::Now();
77 queue_.MoveToNextFrame();
79 // Request that the system not power-down the system, or the display hardware.
80 if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) {
81 if (!set_thread_execution_state_failed_) {
82 set_thread_execution_state_failed_ = true;
83 LOG_F(LS_WARNING) << "Failed to make system & display power assertion: "
88 // Make sure the GDI capture resources are up-to-date.
89 PrepareCaptureResources();
91 if (!CaptureImage()) {
92 callback_->OnCaptureCompleted(NULL);
96 const DesktopFrame* current_frame = queue_.current_frame();
97 const DesktopFrame* last_frame = queue_.previous_frame();
98 if (last_frame && last_frame->size().equals(current_frame->size())) {
99 // Make sure the differencer is set up correctly for these previous and
101 if (!differ_.get() ||
102 (differ_->width() != current_frame->size().width()) ||
103 (differ_->height() != current_frame->size().height()) ||
104 (differ_->bytes_per_row() != current_frame->stride())) {
105 differ_.reset(new Differ(current_frame->size().width(),
106 current_frame->size().height(),
107 DesktopFrame::kBytesPerPixel,
108 current_frame->stride()));
111 // Calculate difference between the two last captured frames.
112 DesktopRegion region;
113 differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(),
115 helper_.InvalidateRegion(region);
117 // No previous frame is available, or the screen is resized. Invalidate the
119 helper_.InvalidateScreen(current_frame->size());
122 helper_.set_size_most_recent(current_frame->size());
124 // Emit the current frame.
125 DesktopFrame* frame = queue_.current_frame()->Share();
126 frame->set_dpi(DesktopVector(
127 GetDeviceCaps(desktop_dc_, LOGPIXELSX),
128 GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
129 frame->mutable_updated_region()->Clear();
130 helper_.TakeInvalidRegion(frame->mutable_updated_region());
131 frame->set_capture_time_ms(
132 (TickTime::Now() - capture_start_time).Milliseconds());
133 callback_->OnCaptureCompleted(frame);
135 // Check for cursor shape update.
136 if (mouse_shape_observer_)
140 void ScreenCapturerWinGdi::SetMouseShapeObserver(
141 MouseShapeObserver* mouse_shape_observer) {
142 assert(!mouse_shape_observer_);
143 assert(mouse_shape_observer);
145 mouse_shape_observer_ = mouse_shape_observer;
148 bool ScreenCapturerWinGdi::GetScreenList(ScreenList* screens) {
149 return webrtc::GetScreenList(screens);
152 bool ScreenCapturerWinGdi::SelectScreen(ScreenId id) {
153 bool valid = IsScreenValid(id, ¤t_device_key_);
155 current_screen_id_ = id;
159 void ScreenCapturerWinGdi::Start(Callback* callback) {
163 callback_ = callback;
165 // Vote to disable Aero composited desktop effects while capturing. Windows
166 // will restore Aero automatically if the process exits. This has no effect
167 // under Windows 8 or higher. See crbug.com/124018.
168 if (composition_func_)
169 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
172 void ScreenCapturerWinGdi::PrepareCaptureResources() {
173 // Switch to the desktop receiving user input if different from the current
175 scoped_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
176 if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) {
177 // Release GDI resources otherwise SetThreadDesktop will fail.
179 ReleaseDC(NULL, desktop_dc_);
184 DeleteDC(memory_dc_);
188 // If SetThreadDesktop() fails, the thread is still assigned a desktop.
189 // So we can continue capture screen bits, just from the wrong desktop.
190 desktop_.SetThreadDesktop(input_desktop.release());
192 // Re-assert our vote to disable Aero.
193 // See crbug.com/124018 and crbug.com/129906.
194 if (composition_func_ != NULL) {
195 (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
199 // If the display bounds have changed then recreate GDI resources.
200 // TODO(wez): Also check for pixel format changes.
201 DesktopRect screen_rect(DesktopRect::MakeXYWH(
202 GetSystemMetrics(SM_XVIRTUALSCREEN),
203 GetSystemMetrics(SM_YVIRTUALSCREEN),
204 GetSystemMetrics(SM_CXVIRTUALSCREEN),
205 GetSystemMetrics(SM_CYVIRTUALSCREEN)));
206 if (!screen_rect.equals(desktop_dc_rect_)) {
208 ReleaseDC(NULL, desktop_dc_);
212 DeleteDC(memory_dc_);
215 desktop_dc_rect_ = DesktopRect();
218 if (desktop_dc_ == NULL) {
219 assert(memory_dc_ == NULL);
221 // Create GDI device contexts to capture from the desktop into memory.
222 desktop_dc_ = GetDC(NULL);
225 memory_dc_ = CreateCompatibleDC(desktop_dc_);
229 desktop_dc_rect_ = screen_rect;
231 // Make sure the frame buffers will be reallocated.
234 helper_.ClearInvalidRegion();
238 bool ScreenCapturerWinGdi::CaptureImage() {
239 DesktopRect screen_rect =
240 GetScreenRect(current_screen_id_, current_device_key_);
241 if (screen_rect.is_empty())
244 DesktopSize size = screen_rect.size();
245 // If the current buffer is from an older generation then allocate a new one.
246 // Note that we can't reallocate other buffers at this point, since the caller
247 // may still be reading from them.
248 if (!queue_.current_frame() ||
249 !queue_.current_frame()->size().equals(screen_rect.size())) {
250 assert(desktop_dc_ != NULL);
251 assert(memory_dc_ != NULL);
253 size_t buffer_size = size.width() * size.height() *
254 DesktopFrame::kBytesPerPixel;
255 SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size);
257 scoped_ptr<DesktopFrame> buffer;
259 DesktopFrameWin::Create(size, shared_memory, desktop_dc_));
260 queue_.ReplaceCurrentFrame(buffer.release());
263 // Select the target bitmap into the memory dc and copy the rect from desktop
265 DesktopFrameWin* current = static_cast<DesktopFrameWin*>(
266 queue_.current_frame()->GetUnderlyingFrame());
267 HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
268 if (previous_object != NULL) {
270 0, 0, screen_rect.width(), screen_rect.height(),
272 screen_rect.left(), screen_rect.top(),
273 SRCCOPY | CAPTUREBLT);
275 // Select back the previously selected object to that the device contect
276 // could be destroyed independently of the bitmap if needed.
277 SelectObject(memory_dc_, previous_object);
282 void ScreenCapturerWinGdi::CaptureCursor() {
283 assert(mouse_shape_observer_);
285 CURSORINFO cursor_info;
286 cursor_info.cbSize = sizeof(CURSORINFO);
287 if (!GetCursorInfo(&cursor_info)) {
288 LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError();
292 // Note that |cursor_info.hCursor| does not need to be freed.
293 scoped_ptr<MouseCursor> cursor_image(
294 CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor));
295 if (!cursor_image.get())
298 scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape);
299 cursor->hotspot = cursor_image->hotspot();
300 cursor->size = cursor_image->image()->size();
301 uint8_t* current_row = cursor_image->image()->data();
302 for (int y = 0; y < cursor_image->image()->size().height(); ++y) {
303 cursor->data.append(current_row,
304 current_row + cursor_image->image()->size().width() *
305 DesktopFrame::kBytesPerPixel);
306 current_row += cursor_image->image()->stride();
309 // Compare the current cursor with the last one we sent to the client. If
310 // they're the same, then don't bother sending the cursor again.
311 if (last_cursor_.size.equals(cursor->size) &&
312 last_cursor_.hotspot.equals(cursor->hotspot) &&
313 last_cursor_.data == cursor->data) {
317 LOG(LS_VERBOSE) << "Sending updated cursor: " << cursor->size.width() << "x"
318 << cursor->size.height();
320 // Record the last cursor image that we sent to the client.
321 last_cursor_ = *cursor;
323 mouse_shape_observer_->OnCursorShapeChanged(cursor.release());
326 } // namespace webrtc