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 "modules/desktop_capture/win/screen_capturer_win_gdi.h"
12 
13 #include <utility>
14 
15 #include "modules/desktop_capture/desktop_capture_options.h"
16 #include "modules/desktop_capture/desktop_frame.h"
17 #include "modules/desktop_capture/desktop_frame_win.h"
18 #include "modules/desktop_capture/desktop_region.h"
19 #include "modules/desktop_capture/mouse_cursor.h"
20 #include "modules/desktop_capture/win/cursor.h"
21 #include "modules/desktop_capture/win/desktop.h"
22 #include "modules/desktop_capture/win/screen_capture_utils.h"
23 #include "rtc_base/checks.h"
24 #include "rtc_base/logging.h"
25 #include "rtc_base/time_utils.h"
26 #include "rtc_base/trace_event.h"
27 
28 namespace webrtc {
29 
30 namespace {
31 
32 // Constants from dwmapi.h.
33 const UINT DWM_EC_DISABLECOMPOSITION = 0;
34 const UINT DWM_EC_ENABLECOMPOSITION = 1;
35 
36 const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll";
37 
38 }  // namespace
39 
ScreenCapturerWinGdi(const DesktopCaptureOptions & options)40 ScreenCapturerWinGdi::ScreenCapturerWinGdi(
41     const DesktopCaptureOptions& options) {
42   if (options.disable_effects()) {
43     // Load dwmapi.dll dynamically since it is not available on XP.
44     if (!dwmapi_library_)
45       dwmapi_library_ = LoadLibraryW(kDwmapiLibraryName);
46 
47     if (dwmapi_library_) {
48       composition_func_ = reinterpret_cast<DwmEnableCompositionFunc>(
49           GetProcAddress(dwmapi_library_, "DwmEnableComposition"));
50     }
51   }
52 }
53 
~ScreenCapturerWinGdi()54 ScreenCapturerWinGdi::~ScreenCapturerWinGdi() {
55   if (desktop_dc_)
56     ReleaseDC(NULL, desktop_dc_);
57   if (memory_dc_)
58     DeleteDC(memory_dc_);
59 
60   // Restore Aero.
61   if (composition_func_)
62     (*composition_func_)(DWM_EC_ENABLECOMPOSITION);
63 
64   if (dwmapi_library_)
65     FreeLibrary(dwmapi_library_);
66 }
67 
SetSharedMemoryFactory(std::unique_ptr<SharedMemoryFactory> shared_memory_factory)68 void ScreenCapturerWinGdi::SetSharedMemoryFactory(
69     std::unique_ptr<SharedMemoryFactory> shared_memory_factory) {
70   shared_memory_factory_ = std::move(shared_memory_factory);
71 }
72 
CaptureFrame()73 void ScreenCapturerWinGdi::CaptureFrame() {
74   TRACE_EVENT0("webrtc", "ScreenCapturerWinGdi::CaptureFrame");
75   int64_t capture_start_time_nanos = rtc::TimeNanos();
76 
77   queue_.MoveToNextFrame();
78   RTC_DCHECK(!queue_.current_frame() || !queue_.current_frame()->IsShared());
79 
80   // Make sure the GDI capture resources are up-to-date.
81   PrepareCaptureResources();
82 
83   if (!CaptureImage()) {
84     RTC_LOG(WARNING) << "Failed to capture screen by GDI.";
85     callback_->OnCaptureResult(Result::ERROR_TEMPORARY, nullptr);
86     return;
87   }
88 
89   // Emit the current frame.
90   std::unique_ptr<DesktopFrame> frame = queue_.current_frame()->Share();
91   frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX),
92                                GetDeviceCaps(desktop_dc_, LOGPIXELSY)));
93   frame->mutable_updated_region()->SetRect(
94       DesktopRect::MakeSize(frame->size()));
95   frame->set_capture_time_ms((rtc::TimeNanos() - capture_start_time_nanos) /
96                              rtc::kNumNanosecsPerMillisec);
97   frame->set_capturer_id(DesktopCapturerId::kScreenCapturerWinGdi);
98   callback_->OnCaptureResult(Result::SUCCESS, std::move(frame));
99 }
100 
GetSourceList(SourceList * sources)101 bool ScreenCapturerWinGdi::GetSourceList(SourceList* sources) {
102   return webrtc::GetScreenList(sources);
103 }
104 
SelectSource(SourceId id)105 bool ScreenCapturerWinGdi::SelectSource(SourceId id) {
106   bool valid = IsScreenValid(id, &current_device_key_);
107   if (valid)
108     current_screen_id_ = id;
109   return valid;
110 }
111 
Start(Callback * callback)112 void ScreenCapturerWinGdi::Start(Callback* callback) {
113   RTC_DCHECK(!callback_);
114   RTC_DCHECK(callback);
115 
116   callback_ = callback;
117 
118   // Vote to disable Aero composited desktop effects while capturing. Windows
119   // will restore Aero automatically if the process exits. This has no effect
120   // under Windows 8 or higher.  See crbug.com/124018.
121   if (composition_func_)
122     (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
123 }
124 
PrepareCaptureResources()125 void ScreenCapturerWinGdi::PrepareCaptureResources() {
126   // Switch to the desktop receiving user input if different from the current
127   // one.
128   std::unique_ptr<Desktop> input_desktop(Desktop::GetInputDesktop());
129   if (input_desktop && !desktop_.IsSame(*input_desktop)) {
130     // Release GDI resources otherwise SetThreadDesktop will fail.
131     if (desktop_dc_) {
132       ReleaseDC(NULL, desktop_dc_);
133       desktop_dc_ = nullptr;
134     }
135 
136     if (memory_dc_) {
137       DeleteDC(memory_dc_);
138       memory_dc_ = nullptr;
139     }
140 
141     // If SetThreadDesktop() fails, the thread is still assigned a desktop.
142     // So we can continue capture screen bits, just from the wrong desktop.
143     desktop_.SetThreadDesktop(input_desktop.release());
144 
145     // Re-assert our vote to disable Aero.
146     // See crbug.com/124018 and crbug.com/129906.
147     if (composition_func_) {
148       (*composition_func_)(DWM_EC_DISABLECOMPOSITION);
149     }
150   }
151 
152   // If the display configurations have changed then recreate GDI resources.
153   if (display_configuration_monitor_.IsChanged()) {
154     if (desktop_dc_) {
155       ReleaseDC(NULL, desktop_dc_);
156       desktop_dc_ = nullptr;
157     }
158     if (memory_dc_) {
159       DeleteDC(memory_dc_);
160       memory_dc_ = nullptr;
161     }
162   }
163 
164   if (!desktop_dc_) {
165     RTC_DCHECK(!memory_dc_);
166 
167     // Create GDI device contexts to capture from the desktop into memory.
168     desktop_dc_ = GetDC(nullptr);
169     RTC_CHECK(desktop_dc_);
170     memory_dc_ = CreateCompatibleDC(desktop_dc_);
171     RTC_CHECK(memory_dc_);
172 
173     // Make sure the frame buffers will be reallocated.
174     queue_.Reset();
175   }
176 }
177 
CaptureImage()178 bool ScreenCapturerWinGdi::CaptureImage() {
179   DesktopRect screen_rect =
180       GetScreenRect(current_screen_id_, current_device_key_);
181   if (screen_rect.is_empty()) {
182     RTC_LOG(LS_WARNING) << "Failed to get screen rect.";
183     return false;
184   }
185 
186   DesktopSize size = screen_rect.size();
187   // If the current buffer is from an older generation then allocate a new one.
188   // Note that we can't reallocate other buffers at this point, since the caller
189   // may still be reading from them.
190   if (!queue_.current_frame() ||
191       !queue_.current_frame()->size().equals(screen_rect.size())) {
192     RTC_DCHECK(desktop_dc_);
193     RTC_DCHECK(memory_dc_);
194 
195     std::unique_ptr<DesktopFrame> buffer = DesktopFrameWin::Create(
196         size, shared_memory_factory_.get(), desktop_dc_);
197     if (!buffer) {
198       RTC_LOG(LS_WARNING) << "Failed to create frame buffer.";
199       return false;
200     }
201     queue_.ReplaceCurrentFrame(SharedDesktopFrame::Wrap(std::move(buffer)));
202   }
203   queue_.current_frame()->set_top_left(
204       screen_rect.top_left().subtract(GetFullscreenRect().top_left()));
205 
206   // Select the target bitmap into the memory dc and copy the rect from desktop
207   // to memory.
208   DesktopFrameWin* current = static_cast<DesktopFrameWin*>(
209       queue_.current_frame()->GetUnderlyingFrame());
210   HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap());
211   if (!previous_object || previous_object == HGDI_ERROR) {
212     RTC_LOG(LS_WARNING) << "Failed to select current bitmap into memery dc.";
213     return false;
214   }
215 
216   bool result = (BitBlt(memory_dc_, 0, 0, screen_rect.width(),
217                         screen_rect.height(), desktop_dc_, screen_rect.left(),
218                         screen_rect.top(), SRCCOPY | CAPTUREBLT) != FALSE);
219   if (!result) {
220     RTC_LOG_GLE(LS_WARNING) << "BitBlt failed";
221   }
222 
223   // Select back the previously selected object to that the device contect
224   // could be destroyed independently of the bitmap if needed.
225   SelectObject(memory_dc_, previous_object);
226 
227   return result;
228 }
229 
230 }  // namespace webrtc
231