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/interface/logging.h" 25 #include "webrtc/system_wrappers/interface/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 39 ScreenCapturerWinGdi::ScreenCapturerWinGdi(const DesktopCaptureOptions& options) 40 : callback_(NULL), 41 mouse_shape_observer_(NULL), 42 current_screen_id_(kFullDesktopScreenId), 43 desktop_dc_(NULL), 44 memory_dc_(NULL), 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. 50 if (!dwmapi_library_) 51 dwmapi_library_ = LoadLibrary(kDwmapiLibraryName); 52 53 if (dwmapi_library_) { 54 composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>( 55 GetProcAddress(dwmapi_library_, "DwmEnableComposition")); 56 } 57 } 58 } 59 60 ScreenCapturerWinGdi::~ScreenCapturerWinGdi() { 61 if (desktop_dc_) 62 ReleaseDC(NULL, desktop_dc_); 63 if (memory_dc_) 64 DeleteDC(memory_dc_); 65 66 // Restore Aero. 67 if (composition_func_) 68 (*composition_func_)(DWM_EC_ENABLECOMPOSITION); 69 70 if (dwmapi_library_) 71 FreeLibrary(dwmapi_library_); 72 } 73 74 void ScreenCapturerWinGdi::Capture(const DesktopRegion& region) { 75 TickTime capture_start_time = TickTime::Now(); 76 77 queue_.MoveToNextFrame(); 78 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: " 84 << GetLastError(); 85 } 86 } 87 88 // Make sure the GDI capture resources are up-to-date. 89 PrepareCaptureResources(); 90 91 if (!CaptureImage()) { 92 callback_->OnCaptureCompleted(NULL); 93 return; 94 } 95 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 100 // current screens. 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())); 109 } 110 111 // Calculate difference between the two last captured frames. 112 DesktopRegion region; 113 differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(), 114 ®ion); 115 helper_.InvalidateRegion(region); 116 } else { 117 // No previous frame is available, or the screen is resized. Invalidate the 118 // whole screen. 119 helper_.InvalidateScreen(current_frame->size()); 120 } 121 122 helper_.set_size_most_recent(current_frame->size()); 123 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); 134 135 // Check for cursor shape update. 136 CaptureCursor(); 137 } 138 139 void ScreenCapturerWinGdi::SetMouseShapeObserver( 140 MouseShapeObserver* mouse_shape_observer) { 141 assert(!mouse_shape_observer_); 142 assert(mouse_shape_observer); 143 144 mouse_shape_observer_ = mouse_shape_observer; 145 } 146 147 bool ScreenCapturerWinGdi::GetScreenList(ScreenList* screens) { 148 return webrtc::GetScreenList(screens); 149 } 150 151 bool ScreenCapturerWinGdi::SelectScreen(ScreenId id) { 152 bool valid = IsScreenValid(id, ¤t_device_key_); 153 if (valid) 154 current_screen_id_ = id; 155 return valid; 156 } 157 158 void ScreenCapturerWinGdi::Start(Callback* callback) { 159 assert(!callback_); 160 assert(callback); 161 162 callback_ = callback; 163 164 // Vote to disable Aero composited desktop effects while capturing. Windows 165 // will restore Aero automatically if the process exits. This has no effect 166 // under Windows 8 or higher. See crbug.com/124018. 167 if (composition_func_) 168 (*composition_func_)(DWM_EC_DISABLECOMPOSITION); 169 } 170 171 void ScreenCapturerWinGdi::PrepareCaptureResources() { 172 // Switch to the desktop receiving user input if different from the current 173 // one. 174 scoped_ptr<Desktop> input_desktop(Desktop::GetInputDesktop()); 175 if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { 176 // Release GDI resources otherwise SetThreadDesktop will fail. 177 if (desktop_dc_) { 178 ReleaseDC(NULL, desktop_dc_); 179 desktop_dc_ = NULL; 180 } 181 182 if (memory_dc_) { 183 DeleteDC(memory_dc_); 184 memory_dc_ = NULL; 185 } 186 187 // If SetThreadDesktop() fails, the thread is still assigned a desktop. 188 // So we can continue capture screen bits, just from the wrong desktop. 189 desktop_.SetThreadDesktop(input_desktop.release()); 190 191 // Re-assert our vote to disable Aero. 192 // See crbug.com/124018 and crbug.com/129906. 193 if (composition_func_ != NULL) { 194 (*composition_func_)(DWM_EC_DISABLECOMPOSITION); 195 } 196 } 197 198 // If the display bounds have changed then recreate GDI resources. 199 // TODO(wez): Also check for pixel format changes. 200 DesktopRect screen_rect(DesktopRect::MakeXYWH( 201 GetSystemMetrics(SM_XVIRTUALSCREEN), 202 GetSystemMetrics(SM_YVIRTUALSCREEN), 203 GetSystemMetrics(SM_CXVIRTUALSCREEN), 204 GetSystemMetrics(SM_CYVIRTUALSCREEN))); 205 if (!screen_rect.equals(desktop_dc_rect_)) { 206 if (desktop_dc_) { 207 ReleaseDC(NULL, desktop_dc_); 208 desktop_dc_ = NULL; 209 } 210 if (memory_dc_) { 211 DeleteDC(memory_dc_); 212 memory_dc_ = NULL; 213 } 214 desktop_dc_rect_ = DesktopRect(); 215 } 216 217 if (desktop_dc_ == NULL) { 218 assert(memory_dc_ == NULL); 219 220 // Create GDI device contexts to capture from the desktop into memory. 221 desktop_dc_ = GetDC(NULL); 222 if (!desktop_dc_) 223 abort(); 224 memory_dc_ = CreateCompatibleDC(desktop_dc_); 225 if (!memory_dc_) 226 abort(); 227 228 desktop_dc_rect_ = screen_rect; 229 230 // Make sure the frame buffers will be reallocated. 231 queue_.Reset(); 232 233 helper_.ClearInvalidRegion(); 234 } 235 } 236 237 bool ScreenCapturerWinGdi::CaptureImage() { 238 DesktopRect screen_rect = 239 GetScreenRect(current_screen_id_, current_device_key_); 240 if (screen_rect.is_empty()) 241 return false; 242 243 DesktopSize size = screen_rect.size(); 244 // If the current buffer is from an older generation then allocate a new one. 245 // Note that we can't reallocate other buffers at this point, since the caller 246 // may still be reading from them. 247 if (!queue_.current_frame() || 248 !queue_.current_frame()->size().equals(screen_rect.size())) { 249 assert(desktop_dc_ != NULL); 250 assert(memory_dc_ != NULL); 251 252 size_t buffer_size = size.width() * size.height() * 253 DesktopFrame::kBytesPerPixel; 254 SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size); 255 256 scoped_ptr<DesktopFrame> buffer; 257 buffer.reset( 258 DesktopFrameWin::Create(size, shared_memory, desktop_dc_)); 259 queue_.ReplaceCurrentFrame(buffer.release()); 260 } 261 262 // Select the target bitmap into the memory dc and copy the rect from desktop 263 // to memory. 264 DesktopFrameWin* current = static_cast<DesktopFrameWin*>( 265 queue_.current_frame()->GetUnderlyingFrame()); 266 HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap()); 267 if (previous_object != NULL) { 268 BitBlt(memory_dc_, 269 0, 0, screen_rect.width(), screen_rect.height(), 270 desktop_dc_, 271 screen_rect.left(), screen_rect.top(), 272 SRCCOPY | CAPTUREBLT); 273 274 // Select back the previously selected object to that the device contect 275 // could be destroyed independently of the bitmap if needed. 276 SelectObject(memory_dc_, previous_object); 277 } 278 return true; 279 } 280 281 void ScreenCapturerWinGdi::CaptureCursor() { 282 CURSORINFO cursor_info; 283 cursor_info.cbSize = sizeof(CURSORINFO); 284 if (!GetCursorInfo(&cursor_info)) { 285 LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError(); 286 return; 287 } 288 289 // Note that |cursor_info.hCursor| does not need to be freed. 290 scoped_ptr<MouseCursor> cursor_image( 291 CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor)); 292 if (!cursor_image.get()) 293 return; 294 295 scoped_ptr<MouseCursorShape> cursor(new MouseCursorShape); 296 cursor->hotspot = cursor_image->hotspot(); 297 cursor->size = cursor_image->image()->size(); 298 uint8_t* current_row = cursor_image->image()->data(); 299 for (int y = 0; y < cursor_image->image()->size().height(); ++y) { 300 cursor->data.append(current_row, 301 current_row + cursor_image->image()->size().width() * 302 DesktopFrame::kBytesPerPixel); 303 current_row += cursor_image->image()->stride(); 304 } 305 306 // Compare the current cursor with the last one we sent to the client. If 307 // they're the same, then don't bother sending the cursor again. 308 if (last_cursor_.size.equals(cursor->size) && 309 last_cursor_.hotspot.equals(cursor->hotspot) && 310 last_cursor_.data == cursor->data) { 311 return; 312 } 313 314 LOG(LS_VERBOSE) << "Sending updated cursor: " << cursor->size.width() << "x" 315 << cursor->size.height(); 316 317 // Record the last cursor image that we sent to the client. 318 last_cursor_ = *cursor; 319 320 if (mouse_shape_observer_) 321 mouse_shape_observer_->OnCursorShapeChanged(cursor.release()); 322 } 323 324 } // namespace webrtc 325