From 4220434d37cfbdb4e9ac5e7983b0a0210f21d139 Mon Sep 17 00:00:00 2001 From: "jiayl@webrtc.org" Date: Mon, 5 May 2014 16:08:47 +0000 Subject: [PATCH] Implement the Windows screen capturer using the Magnification API. The original ScreenCapturerWin is renamed ScreenCapturerWinGdi. BUG=2789 TESTED=full desktop cast and single monitor cast works on win7 and win8 desktop mode. Have to use GDI capturer on win8 metro mode. Changing display configuration work on the fly. R=sergeyu@chromium.org, wez@chromium.org Committed: https://code.google.com/p/webrtc/source/detail?r=6048 Review URL: https://webrtc-codereview.appspot.com/12149004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@6053 4adac7df-926f-26a2-2b94-8c16560cd09d --- .../valgrind-webrtc/drmemory/suppressions.txt | 41 +- .../desktop_capture/desktop_capture.gypi | 6 + .../desktop_capture_options.cc | 4 + .../desktop_capture/desktop_capture_options.h | 13 + .../screen_capturer_unittest.cc | 18 +- .../desktop_capture/screen_capturer_win.cc | 447 +---------------- .../win/screen_capture_utils.cc | 91 ++++ .../win/screen_capture_utils.h | 35 ++ .../win/screen_capturer_win_gdi.cc | 322 ++++++++++++ .../win/screen_capturer_win_gdi.h | 99 ++++ .../win/screen_capturer_win_magnifier.cc | 459 ++++++++++++++++++ .../win/screen_capturer_win_magnifier.h | 159 ++++++ 12 files changed, 1237 insertions(+), 457 deletions(-) create mode 100644 webrtc/modules/desktop_capture/win/screen_capture_utils.cc create mode 100644 webrtc/modules/desktop_capture/win/screen_capture_utils.h create mode 100644 webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc create mode 100644 webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h create mode 100644 webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc create mode 100644 webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h diff --git a/tools/valgrind-webrtc/drmemory/suppressions.txt b/tools/valgrind-webrtc/drmemory/suppressions.txt index 5bfc865af..8b75653ce 100644 --- a/tools/valgrind-webrtc/drmemory/suppressions.txt +++ b/tools/valgrind-webrtc/drmemory/suppressions.txt @@ -29,28 +29,37 @@ GDI32.dll!DeleteDC *!testing::internal::HandleSehExceptionsInMethodIfSupported UNINITIALIZED READ -name=https://code.google.com/p/webrtc/issues/detail?id=2323 (2) +name= system call NtUserGetThreadDesktop parameter value #1 *!webrtc::Desktop::GetThreadDesktop *!webrtc::ScopedThreadDesktop::ScopedThreadDesktop -*!webrtc::`anonymous namespace'::ScreenCapturerWin::ScreenCapturerWin -*!webrtc::ScreenCapturer::CreateWithDisableAero -*!webrtc::ScreenCapturer::Create -... -*!testing::internal::HandleSehExceptionsInMethodIfSupported - -UNINITIALIZED READ -name=https://code.google.com/p/webrtc/issues/detail?id=2323 (3) -system call NtUserGetThreadDesktop parameter value #1 -*!webrtc::Desktop::GetThreadDesktop -*!webrtc::ScopedThreadDesktop::ScopedThreadDesktop -*!webrtc::`anonymous namespace'::ScreenCapturerWin::ScreenCapturerWin +*!webrtc::ScreenCapturerWinGdi::ScreenCapturerWinGdi *!webrtc::ScreenCapturer::Create *!webrtc::ScreenCapturerTest::SetUp *!testing::internal::HandleSehExceptionsInMethodIfSupported<> UNINITIALIZED READ -name=https://code.google.com/p/webrtc/issues/detail?id=2323 (4) +name= +system call NtUserGetThreadDesktop parameter value #1 +*!webrtc::Desktop::GetThreadDesktop +*!webrtc::ScopedThreadDesktop::ScopedThreadDesktop +*!webrtc::ScreenCapturerWinGdi::ScreenCapturerWinGdi +*!webrtc::ScreenCapturer::Create +*!webrtc::ScreenCapturerTest_UseMagnifier_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name= +system call NtUserGetThreadDesktop parameter value #1 +*!webrtc::Desktop::GetThreadDesktop +*!webrtc::ScopedThreadDesktop::ScopedThreadDesktop +*!webrtc::ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier +*!webrtc::ScreenCapturer::Create +*!webrtc::ScreenCapturerTest_UseMagnifier_Test::TestBody +*!testing::internal::HandleSehExceptionsInMethodIfSupported<> + +UNINITIALIZED READ +name=https://code.google.com/p/webrtc/issues/detail?id=2323 (5) system call NtUserGetIconInfo parameter value #0 USER32.dll!GetIconInfo *!webrtc::CreateMouseCursorFromHCursor @@ -60,7 +69,7 @@ USER32.dll!GetIconInfo *!testing::internal::HandleSehExceptionsInMethodIfSupported<> UNINITIALIZED READ -name=https://code.google.com/p/webrtc/issues/detail?id=2323 (5) +name=https://code.google.com/p/webrtc/issues/detail?id=2323 (6) system call NtUserGetWindowPlacement *!webrtc::GetCroppedWindowRect *!webrtc::`anonymous namespace'::WindowCapturerWin::Capture @@ -68,7 +77,7 @@ system call NtUserGetWindowPlacement *!testing::internal::HandleSehExceptionsInMethodIfSupported<> UNADDRESSABLE ACCESS -name=https://code.google.com/p/webrtc/issues/detail?id=2323 (6) +name=https://code.google.com/p/webrtc/issues/detail?id=2323 (7) system call NtUserGetWindowPlacement parameter #1 *!webrtc::GetCroppedWindowRect *!webrtc::`anonymous namespace'::WindowCapturerWin::Capture diff --git a/webrtc/modules/desktop_capture/desktop_capture.gypi b/webrtc/modules/desktop_capture/desktop_capture.gypi index 5f967453f..6f4a08301 100644 --- a/webrtc/modules/desktop_capture/desktop_capture.gypi +++ b/webrtc/modules/desktop_capture/desktop_capture.gypi @@ -69,6 +69,12 @@ "win/scoped_gdi_object.h", "win/scoped_thread_desktop.cc", "win/scoped_thread_desktop.h", + "win/screen_capturer_win_gdi.cc", + "win/screen_capturer_win_gdi.h", + "win/screen_capturer_win_magnifier.cc", + "win/screen_capturer_win_magnifier.h", + "win/screen_capture_utils.cc", + "win/screen_capture_utils.h", "win/window_capture_utils.cc", "win/window_capture_utils.h", "window_capturer.cc", diff --git a/webrtc/modules/desktop_capture/desktop_capture_options.cc b/webrtc/modules/desktop_capture/desktop_capture_options.cc index 26044e127..105853bf9 100644 --- a/webrtc/modules/desktop_capture/desktop_capture_options.cc +++ b/webrtc/modules/desktop_capture/desktop_capture_options.cc @@ -19,6 +19,10 @@ DesktopCaptureOptions::DesktopCaptureOptions() // XDamage is often broken, so don't use it by default. use_update_notifications_ = false; #endif + +#if defined(WEBRTC_WIN) + allow_use_magnification_api_ = false; +#endif } DesktopCaptureOptions::~DesktopCaptureOptions() {} diff --git a/webrtc/modules/desktop_capture/desktop_capture_options.h b/webrtc/modules/desktop_capture/desktop_capture_options.h index 2a188a03a..c01d3d29d 100644 --- a/webrtc/modules/desktop_capture/desktop_capture_options.h +++ b/webrtc/modules/desktop_capture/desktop_capture_options.h @@ -66,6 +66,15 @@ class DesktopCaptureOptions { disable_effects_ = disable_effects; } +#if defined(WEBRTC_WIN) + bool allow_use_magnification_api() const { + return allow_use_magnification_api_; + } + void set_allow_use_magnification_api(bool allow) { + allow_use_magnification_api_ = allow; + } +#endif + private: #if defined(USE_X11) scoped_refptr x_display_; @@ -74,6 +83,10 @@ class DesktopCaptureOptions { #if defined(WEBRTC_MAC) && !defined(WEBRTC_IOS) scoped_refptr configuration_monitor_; #endif + +#if defined(WEBRTC_WIN) + bool allow_use_magnification_api_; +#endif bool use_update_notifications_; bool disable_effects_; }; diff --git a/webrtc/modules/desktop_capture/screen_capturer_unittest.cc b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc index 94c1f707e..50ff7a285 100644 --- a/webrtc/modules/desktop_capture/screen_capturer_unittest.cc +++ b/webrtc/modules/desktop_capture/screen_capturer_unittest.cc @@ -106,7 +106,7 @@ TEST_F(ScreenCapturerTest, Capture) { delete frame; } -#if defined(OS_WIN) +#if defined(WEBRTC_WIN) TEST_F(ScreenCapturerTest, UseSharedBuffers) { DesktopFrame* frame = NULL; @@ -129,6 +129,20 @@ TEST_F(ScreenCapturerTest, UseSharedBuffers) { delete frame; } -#endif // defined(OS_WIN) +TEST_F(ScreenCapturerTest, UseMagnifier) { + DesktopCaptureOptions options(DesktopCaptureOptions::CreateDefault()); + options.set_allow_use_magnification_api(true); + capturer_.reset(ScreenCapturer::Create(options)); + + DesktopFrame* frame = NULL; + EXPECT_CALL(callback_, OnCaptureCompleted(_)).WillOnce(SaveArg<0>(&frame)); + + capturer_->Start(&callback_); + capturer_->Capture(DesktopRegion()); + ASSERT_TRUE(frame); + delete frame; +} + +#endif // defined(WEBRTC_WIN) } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/screen_capturer_win.cc b/webrtc/modules/desktop_capture/screen_capturer_win.cc index fc6ce5081..5950795d4 100644 --- a/webrtc/modules/desktop_capture/screen_capturer_win.cc +++ b/webrtc/modules/desktop_capture/screen_capturer_win.cc @@ -10,451 +10,20 @@ #include "webrtc/modules/desktop_capture/screen_capturer.h" -#include - #include "webrtc/modules/desktop_capture/desktop_capture_options.h" -#include "webrtc/modules/desktop_capture/desktop_frame.h" -#include "webrtc/modules/desktop_capture/desktop_frame_win.h" -#include "webrtc/modules/desktop_capture/desktop_region.h" -#include "webrtc/modules/desktop_capture/differ.h" -#include "webrtc/modules/desktop_capture/mouse_cursor.h" -#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h" -#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" -#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" -#include "webrtc/modules/desktop_capture/win/cursor.h" -#include "webrtc/modules/desktop_capture/win/desktop.h" -#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" -#include "webrtc/system_wrappers/interface/logging.h" -#include "webrtc/system_wrappers/interface/scoped_ptr.h" -#include "webrtc/system_wrappers/interface/tick_util.h" +#include "webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h" +#include "webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h" namespace webrtc { -namespace { - -// Constants from dwmapi.h. -const UINT DWM_EC_DISABLECOMPOSITION = 0; -const UINT DWM_EC_ENABLECOMPOSITION = 1; - -typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT); - -const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll"; - -// ScreenCapturerWin captures 32bit RGB using GDI. -// -// ScreenCapturerWin is double-buffered as required by ScreenCapturer. -class ScreenCapturerWin : public ScreenCapturer { - public: - ScreenCapturerWin(const DesktopCaptureOptions& options); - virtual ~ScreenCapturerWin(); - - // Overridden from ScreenCapturer: - virtual void Start(Callback* callback) OVERRIDE; - virtual void Capture(const DesktopRegion& region) OVERRIDE; - virtual void SetMouseShapeObserver( - MouseShapeObserver* mouse_shape_observer) OVERRIDE; - virtual bool GetScreenList(ScreenList* screens) OVERRIDE; - virtual bool SelectScreen(ScreenId id) OVERRIDE; - - private: - // Make sure that the device contexts match the screen configuration. - void PrepareCaptureResources(); - - // Captures the current screen contents into the current buffer. Returns true - // if succeeded. - bool CaptureImage(); - - // Capture the current cursor shape. - void CaptureCursor(); - - // Get the rect of the currently selected screen, relative to the primary - // display's top-left. If the screen is disabled or disconnected, or any error - // happens, an empty rect is returned. - DesktopRect GetScreenRect(); - - Callback* callback_; - MouseShapeObserver* mouse_shape_observer_; - ScreenId current_screen_id_; - std::wstring current_device_key_; - - // A thread-safe list of invalid rectangles, and the size of the most - // recently captured screen. - ScreenCapturerHelper helper_; - - // Snapshot of the last cursor bitmap we sent to the client. This is used - // to diff against the current cursor so we only send a cursor-change - // message when the shape has changed. - MouseCursorShape last_cursor_; - - ScopedThreadDesktop desktop_; - - // GDI resources used for screen capture. - HDC desktop_dc_; - HDC memory_dc_; - - // Queue of the frames buffers. - ScreenCaptureFrameQueue queue_; - - // Rectangle describing the bounds of the desktop device context, relative to - // the primary display's top-left. - DesktopRect desktop_dc_rect_; - - // Class to calculate the difference between two screen bitmaps. - scoped_ptr differ_; - - HMODULE dwmapi_library_; - DwmEnableCompositionFunc composition_func_; - - // Used to suppress duplicate logging of SetThreadExecutionState errors. - bool set_thread_execution_state_failed_; - - DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWin); -}; - -ScreenCapturerWin::ScreenCapturerWin(const DesktopCaptureOptions& options) - : callback_(NULL), - mouse_shape_observer_(NULL), - current_screen_id_(kFullDesktopScreenId), - desktop_dc_(NULL), - memory_dc_(NULL), - dwmapi_library_(NULL), - composition_func_(NULL), - set_thread_execution_state_failed_(false) { - if (options.disable_effects()) { - // Load dwmapi.dll dynamically since it is not available on XP. - if (!dwmapi_library_) - dwmapi_library_ = LoadLibrary(kDwmapiLibraryName); - - if (dwmapi_library_) { - composition_func_ = reinterpret_cast( - GetProcAddress(dwmapi_library_, "DwmEnableComposition")); - } - } -} - -ScreenCapturerWin::~ScreenCapturerWin() { - if (desktop_dc_) - ReleaseDC(NULL, desktop_dc_); - if (memory_dc_) - DeleteDC(memory_dc_); - - // Restore Aero. - if (composition_func_) - (*composition_func_)(DWM_EC_ENABLECOMPOSITION); - - if (dwmapi_library_) - FreeLibrary(dwmapi_library_); -} - -void ScreenCapturerWin::Capture(const DesktopRegion& region) { - TickTime capture_start_time = TickTime::Now(); - - queue_.MoveToNextFrame(); - - // Request that the system not power-down the system, or the display hardware. - if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) { - if (!set_thread_execution_state_failed_) { - set_thread_execution_state_failed_ = true; - LOG_F(LS_WARNING) << "Failed to make system & display power assertion: " - << GetLastError(); - } - } - - // Make sure the GDI capture resources are up-to-date. - PrepareCaptureResources(); - - // Copy screen bits to the current buffer. - if (!CaptureImage()) { - callback_->OnCaptureCompleted(NULL); - return; - } - - const DesktopFrame* current_frame = queue_.current_frame(); - const DesktopFrame* last_frame = queue_.previous_frame(); - if (last_frame && last_frame->size().equals(current_frame->size())) { - // Make sure the differencer is set up correctly for these previous and - // current screens. - if (!differ_.get() || - (differ_->width() != current_frame->size().width()) || - (differ_->height() != current_frame->size().height()) || - (differ_->bytes_per_row() != current_frame->stride())) { - differ_.reset(new Differ(current_frame->size().width(), - current_frame->size().height(), - DesktopFrame::kBytesPerPixel, - current_frame->stride())); - } - - // Calculate difference between the two last captured frames. - DesktopRegion region; - differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(), - ®ion); - helper_.InvalidateRegion(region); - } else { - // No previous frame is available, or the screen is resized. Invalidate the - // whole screen. - helper_.InvalidateScreen(current_frame->size()); - } - - helper_.set_size_most_recent(current_frame->size()); - - // Emit the current frame. - DesktopFrame* frame = queue_.current_frame()->Share(); - frame->set_dpi(DesktopVector( - GetDeviceCaps(desktop_dc_, LOGPIXELSX), - GetDeviceCaps(desktop_dc_, LOGPIXELSY))); - frame->mutable_updated_region()->Clear(); - helper_.TakeInvalidRegion(frame->mutable_updated_region()); - frame->set_capture_time_ms( - (TickTime::Now() - capture_start_time).Milliseconds()); - callback_->OnCaptureCompleted(frame); - - // Check for cursor shape update. - CaptureCursor(); -} - -void ScreenCapturerWin::SetMouseShapeObserver( - MouseShapeObserver* mouse_shape_observer) { - assert(!mouse_shape_observer_); - assert(mouse_shape_observer); - - mouse_shape_observer_ = mouse_shape_observer; -} - -bool ScreenCapturerWin::GetScreenList(ScreenList* screens) { - assert(screens->size() == 0); - BOOL enum_result = TRUE; - for (int device_index = 0; ; ++device_index) { - DISPLAY_DEVICE device; - device.cb = sizeof(device); - enum_result = EnumDisplayDevices(NULL, device_index, &device, 0); - // |enum_result| is 0 if we have enumerated all devices. - if (!enum_result) - break; - - // We only care about active displays. - if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) - continue; - Screen screen; - screen.id = device_index; - screens->push_back(screen); - } - return true; -} - -bool ScreenCapturerWin::SelectScreen(ScreenId id) { - if (id == kFullDesktopScreenId) { - current_screen_id_ = id; - return true; - } - DISPLAY_DEVICE device; - device.cb = sizeof(device); - BOOL enum_result = EnumDisplayDevices(NULL, id, &device, 0); - if (!enum_result) - return false; - - current_device_key_ = device.DeviceKey; - current_screen_id_ = id; - return true; -} - -void ScreenCapturerWin::Start(Callback* callback) { - assert(!callback_); - assert(callback); - - callback_ = callback; - - // Vote to disable Aero composited desktop effects while capturing. Windows - // will restore Aero automatically if the process exits. This has no effect - // under Windows 8 or higher. See crbug.com/124018. - if (composition_func_) - (*composition_func_)(DWM_EC_DISABLECOMPOSITION); -} - -void ScreenCapturerWin::PrepareCaptureResources() { - // Switch to the desktop receiving user input if different from the current - // one. - scoped_ptr input_desktop(Desktop::GetInputDesktop()); - if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { - // Release GDI resources otherwise SetThreadDesktop will fail. - if (desktop_dc_) { - ReleaseDC(NULL, desktop_dc_); - desktop_dc_ = NULL; - } - - if (memory_dc_) { - DeleteDC(memory_dc_); - memory_dc_ = NULL; - } - - // If SetThreadDesktop() fails, the thread is still assigned a desktop. - // So we can continue capture screen bits, just from the wrong desktop. - desktop_.SetThreadDesktop(input_desktop.release()); - - // Re-assert our vote to disable Aero. - // See crbug.com/124018 and crbug.com/129906. - if (composition_func_ != NULL) { - (*composition_func_)(DWM_EC_DISABLECOMPOSITION); - } - } - - // If the display bounds have changed then recreate GDI resources. - // TODO(wez): Also check for pixel format changes. - DesktopRect screen_rect(DesktopRect::MakeXYWH( - GetSystemMetrics(SM_XVIRTUALSCREEN), - GetSystemMetrics(SM_YVIRTUALSCREEN), - GetSystemMetrics(SM_CXVIRTUALSCREEN), - GetSystemMetrics(SM_CYVIRTUALSCREEN))); - if (!screen_rect.equals(desktop_dc_rect_)) { - if (desktop_dc_) { - ReleaseDC(NULL, desktop_dc_); - desktop_dc_ = NULL; - } - if (memory_dc_) { - DeleteDC(memory_dc_); - memory_dc_ = NULL; - } - desktop_dc_rect_ = DesktopRect(); - } - - if (desktop_dc_ == NULL) { - assert(memory_dc_ == NULL); - - // Create GDI device contexts to capture from the desktop into memory. - desktop_dc_ = GetDC(NULL); - if (!desktop_dc_) - abort(); - memory_dc_ = CreateCompatibleDC(desktop_dc_); - if (!memory_dc_) - abort(); - desktop_dc_rect_ = screen_rect; - - // Make sure the frame buffers will be reallocated. - queue_.Reset(); - - helper_.ClearInvalidRegion(); - } -} - -bool ScreenCapturerWin::CaptureImage() { - DesktopRect screen_rect = GetScreenRect(); - if (screen_rect.is_empty()) - return false; - DesktopSize size = screen_rect.size(); - // If the current buffer is from an older generation then allocate a new one. - // Note that we can't reallocate other buffers at this point, since the caller - // may still be reading from them. - if (!queue_.current_frame() || - !queue_.current_frame()->size().equals(size)) { - assert(desktop_dc_ != NULL); - assert(memory_dc_ != NULL); - - size_t buffer_size = size.width() * size.height() * - DesktopFrame::kBytesPerPixel; - SharedMemory* shared_memory = - callback_->CreateSharedMemory(buffer_size); - scoped_ptr buffer( - DesktopFrameWin::Create(size, shared_memory, desktop_dc_)); - queue_.ReplaceCurrentFrame(buffer.release()); - } - - // Select the target bitmap into the memory dc and copy the rect from desktop - // to memory. - DesktopFrameWin* current = static_cast( - queue_.current_frame()->GetUnderlyingFrame()); - HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap()); - if (previous_object != NULL) { - BitBlt(memory_dc_, - 0, 0, screen_rect.width(), screen_rect.height(), - desktop_dc_, - screen_rect.left(), screen_rect.top(), - SRCCOPY | CAPTUREBLT); - - // Select back the previously selected object to that the device contect - // could be destroyed independently of the bitmap if needed. - SelectObject(memory_dc_, previous_object); - } - return true; -} - -void ScreenCapturerWin::CaptureCursor() { - CURSORINFO cursor_info; - cursor_info.cbSize = sizeof(CURSORINFO); - if (!GetCursorInfo(&cursor_info)) { - LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError(); - return; - } - - // Note that |cursor_info.hCursor| does not need to be freed. - scoped_ptr cursor_image( - CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor)); - if (!cursor_image.get()) - return; - - scoped_ptr cursor(new MouseCursorShape); - cursor->hotspot = cursor_image->hotspot(); - cursor->size = cursor_image->image()->size(); - uint8_t* current_row = cursor_image->image()->data(); - for (int y = 0; y < cursor_image->image()->size().height(); ++y) { - cursor->data.append(current_row, - current_row + cursor_image->image()->size().width() * - DesktopFrame::kBytesPerPixel); - current_row += cursor_image->image()->stride(); - } - - // Compare the current cursor with the last one we sent to the client. If - // they're the same, then don't bother sending the cursor again. - if (last_cursor_.size.equals(cursor->size) && - last_cursor_.hotspot.equals(cursor->hotspot) && - last_cursor_.data == cursor->data) { - return; - } - - LOG(LS_VERBOSE) << "Sending updated cursor: " << cursor->size.width() << "x" - << cursor->size.height(); - - // Record the last cursor image that we sent to the client. - last_cursor_ = *cursor; - - if (mouse_shape_observer_) - mouse_shape_observer_->OnCursorShapeChanged(cursor.release()); -} - -DesktopRect ScreenCapturerWin::GetScreenRect() { - DesktopRect rect = desktop_dc_rect_; - if (current_screen_id_ == kFullDesktopScreenId) - return rect; - - DISPLAY_DEVICE device; - device.cb = sizeof(device); - BOOL result = EnumDisplayDevices(NULL, current_screen_id_, &device, 0); - if (!result) - return DesktopRect(); - - // Verifies the device index still maps to the same display device. DeviceKey - // is documented as reserved, but it actually contains the registry key for - // the device and is unique for each monitor, while DeviceID is not. - if (current_device_key_ != device.DeviceKey) - return DesktopRect(); - - DEVMODE device_mode; - device_mode.dmSize = sizeof(device_mode); - device_mode.dmDriverExtra = 0; - result = EnumDisplaySettingsEx( - device.DeviceName, ENUM_CURRENT_SETTINGS, &device_mode, 0); - if (!result) - return DesktopRect(); - - rect = DesktopRect::MakeXYWH(device_mode.dmPosition.x, - device_mode.dmPosition.y, - device_mode.dmPelsWidth, - device_mode.dmPelsHeight); - return rect; -} -} // namespace - // static ScreenCapturer* ScreenCapturer::Create(const DesktopCaptureOptions& options) { - return new ScreenCapturerWin(options); + scoped_ptr gdi_capturer(new ScreenCapturerWinGdi(options)); + + if (options.allow_use_magnification_api()) + return new ScreenCapturerWinMagnifier(gdi_capturer.Pass()); + + return gdi_capturer.release(); } } // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capture_utils.cc b/webrtc/modules/desktop_capture/win/screen_capture_utils.cc new file mode 100644 index 000000000..4487e6d54 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capture_utils.cc @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h" + +#include + +namespace webrtc { + +bool GetScreenList(ScreenCapturer::ScreenList* screens) { + assert(screens->size() == 0); + + BOOL enum_result = TRUE; + for (int device_index = 0;; ++device_index) { + DISPLAY_DEVICE device; + device.cb = sizeof(device); + enum_result = EnumDisplayDevices(NULL, device_index, &device, 0); + + // |enum_result| is 0 if we have enumerated all devices. + if (!enum_result) + break; + + // We only care about active displays. + if (!(device.StateFlags & DISPLAY_DEVICE_ACTIVE)) + continue; + + ScreenCapturer::Screen screen; + screen.id = device_index; + screens->push_back(screen); + } + return true; +} + +bool IsScreenValid(ScreenId screen, std::wstring* device_key) { + if (screen == kFullDesktopScreenId) { + *device_key = L""; + return true; + } + + DISPLAY_DEVICE device; + device.cb = sizeof(device); + BOOL enum_result = EnumDisplayDevices(NULL, screen, &device, 0); + if (enum_result) + *device_key = device.DeviceKey; + + return !!enum_result; +} + +DesktopRect GetScreenRect(ScreenId screen, const std::wstring& device_key) { + if (screen == kFullDesktopScreenId) { + return DesktopRect::MakeXYWH(GetSystemMetrics(SM_XVIRTUALSCREEN), + GetSystemMetrics(SM_YVIRTUALSCREEN), + GetSystemMetrics(SM_CXVIRTUALSCREEN), + GetSystemMetrics(SM_CYVIRTUALSCREEN)); + } + + DISPLAY_DEVICE device; + device.cb = sizeof(device); + BOOL result = EnumDisplayDevices(NULL, screen, &device, 0); + if (!result) + return DesktopRect(); + + // Verifies the device index still maps to the same display device, to make + // sure we are capturing the same device when devices are added or removed. + // DeviceKey is documented as reserved, but it actually contains the registry + // key for the device and is unique for each monitor, while DeviceID is not. + if (device_key != device.DeviceKey) + return DesktopRect(); + + DEVMODE device_mode; + device_mode.dmSize = sizeof(device_mode); + device_mode.dmDriverExtra = 0; + result = EnumDisplaySettingsEx( + device.DeviceName, ENUM_CURRENT_SETTINGS, &device_mode, 0); + if (!result) + return DesktopRect(); + + return DesktopRect::MakeXYWH(device_mode.dmPosition.x, + device_mode.dmPosition.y, + device_mode.dmPelsWidth, + device_mode.dmPelsHeight); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capture_utils.h b/webrtc/modules/desktop_capture/win/screen_capture_utils.h new file mode 100644 index 000000000..42473e047 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capture_utils.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +namespace webrtc { + +// Output the list of active screens into |screens|. Returns true if succeeded, +// or false if it fails to enumerate the display devices. +bool GetScreenList(ScreenCapturer::ScreenList* screens); + +// Returns true if |screen| is a valid screen. The screen device key is +// returned through |device_key| if the screen is valid. The device key can be +// used in GetScreenRect to verify the screen matches the previously obtained +// id. +bool IsScreenValid(ScreenId screen, std::wstring* device_key); + +// Get the rect of the screen identified by |screen|, relative to the primary +// display's top-left. If the screen device key does not match |device_key|, or +// the screen does not exist, or any error happens, an empty rect is returned. +DesktopRect GetScreenRect(ScreenId screen, const std::wstring& device_key); + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURE_UTILS_H_ diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc new file mode 100644 index 000000000..5ab255337 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.cc @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h" + +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_frame_win.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/win/cursor.h" +#include "webrtc/modules/desktop_capture/win/desktop.h" +#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h" +#include "webrtc/system_wrappers/interface/logging.h" +#include "webrtc/system_wrappers/interface/tick_util.h" + +namespace webrtc { + +namespace { + +// Constants from dwmapi.h. +const UINT DWM_EC_DISABLECOMPOSITION = 0; +const UINT DWM_EC_ENABLECOMPOSITION = 1; + +const wchar_t kDwmapiLibraryName[] = L"dwmapi.dll"; + +} // namespace + +ScreenCapturerWinGdi::ScreenCapturerWinGdi(const DesktopCaptureOptions& options) + : callback_(NULL), + mouse_shape_observer_(NULL), + current_screen_id_(kFullDesktopScreenId), + desktop_dc_(NULL), + memory_dc_(NULL), + dwmapi_library_(NULL), + composition_func_(NULL), + set_thread_execution_state_failed_(false) { + if (options.disable_effects()) { + // Load dwmapi.dll dynamically since it is not available on XP. + if (!dwmapi_library_) + dwmapi_library_ = LoadLibrary(kDwmapiLibraryName); + + if (dwmapi_library_) { + composition_func_ = reinterpret_cast( + GetProcAddress(dwmapi_library_, "DwmEnableComposition")); + } + } +} + +ScreenCapturerWinGdi::~ScreenCapturerWinGdi() { + if (desktop_dc_) + ReleaseDC(NULL, desktop_dc_); + if (memory_dc_) + DeleteDC(memory_dc_); + + // Restore Aero. + if (composition_func_) + (*composition_func_)(DWM_EC_ENABLECOMPOSITION); + + if (dwmapi_library_) + FreeLibrary(dwmapi_library_); +} + +void ScreenCapturerWinGdi::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // Request that the system not power-down the system, or the display hardware. + if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) { + if (!set_thread_execution_state_failed_) { + set_thread_execution_state_failed_ = true; + LOG_F(LS_WARNING) << "Failed to make system & display power assertion: " + << GetLastError(); + } + } + + // Make sure the GDI capture resources are up-to-date. + PrepareCaptureResources(); + + if (!CaptureImage()) { + callback_->OnCaptureCompleted(NULL); + return; + } + + const DesktopFrame* current_frame = queue_.current_frame(); + const DesktopFrame* last_frame = queue_.previous_frame(); + if (last_frame && last_frame->size().equals(current_frame->size())) { + // Make sure the differencer is set up correctly for these previous and + // current screens. + if (!differ_.get() || + (differ_->width() != current_frame->size().width()) || + (differ_->height() != current_frame->size().height()) || + (differ_->bytes_per_row() != current_frame->stride())) { + differ_.reset(new Differ(current_frame->size().width(), + current_frame->size().height(), + DesktopFrame::kBytesPerPixel, + current_frame->stride())); + } + + // Calculate difference between the two last captured frames. + DesktopRegion region; + differ_->CalcDirtyRegion(last_frame->data(), current_frame->data(), + ®ion); + helper_.InvalidateRegion(region); + } else { + // No previous frame is available, or the screen is resized. Invalidate the + // whole screen. + helper_.InvalidateScreen(current_frame->size()); + } + + helper_.set_size_most_recent(current_frame->size()); + + // Emit the current frame. + DesktopFrame* frame = queue_.current_frame()->Share(); + frame->set_dpi(DesktopVector( + GetDeviceCaps(desktop_dc_, LOGPIXELSX), + GetDeviceCaps(desktop_dc_, LOGPIXELSY))); + frame->mutable_updated_region()->Clear(); + helper_.TakeInvalidRegion(frame->mutable_updated_region()); + frame->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(frame); + + // Check for cursor shape update. + CaptureCursor(); +} + +void ScreenCapturerWinGdi::SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) { + assert(!mouse_shape_observer_); + assert(mouse_shape_observer); + + mouse_shape_observer_ = mouse_shape_observer; +} + +bool ScreenCapturerWinGdi::GetScreenList(ScreenList* screens) { + return webrtc::GetScreenList(screens); +} + +bool ScreenCapturerWinGdi::SelectScreen(ScreenId id) { + bool valid = IsScreenValid(id, ¤t_device_key_); + if (valid) + current_screen_id_ = id; + return valid; +} + +void ScreenCapturerWinGdi::Start(Callback* callback) { + assert(!callback_); + assert(callback); + + callback_ = callback; + + // Vote to disable Aero composited desktop effects while capturing. Windows + // will restore Aero automatically if the process exits. This has no effect + // under Windows 8 or higher. See crbug.com/124018. + if (composition_func_) + (*composition_func_)(DWM_EC_DISABLECOMPOSITION); +} + +void ScreenCapturerWinGdi::PrepareCaptureResources() { + // Switch to the desktop receiving user input if different from the current + // one. + scoped_ptr input_desktop(Desktop::GetInputDesktop()); + if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { + // Release GDI resources otherwise SetThreadDesktop will fail. + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + + if (memory_dc_) { + DeleteDC(memory_dc_); + memory_dc_ = NULL; + } + + // If SetThreadDesktop() fails, the thread is still assigned a desktop. + // So we can continue capture screen bits, just from the wrong desktop. + desktop_.SetThreadDesktop(input_desktop.release()); + + // Re-assert our vote to disable Aero. + // See crbug.com/124018 and crbug.com/129906. + if (composition_func_ != NULL) { + (*composition_func_)(DWM_EC_DISABLECOMPOSITION); + } + } + + // If the display bounds have changed then recreate GDI resources. + // TODO(wez): Also check for pixel format changes. + DesktopRect screen_rect(DesktopRect::MakeXYWH( + GetSystemMetrics(SM_XVIRTUALSCREEN), + GetSystemMetrics(SM_YVIRTUALSCREEN), + GetSystemMetrics(SM_CXVIRTUALSCREEN), + GetSystemMetrics(SM_CYVIRTUALSCREEN))); + if (!screen_rect.equals(desktop_dc_rect_)) { + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + if (memory_dc_) { + DeleteDC(memory_dc_); + memory_dc_ = NULL; + } + desktop_dc_rect_ = DesktopRect(); + } + + if (desktop_dc_ == NULL) { + assert(memory_dc_ == NULL); + + // Create GDI device contexts to capture from the desktop into memory. + desktop_dc_ = GetDC(NULL); + if (!desktop_dc_) + abort(); + memory_dc_ = CreateCompatibleDC(desktop_dc_); + if (!memory_dc_) + abort(); + + desktop_dc_rect_ = screen_rect; + + // Make sure the frame buffers will be reallocated. + queue_.Reset(); + + helper_.ClearInvalidRegion(); + } +} + +bool ScreenCapturerWinGdi::CaptureImage() { + DesktopRect screen_rect = + GetScreenRect(current_screen_id_, current_device_key_); + if (screen_rect.is_empty()) + return false; + + DesktopSize size = screen_rect.size(); + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame() || + !queue_.current_frame()->size().equals(screen_rect.size())) { + assert(desktop_dc_ != NULL); + assert(memory_dc_ != NULL); + + size_t buffer_size = size.width() * size.height() * + DesktopFrame::kBytesPerPixel; + SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size); + + scoped_ptr buffer; + buffer.reset( + DesktopFrameWin::Create(size, shared_memory, desktop_dc_)); + queue_.ReplaceCurrentFrame(buffer.release()); + } + + // Select the target bitmap into the memory dc and copy the rect from desktop + // to memory. + DesktopFrameWin* current = static_cast( + queue_.current_frame()->GetUnderlyingFrame()); + HGDIOBJ previous_object = SelectObject(memory_dc_, current->bitmap()); + if (previous_object != NULL) { + BitBlt(memory_dc_, + 0, 0, screen_rect.width(), screen_rect.height(), + desktop_dc_, + screen_rect.left(), screen_rect.top(), + SRCCOPY | CAPTUREBLT); + + // Select back the previously selected object to that the device contect + // could be destroyed independently of the bitmap if needed. + SelectObject(memory_dc_, previous_object); + } + return true; +} + +void ScreenCapturerWinGdi::CaptureCursor() { + CURSORINFO cursor_info; + cursor_info.cbSize = sizeof(CURSORINFO); + if (!GetCursorInfo(&cursor_info)) { + LOG_F(LS_ERROR) << "Unable to get cursor info. Error = " << GetLastError(); + return; + } + + // Note that |cursor_info.hCursor| does not need to be freed. + scoped_ptr cursor_image( + CreateMouseCursorFromHCursor(desktop_dc_, cursor_info.hCursor)); + if (!cursor_image.get()) + return; + + scoped_ptr cursor(new MouseCursorShape); + cursor->hotspot = cursor_image->hotspot(); + cursor->size = cursor_image->image()->size(); + uint8_t* current_row = cursor_image->image()->data(); + for (int y = 0; y < cursor_image->image()->size().height(); ++y) { + cursor->data.append(current_row, + current_row + cursor_image->image()->size().width() * + DesktopFrame::kBytesPerPixel); + current_row += cursor_image->image()->stride(); + } + + // Compare the current cursor with the last one we sent to the client. If + // they're the same, then don't bother sending the cursor again. + if (last_cursor_.size.equals(cursor->size) && + last_cursor_.hotspot.equals(cursor->hotspot) && + last_cursor_.data == cursor->data) { + return; + } + + LOG(LS_VERBOSE) << "Sending updated cursor: " << cursor->size.width() << "x" + << cursor->size.height(); + + // Record the last cursor image that we sent to the client. + last_cursor_ = *cursor; + + if (mouse_shape_observer_) + mouse_shape_observer_->OnCursorShapeChanged(cursor.release()); +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h new file mode 100644 index 000000000..2db87d097 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_gdi.h @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ + +#include "webrtc/modules/desktop_capture/screen_capturer.h" + +#include + +#include "webrtc/modules/desktop_capture/mouse_cursor_shape.h" +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class Differ; +class MouseShapeObserver; + +// ScreenCapturerWinGdi captures 32bit RGB using GDI. +// +// ScreenCapturerWinGdi is double-buffered as required by ScreenCapturer. +class ScreenCapturerWinGdi : public ScreenCapturer { + public: + explicit ScreenCapturerWinGdi(const DesktopCaptureOptions& options); + virtual ~ScreenCapturerWinGdi(); + + // Overridden from ScreenCapturer: + virtual void Start(Callback* callback) OVERRIDE; + virtual void Capture(const DesktopRegion& region) OVERRIDE; + virtual void SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) OVERRIDE; + virtual bool GetScreenList(ScreenList* screens) OVERRIDE; + virtual bool SelectScreen(ScreenId id) OVERRIDE; + + private: + typedef HRESULT (WINAPI * DwmEnableCompositionFunc)(UINT); + + // Make sure that the device contexts match the screen configuration. + void PrepareCaptureResources(); + + // Captures the current screen contents into the current buffer. Returns true + // if succeeded. + bool CaptureImage(); + + // Capture the current cursor shape. + void CaptureCursor(); + + Callback* callback_; + MouseShapeObserver* mouse_shape_observer_; + ScreenId current_screen_id_; + std::wstring current_device_key_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Snapshot of the last cursor bitmap we sent to the client. This is used + // to diff against the current cursor so we only send a cursor-change + // message when the shape has changed. + MouseCursorShape last_cursor_; + + ScopedThreadDesktop desktop_; + + // GDI resources used for screen capture. + HDC desktop_dc_; + HDC memory_dc_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue queue_; + + // Rectangle describing the bounds of the desktop device context, relative to + // the primary display's top-left. + DesktopRect desktop_dc_rect_; + + // Class to calculate the difference between two screen bitmaps. + scoped_ptr differ_; + + HMODULE dwmapi_library_; + DwmEnableCompositionFunc composition_func_; + + // Used to suppress duplicate logging of SetThreadExecutionState errors. + bool set_thread_execution_state_failed_; + + DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWinGdi); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_GDI_H_ diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc new file mode 100644 index 000000000..4ce007328 --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.cc @@ -0,0 +1,459 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h" + +#include "webrtc/modules/desktop_capture/desktop_capture_options.h" +#include "webrtc/modules/desktop_capture/desktop_frame.h" +#include "webrtc/modules/desktop_capture/desktop_frame_win.h" +#include "webrtc/modules/desktop_capture/desktop_region.h" +#include "webrtc/modules/desktop_capture/differ.h" +#include "webrtc/modules/desktop_capture/mouse_cursor.h" +#include "webrtc/modules/desktop_capture/win/cursor.h" +#include "webrtc/modules/desktop_capture/win/desktop.h" +#include "webrtc/modules/desktop_capture/win/screen_capture_utils.h" +#include "webrtc/system_wrappers/interface/logging.h" +#include "webrtc/system_wrappers/interface/tick_util.h" + +namespace webrtc { + +// kMagnifierWindowClass has to be "Magnifier" according to the Magnification +// API. The other strings can be anything. +static LPCTSTR kMagnifierHostClass = L"ScreenCapturerWinMagnifierHost"; +static LPCTSTR kHostWindowName = L"MagnifierHost"; +static LPCTSTR kMagnifierWindowClass = L"Magnifier"; +static LPCTSTR kMagnifierWindowName = L"MagnifierWindow"; + +Atomic32 ScreenCapturerWinMagnifier::tls_index_(TLS_OUT_OF_INDEXES); + +ScreenCapturerWinMagnifier::ScreenCapturerWinMagnifier( + scoped_ptr fallback_capturer) + : fallback_capturer_(fallback_capturer.Pass()), + fallback_capturer_started_(false), + callback_(NULL), + current_screen_id_(kFullDesktopScreenId), + excluded_window_(NULL), + set_thread_execution_state_failed_(false), + desktop_dc_(NULL), + mag_lib_handle_(NULL), + mag_initialize_func_(NULL), + mag_uninitialize_func_(NULL), + set_window_source_func_(NULL), + set_window_filter_list_func_(NULL), + set_image_scaling_callback_func_(NULL), + host_window_(NULL), + magnifier_window_(NULL), + magnifier_initialized_(false), + magnifier_capture_succeeded_(true) { +} + +ScreenCapturerWinMagnifier::~ScreenCapturerWinMagnifier() { + // DestroyWindow must be called before MagUninitialize. magnifier_window_ is + // destroyed automatically when host_window_ is destroyed. + if (host_window_) + DestroyWindow(host_window_); + + if (magnifier_initialized_) + mag_uninitialize_func_(); + + if (mag_lib_handle_) + FreeLibrary(mag_lib_handle_); + + if (desktop_dc_) + ReleaseDC(NULL, desktop_dc_); +} + +void ScreenCapturerWinMagnifier::Start(Callback* callback) { + assert(!callback_); + assert(callback); + callback_ = callback; + + InitializeMagnifier(); +} + +void ScreenCapturerWinMagnifier::Capture(const DesktopRegion& region) { + TickTime capture_start_time = TickTime::Now(); + + queue_.MoveToNextFrame(); + + // Request that the system not power-down the system, or the display hardware. + if (!SetThreadExecutionState(ES_DISPLAY_REQUIRED | ES_SYSTEM_REQUIRED)) { + if (!set_thread_execution_state_failed_) { + set_thread_execution_state_failed_ = true; + LOG_F(LS_WARNING) << "Failed to make system & display power assertion: " + << GetLastError(); + } + } + // Switch to the desktop receiving user input if different from the current + // one. + scoped_ptr input_desktop(Desktop::GetInputDesktop()); + if (input_desktop.get() != NULL && !desktop_.IsSame(*input_desktop)) { + // Release GDI resources otherwise SetThreadDesktop will fail. + if (desktop_dc_) { + ReleaseDC(NULL, desktop_dc_); + desktop_dc_ = NULL; + } + // If SetThreadDesktop() fails, the thread is still assigned a desktop. + // So we can continue capture screen bits, just from the wrong desktop. + desktop_.SetThreadDesktop(input_desktop.release()); + } + + bool succeeded = false; + + // Do not try to use the magnfiier if it's capturing non-primary screen, or it + // failed before. + if (magnifier_initialized_ && IsCapturingPrimaryScreenOnly() && + magnifier_capture_succeeded_) { + DesktopRect rect = GetScreenRect(current_screen_id_, current_device_key_); + CreateCurrentFrameIfNecessary(rect.size()); + + // CaptureImage may fail in some situations, e.g. windows8 metro mode. + succeeded = CaptureImage(rect); + } + + // Defer to the fallback capturer if magnifier capturer did not work. + if (!succeeded) { + LOG_F(LS_WARNING) << "Switching to the fallback screen capturer."; + StartFallbackCapturer(); + fallback_capturer_->Capture(region); + return; + } + + const DesktopFrame* current_frame = queue_.current_frame(); + const DesktopFrame* last_frame = queue_.previous_frame(); + if (last_frame && last_frame->size().equals(current_frame->size())) { + // Make sure the differencer is set up correctly for these previous and + // current screens. + if (!differ_.get() || (differ_->width() != current_frame->size().width()) || + (differ_->height() != current_frame->size().height()) || + (differ_->bytes_per_row() != current_frame->stride())) { + differ_.reset(new Differ(current_frame->size().width(), + current_frame->size().height(), + DesktopFrame::kBytesPerPixel, + current_frame->stride())); + } + + // Calculate difference between the two last captured frames. + DesktopRegion region; + differ_->CalcDirtyRegion( + last_frame->data(), current_frame->data(), ®ion); + helper_.InvalidateRegion(region); + } else { + // No previous frame is available, or the screen is resized. Invalidate the + // whole screen. + helper_.InvalidateScreen(current_frame->size()); + } + + helper_.set_size_most_recent(current_frame->size()); + + // Emit the current frame. + DesktopFrame* frame = queue_.current_frame()->Share(); + frame->set_dpi(DesktopVector(GetDeviceCaps(desktop_dc_, LOGPIXELSX), + GetDeviceCaps(desktop_dc_, LOGPIXELSY))); + frame->mutable_updated_region()->Clear(); + helper_.TakeInvalidRegion(frame->mutable_updated_region()); + frame->set_capture_time_ms( + (TickTime::Now() - capture_start_time).Milliseconds()); + callback_->OnCaptureCompleted(frame); +} + +void ScreenCapturerWinMagnifier::SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) { + assert(false); // NOTREACHED(); +} + +bool ScreenCapturerWinMagnifier::GetScreenList(ScreenList* screens) { + return webrtc::GetScreenList(screens); +} + +bool ScreenCapturerWinMagnifier::SelectScreen(ScreenId id) { + bool valid = IsScreenValid(id, ¤t_device_key_); + + // Set current_screen_id_ even if the fallback capturer is being used, so we + // can switch back to the magnifier when possible. + if (valid) + current_screen_id_ = id; + + if (fallback_capturer_started_) + fallback_capturer_->SelectScreen(id); + + return valid; +} + +void ScreenCapturerWinMagnifier::SetExcludedWindow(WindowId excluded_window) { + excluded_window_ = (HWND)excluded_window; + if (excluded_window_ && magnifier_initialized_) { + set_window_filter_list_func_( + magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_); + } +} + +bool ScreenCapturerWinMagnifier::CaptureImage(const DesktopRect& rect) { + assert(magnifier_initialized_); + + // Set the magnifier control to cover the captured rect. The content of the + // magnifier control will be the captured image. + BOOL result = SetWindowPos(magnifier_window_, + NULL, + rect.left(), rect.top(), + rect.width(), rect.height(), + 0); + if (!result) { + LOG_F(LS_WARNING) << "Failed to call SetWindowPos: " << GetLastError() + << ". Rect = {" << rect.left() << ", " << rect.top() + << ", " << rect.right() << ", " << rect.bottom() << "}"; + return false; + } + + magnifier_capture_succeeded_ = false; + + RECT native_rect = {rect.left(), rect.top(), rect.right(), rect.bottom()}; + + // OnCaptured will be called via OnMagImageScalingCallback and fill in the + // frame before set_window_source_func_ returns. + result = set_window_source_func_(magnifier_window_, native_rect); + + if (!result) { + LOG_F(LS_WARNING) << "Failed to call MagSetWindowSource: " << GetLastError() + << ". Rect = {" << rect.left() << ", " << rect.top() + << ", " << rect.right() << ", " << rect.bottom() << "}"; + return false; + } + + return magnifier_capture_succeeded_; +} + +BOOL ScreenCapturerWinMagnifier::OnMagImageScalingCallback( + HWND hwnd, + void* srcdata, + MAGIMAGEHEADER srcheader, + void* destdata, + MAGIMAGEHEADER destheader, + RECT unclipped, + RECT clipped, + HRGN dirty) { + assert(tls_index_.Value() != TLS_OUT_OF_INDEXES); + + ScreenCapturerWinMagnifier* owner = + reinterpret_cast( + TlsGetValue(tls_index_.Value())); + + owner->OnCaptured(srcdata, srcheader); + + return TRUE; +} + +bool ScreenCapturerWinMagnifier::InitializeMagnifier() { + assert(!magnifier_initialized_); + + desktop_dc_ = GetDC(NULL); + + mag_lib_handle_ = LoadLibrary(L"Magnification.dll"); + if (!mag_lib_handle_) + return false; + + // Initialize Magnification API function pointers. + mag_initialize_func_ = reinterpret_cast( + GetProcAddress(mag_lib_handle_, "MagInitialize")); + mag_uninitialize_func_ = reinterpret_cast( + GetProcAddress(mag_lib_handle_, "MagUninitialize")); + set_window_source_func_ = reinterpret_cast( + GetProcAddress(mag_lib_handle_, "MagSetWindowSource")); + set_window_filter_list_func_ = reinterpret_cast( + GetProcAddress(mag_lib_handle_, "MagSetWindowFilterList")); + set_image_scaling_callback_func_ = + reinterpret_cast( + GetProcAddress(mag_lib_handle_, "MagSetImageScalingCallback")); + + if (!mag_initialize_func_ || !mag_uninitialize_func_ || + !set_window_source_func_ || !set_window_filter_list_func_ || + !set_image_scaling_callback_func_) { + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "library functions missing."; + return false; + } + + BOOL result = mag_initialize_func_(); + if (!result) { + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from MagInitialize " << GetLastError(); + return false; + } + + HMODULE hInstance = NULL; + result = GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(&DefWindowProc), + &hInstance); + if (!result) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from GetModulehandleExA " << GetLastError(); + return false; + } + + // Register the host window class. See the MSDN documentation of the + // Magnification API for more infomation. + WNDCLASSEX wcex = {}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = &DefWindowProc; + wcex.hInstance = hInstance; + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.lpszClassName = kMagnifierHostClass; + + // Ignore the error which may happen when the class is already registered. + RegisterClassEx(&wcex); + + // Create the host window. + host_window_ = CreateWindowEx(WS_EX_LAYERED, + kMagnifierHostClass, + kHostWindowName, + 0, + 0, 0, 0, 0, + NULL, + NULL, + hInstance, + NULL); + if (!host_window_) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from creating host window " << GetLastError(); + return false; + } + + // Create the magnifier control. + magnifier_window_ = CreateWindow(kMagnifierWindowClass, + kMagnifierWindowName, + WS_CHILD | WS_VISIBLE, + 0, 0, 0, 0, + host_window_, + NULL, + hInstance, + NULL); + if (!magnifier_window_) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from creating magnifier window " + << GetLastError(); + return false; + } + + // Hide the host window. + ShowWindow(host_window_, SW_HIDE); + + // Set the scaling callback to receive captured image. + result = set_image_scaling_callback_func_( + magnifier_window_, + &ScreenCapturerWinMagnifier::OnMagImageScalingCallback); + if (!result) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from MagSetImageScalingCallback " + << GetLastError(); + return false; + } + + if (excluded_window_) { + result = set_window_filter_list_func_( + magnifier_window_, MW_FILTERMODE_EXCLUDE, 1, &excluded_window_); + if (!result) { + mag_uninitialize_func_(); + LOG_F(LS_WARNING) << "Failed to initialize ScreenCapturerWinMagnifier: " + << "error from MagSetWindowFilterList " + << GetLastError(); + return false; + } + } + + if (tls_index_.Value() == TLS_OUT_OF_INDEXES) { + // More than one threads may get here at the same time, but only one will + // write to tls_index_ using CompareExchange. + DWORD new_tls_index = TlsAlloc(); + if (!tls_index_.CompareExchange(new_tls_index, TLS_OUT_OF_INDEXES)) + TlsFree(new_tls_index); + } + + assert(tls_index_.Value() != TLS_OUT_OF_INDEXES); + TlsSetValue(tls_index_.Value(), this); + + magnifier_initialized_ = true; + return true; +} + +void ScreenCapturerWinMagnifier::OnCaptured(void* data, + const MAGIMAGEHEADER& header) { + DesktopFrame* current_frame = queue_.current_frame(); + + // Verify the format. + // TODO(jiayl): support capturing sources with pixel formats other than RGBA. + int captured_bytes_per_pixel = header.cbSize / header.width / header.height; + if (header.format != GUID_WICPixelFormat32bppRGBA || + header.width != static_cast(current_frame->size().width()) || + header.height != static_cast(current_frame->size().height()) || + header.stride != static_cast(current_frame->stride()) || + captured_bytes_per_pixel != DesktopFrame::kBytesPerPixel) { + LOG_F(LS_WARNING) << "Output format does not match the captured format: " + << "width = " << header.width << ", " + << "height = " << header.height << ", " + << "stride = " << header.stride << ", " + << "bpp = " << captured_bytes_per_pixel << ", " + << "pixel format RGBA ? " + << (header.format == GUID_WICPixelFormat32bppRGBA) << "."; + return; + } + + // Copy the data into the frame. + current_frame->CopyPixelsFrom( + reinterpret_cast(data), + header.stride, + DesktopRect::MakeXYWH(0, 0, header.width, header.height)); + + magnifier_capture_succeeded_ = true; +} + +void ScreenCapturerWinMagnifier::CreateCurrentFrameIfNecessary( + const DesktopSize& size) { + // If the current buffer is from an older generation then allocate a new one. + // Note that we can't reallocate other buffers at this point, since the caller + // may still be reading from them. + if (!queue_.current_frame() || !queue_.current_frame()->size().equals(size)) { + size_t buffer_size = + size.width() * size.height() * DesktopFrame::kBytesPerPixel; + SharedMemory* shared_memory = callback_->CreateSharedMemory(buffer_size); + + scoped_ptr buffer; + if (shared_memory) { + buffer.reset(new SharedMemoryDesktopFrame( + size, size.width() * DesktopFrame::kBytesPerPixel, shared_memory)); + } else { + buffer.reset(new BasicDesktopFrame(size)); + } + queue_.ReplaceCurrentFrame(buffer.release()); + } +} + +bool ScreenCapturerWinMagnifier::IsCapturingPrimaryScreenOnly() const { + if (current_screen_id_ != kFullDesktopScreenId) + return current_screen_id_ == 0; // the primary screen is always '0'. + + return GetSystemMetrics(SM_CMONITORS) == 1; +} + +void ScreenCapturerWinMagnifier::StartFallbackCapturer() { + assert(fallback_capturer_); + if (!fallback_capturer_started_) { + fallback_capturer_started_ = true; + + fallback_capturer_->Start(callback_); + fallback_capturer_->SelectScreen(current_screen_id_); + } +} + +} // namespace webrtc diff --git a/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h new file mode 100644 index 000000000..618386a9d --- /dev/null +++ b/webrtc/modules/desktop_capture/win/screen_capturer_win_magnifier.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ +#define WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_ + +#include +#include +#include + +#include "webrtc/modules/desktop_capture/screen_capture_frame_queue.h" +#include "webrtc/modules/desktop_capture/screen_capturer_helper.h" +#include "webrtc/modules/desktop_capture/screen_capturer.h" +#include "webrtc/modules/desktop_capture/win/scoped_thread_desktop.h" +#include "webrtc/system_wrappers/interface/atomic32.h" +#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class DesktopFrame; +class DesktopRect; +class Differ; +class MouseShapeObserver; + +// Captures the screen using the Magnification API to support window exclusion. +// Each capturer must run on a dedicated thread because it uses thread local +// storage for redirecting the library callback. Also the thread must have a UI +// message loop to handle the window messages for the magnifier window. +class ScreenCapturerWinMagnifier : public ScreenCapturer { + public: + // |fallback_capturer| will be used to capture the screen if a non-primary + // screen is being captured, or the OS does not support Magnification API, or + // the magnifier capturer fails (e.g. in Windows8 Metro mode). + explicit ScreenCapturerWinMagnifier( + scoped_ptr fallback_capturer); + virtual ~ScreenCapturerWinMagnifier(); + + // Overridden from ScreenCapturer: + virtual void Start(Callback* callback) OVERRIDE; + virtual void Capture(const DesktopRegion& region) OVERRIDE; + virtual void SetMouseShapeObserver( + MouseShapeObserver* mouse_shape_observer) OVERRIDE; + virtual bool GetScreenList(ScreenList* screens) OVERRIDE; + virtual bool SelectScreen(ScreenId id) OVERRIDE; + virtual void SetExcludedWindow(WindowId window) OVERRIDE; + + private: + typedef BOOL(WINAPI* MagImageScalingCallback)(HWND hwnd, + void* srcdata, + MAGIMAGEHEADER srcheader, + void* destdata, + MAGIMAGEHEADER destheader, + RECT unclipped, + RECT clipped, + HRGN dirty); + typedef BOOL(WINAPI* MagInitializeFunc)(void); + typedef BOOL(WINAPI* MagUninitializeFunc)(void); + typedef BOOL(WINAPI* MagSetWindowSourceFunc)(HWND hwnd, RECT rect); + typedef BOOL(WINAPI* MagSetWindowFilterListFunc)(HWND hwnd, + DWORD dwFilterMode, + int count, + HWND* pHWND); + typedef BOOL(WINAPI* MagSetImageScalingCallbackFunc)( + HWND hwnd, + MagImageScalingCallback callback); + + static BOOL WINAPI OnMagImageScalingCallback(HWND hwnd, + void* srcdata, + MAGIMAGEHEADER srcheader, + void* destdata, + MAGIMAGEHEADER destheader, + RECT unclipped, + RECT clipped, + HRGN dirty); + + // Captures the screen within |rect| in the desktop coordinates. Returns true + // if succeeded. + // It can only capture the primary screen for now. The magnification library + // crashes under some screen configurations (e.g. secondary screen on top of + // primary screen) if it tries to capture a non-primary screen. The caller + // must make sure not calling it on non-primary screens. + bool CaptureImage(const DesktopRect& rect); + + // Helper method for setting up the magnifier control. Returns true if + // succeeded. + bool InitializeMagnifier(); + + // Called by OnMagImageScalingCallback to output captured data. + void OnCaptured(void* data, const MAGIMAGEHEADER& header); + + // Makes sure the current frame exists and matches |size|. + void CreateCurrentFrameIfNecessary(const DesktopSize& size); + + // Returns true if we are capturing the primary screen only. + bool IsCapturingPrimaryScreenOnly() const; + + // Start the fallback capturer and select the screen. + void StartFallbackCapturer(); + + static Atomic32 tls_index_; + + scoped_ptr fallback_capturer_; + bool fallback_capturer_started_; + Callback* callback_; + ScreenId current_screen_id_; + std::wstring current_device_key_; + HWND excluded_window_; + + // A thread-safe list of invalid rectangles, and the size of the most + // recently captured screen. + ScreenCapturerHelper helper_; + + // Queue of the frames buffers. + ScreenCaptureFrameQueue queue_; + + // Class to calculate the difference between two screen bitmaps. + scoped_ptr differ_; + + // Used to suppress duplicate logging of SetThreadExecutionState errors. + bool set_thread_execution_state_failed_; + + ScopedThreadDesktop desktop_; + + // Used for getting the screen dpi. + HDC desktop_dc_; + + HMODULE mag_lib_handle_; + MagInitializeFunc mag_initialize_func_; + MagUninitializeFunc mag_uninitialize_func_; + MagSetWindowSourceFunc set_window_source_func_; + MagSetWindowFilterListFunc set_window_filter_list_func_; + MagSetImageScalingCallbackFunc set_image_scaling_callback_func_; + + // The hidden window hosting the magnifier control. + HWND host_window_; + // The magnifier control that captures the screen. + HWND magnifier_window_; + + // True if the magnifier control has been successfully initialized. + bool magnifier_initialized_; + + // True if the last OnMagImageScalingCallback was called and handled + // successfully. Reset at the beginning of each CaptureImage call. + bool magnifier_capture_succeeded_; + + DISALLOW_COPY_AND_ASSIGN(ScreenCapturerWinMagnifier); +}; + +} // namespace webrtc + +#endif // WEBRTC_MODULES_DESKTOP_CAPTURE_WIN_SCREEN_CAPTURER_WIN_MAGNIFIER_H_