diff --git a/modules/desktop_capture/BUILD.gn b/modules/desktop_capture/BUILD.gn index 3108572348..25b92bed45 100644 --- a/modules/desktop_capture/BUILD.gn +++ b/modules/desktop_capture/BUILD.gn @@ -145,7 +145,10 @@ if (rtc_include_tests) { sources += [ "screen_capturer_mac_unittest.cc" ] } if (rtc_enable_win_wgc) { - sources += [ "win/wgc_capturer_win_unittest.cc" ] + sources += [ + "win/wgc_capture_source_unittest.cc", + "win/wgc_capturer_win_unittest.cc", + ] } deps += [ ":desktop_capture_mock", diff --git a/modules/desktop_capture/win/screen_capture_utils.cc b/modules/desktop_capture/win/screen_capture_utils.cc index b66e4912d8..53b6dd399c 100644 --- a/modules/desktop_capture/win/screen_capture_utils.cc +++ b/modules/desktop_capture/win/screen_capture_utils.cc @@ -99,6 +99,18 @@ bool IsMonitorValid(const HMONITOR monitor) { return GetMonitorInfoA(monitor, &monitor_info); } +DesktopRect GetMonitorRect(const HMONITOR monitor) { + MONITORINFO monitor_info; + monitor_info.cbSize = sizeof(MONITORINFO); + if (!GetMonitorInfoA(monitor, &monitor_info)) { + return DesktopRect(); + } + + return DesktopRect::MakeLTRB( + monitor_info.rcMonitor.left, monitor_info.rcMonitor.top, + monitor_info.rcMonitor.right, monitor_info.rcMonitor.bottom); +} + bool IsScreenValid(const DesktopCapturer::SourceId screen, std::wstring* device_key) { if (screen == kFullDesktopScreenId) { diff --git a/modules/desktop_capture/win/screen_capture_utils.h b/modules/desktop_capture/win/screen_capture_utils.h index 86d92e1d71..dc993dad25 100644 --- a/modules/desktop_capture/win/screen_capture_utils.h +++ b/modules/desktop_capture/win/screen_capture_utils.h @@ -37,6 +37,10 @@ bool GetHmonitorFromDeviceIndex(const DesktopCapturer::SourceId device_index, // WM_DISPLAYCHANGE message has been received. bool IsMonitorValid(const HMONITOR monitor); +// Returns the rect of the monitor identified by |monitor|, relative to the +// primary display's top-left. On failure, returns an empty rect. +DesktopRect GetMonitorRect(const HMONITOR monitor); + // 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 diff --git a/modules/desktop_capture/win/test_support/test_window.cc b/modules/desktop_capture/win/test_support/test_window.cc index bcbadecfaf..c07ff74aa5 100644 --- a/modules/desktop_capture/win/test_support/test_window.cc +++ b/modules/desktop_capture/win/test_support/test_window.cc @@ -75,8 +75,16 @@ WindowInfo CreateTestWindow(const WCHAR* window_title, } void ResizeTestWindow(const HWND hwnd, const int width, const int height) { + // SWP_NOMOVE results in the x and y params being ignored. ::SetWindowPos(hwnd, HWND_TOP, /*x-coord=*/0, /*y-coord=*/0, width, height, - SWP_SHOWWINDOW); + SWP_SHOWWINDOW | SWP_NOMOVE); + ::UpdateWindow(hwnd); +} + +void MoveTestWindow(const HWND hwnd, const int x, const int y) { + // SWP_NOSIZE results in the width and height params being ignored. + ::SetWindowPos(hwnd, HWND_TOP, x, y, /*width=*/0, /*height=*/0, + SWP_SHOWWINDOW | SWP_NOSIZE); ::UpdateWindow(hwnd); } diff --git a/modules/desktop_capture/win/test_support/test_window.h b/modules/desktop_capture/win/test_support/test_window.h index 05727684ea..8701dc990b 100644 --- a/modules/desktop_capture/win/test_support/test_window.h +++ b/modules/desktop_capture/win/test_support/test_window.h @@ -38,6 +38,8 @@ WindowInfo CreateTestWindow(const WCHAR* window_title, void ResizeTestWindow(const HWND hwnd, const int width, const int height); +void MoveTestWindow(const HWND hwnd, const int x, const int y); + void MinimizeTestWindow(const HWND hwnd); void UnminimizeTestWindow(const HWND hwnd); diff --git a/modules/desktop_capture/win/wgc_capture_source.cc b/modules/desktop_capture/win/wgc_capture_source.cc index 33a69fac50..93c49f5090 100644 --- a/modules/desktop_capture/win/wgc_capture_source.cc +++ b/modules/desktop_capture/win/wgc_capture_source.cc @@ -72,6 +72,14 @@ WgcWindowSource::WgcWindowSource(DesktopCapturer::SourceId source_id) : WgcCaptureSource(source_id) {} WgcWindowSource::~WgcWindowSource() = default; +DesktopVector WgcWindowSource::GetTopLeft() { + DesktopRect window_rect; + if (!GetWindowRect(reinterpret_cast(GetSourceId()), &window_rect)) + return DesktopVector(); + + return window_rect.top_left(); +} + bool WgcWindowSource::IsCapturable() { if (!IsWindowValidAndVisible(reinterpret_cast(GetSourceId()))) return false; @@ -113,17 +121,26 @@ HRESULT WgcWindowSource::CreateCaptureItem( } WgcScreenSource::WgcScreenSource(DesktopCapturer::SourceId source_id) - : WgcCaptureSource(source_id) {} + : WgcCaptureSource(source_id) { + // Getting the HMONITOR could fail if the source_id is invalid. In that case, + // we leave hmonitor_ uninitialized and |IsCapturable()| will fail. + HMONITOR hmon; + if (GetHmonitorFromDeviceIndex(GetSourceId(), &hmon)) + hmonitor_ = hmon; +} + WgcScreenSource::~WgcScreenSource() = default; -bool WgcScreenSource::IsCapturable() { - if (!hmonitor_) { - HMONITOR hmon; - if (!GetHmonitorFromDeviceIndex(GetSourceId(), &hmon)) - return false; +DesktopVector WgcScreenSource::GetTopLeft() { + if (!hmonitor_) + return DesktopVector(); - hmonitor_ = hmon; - } + return GetMonitorRect(*hmonitor_)->top_left(); +} + +bool WgcScreenSource::IsCapturable() { + if (!hmonitor_) + return false; if (!IsMonitorValid(*hmonitor_)) return false; diff --git a/modules/desktop_capture/win/wgc_capture_source.h b/modules/desktop_capture/win/wgc_capture_source.h index 5dee102281..135f92bb84 100644 --- a/modules/desktop_capture/win/wgc_capture_source.h +++ b/modules/desktop_capture/win/wgc_capture_source.h @@ -18,6 +18,7 @@ #include "absl/types/optional.h" #include "modules/desktop_capture/desktop_capturer.h" +#include "modules/desktop_capture/desktop_geometry.h" namespace webrtc { @@ -30,6 +31,7 @@ class WgcCaptureSource { explicit WgcCaptureSource(DesktopCapturer::SourceId source_id); virtual ~WgcCaptureSource(); + virtual DesktopVector GetTopLeft() = 0; virtual bool IsCapturable(); virtual bool FocusOnSource(); HRESULT GetCaptureItem( @@ -93,6 +95,7 @@ class WgcWindowSource final : public WgcCaptureSource { ~WgcWindowSource() override; + DesktopVector GetTopLeft() override; bool IsCapturable() override; bool FocusOnSource() override; @@ -113,6 +116,7 @@ class WgcScreenSource final : public WgcCaptureSource { ~WgcScreenSource() override; + DesktopVector GetTopLeft() override; bool IsCapturable() override; private: diff --git a/modules/desktop_capture/win/wgc_capture_source_unittest.cc b/modules/desktop_capture/win/wgc_capture_source_unittest.cc new file mode 100644 index 0000000000..a230e12578 --- /dev/null +++ b/modules/desktop_capture/win/wgc_capture_source_unittest.cc @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2021 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 "modules/desktop_capture/win/wgc_capture_source.h" + +#include +#include + +#include + +#include "modules/desktop_capture/desktop_capture_types.h" +#include "modules/desktop_capture/desktop_geometry.h" +#include "modules/desktop_capture/win/screen_capture_utils.h" +#include "modules/desktop_capture/win/test_support/test_window.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/win/scoped_com_initializer.h" +#include "rtc_base/win/windows_version.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +const WCHAR kWindowTitle[] = L"WGC Capture Source Test Window"; + +const int kFirstXCoord = 25; +const int kFirstYCoord = 50; +const int kSecondXCoord = 50; +const int kSecondYCoord = 75; + +enum SourceType { kWindowSource = 0, kScreenSource = 1 }; + +} // namespace + +class WgcCaptureSourceTest : public ::testing::TestWithParam { + public: + void SetUp() override { + if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_RS5) { + RTC_LOG(LS_INFO) + << "Skipping WgcCaptureSourceTests on Windows versions < RS5."; + GTEST_SKIP(); + } + + com_initializer_ = + std::make_unique(ScopedCOMInitializer::kMTA); + ASSERT_TRUE(com_initializer_->Succeeded()); + } + + void TearDown() override { + if (window_open_) { + DestroyTestWindow(window_info_); + } + } + + void SetUpForWindowSource() { + window_info_ = CreateTestWindow(kWindowTitle); + window_open_ = true; + source_id_ = reinterpret_cast(window_info_.hwnd); + source_factory_ = std::make_unique(); + } + + void SetUpForScreenSource() { + source_id_ = kFullDesktopScreenId; + source_factory_ = std::make_unique(); + } + + protected: + std::unique_ptr com_initializer_; + std::unique_ptr source_factory_; + std::unique_ptr source_; + DesktopCapturer::SourceId source_id_; + WindowInfo window_info_; + bool window_open_ = false; +}; + +// Window specific test +TEST_F(WgcCaptureSourceTest, WindowPosition) { + SetUpForWindowSource(); + source_ = source_factory_->CreateCaptureSource(source_id_); + ASSERT_TRUE(source_); + EXPECT_EQ(source_->GetSourceId(), source_id_); + + MoveTestWindow(window_info_.hwnd, kFirstXCoord, kFirstYCoord); + DesktopVector source_vector = source_->GetTopLeft(); + EXPECT_EQ(source_vector.x(), kFirstXCoord); + EXPECT_EQ(source_vector.y(), kFirstYCoord); + + MoveTestWindow(window_info_.hwnd, kSecondXCoord, kSecondYCoord); + source_vector = source_->GetTopLeft(); + EXPECT_EQ(source_vector.x(), kSecondXCoord); + EXPECT_EQ(source_vector.y(), kSecondYCoord); +} + +// Screen specific test +TEST_F(WgcCaptureSourceTest, ScreenPosition) { + SetUpForScreenSource(); + source_ = source_factory_->CreateCaptureSource(source_id_); + ASSERT_TRUE(source_); + EXPECT_EQ(source_id_, source_->GetSourceId()); + + DesktopRect screen_rect = GetFullscreenRect(); + DesktopVector source_vector = source_->GetTopLeft(); + EXPECT_EQ(source_vector.x(), screen_rect.left()); + EXPECT_EQ(source_vector.y(), screen_rect.top()); +} + +// Source agnostic test +TEST_P(WgcCaptureSourceTest, CreateSource) { + if (GetParam() == SourceType::kWindowSource) { + SetUpForWindowSource(); + } else { + SetUpForScreenSource(); + } + + source_ = source_factory_->CreateCaptureSource(source_id_); + ASSERT_TRUE(source_); + EXPECT_EQ(source_id_, source_->GetSourceId()); + EXPECT_TRUE(source_->IsCapturable()); + + Microsoft::WRL::ComPtr + item; + EXPECT_TRUE(SUCCEEDED(source_->GetCaptureItem(&item))); + EXPECT_TRUE(item); +} + +INSTANTIATE_TEST_SUITE_P(SourceAgnostic, + WgcCaptureSourceTest, + ::testing::Values(SourceType::kWindowSource, + SourceType::kScreenSource)); + +} // namespace webrtc diff --git a/modules/desktop_capture/win/wgc_capturer_win.cc b/modules/desktop_capture/win/wgc_capturer_win.cc index 83a1e342a3..88859b6e84 100644 --- a/modules/desktop_capture/win/wgc_capturer_win.cc +++ b/modules/desktop_capture/win/wgc_capturer_win.cc @@ -199,6 +199,7 @@ void WgcCapturerWin::CaptureFrame() { frame->set_capture_time_ms(capture_time_ms); frame->set_capturer_id(DesktopCapturerId::kWgcCapturerWin); frame->set_may_contain_cursor(true); + frame->set_top_left(capture_source_->GetTopLeft()); RecordWgcCapturerResult(WgcCapturerResult::kSuccess); callback_->OnCaptureResult(DesktopCapturer::Result::SUCCESS, std::move(frame)); diff --git a/modules/desktop_capture/win/wgc_capturer_win_unittest.cc b/modules/desktop_capture/win/wgc_capturer_win_unittest.cc index 1700ede6d9..ebfb576e63 100644 --- a/modules/desktop_capture/win/wgc_capturer_win_unittest.cc +++ b/modules/desktop_capture/win/wgc_capturer_win_unittest.cc @@ -77,7 +77,7 @@ class WgcCapturerWinTest : public ::testing::TestWithParam, void SetUp() override { if (rtc::rtc_win::GetVersion() < rtc::rtc_win::Version::VERSION_WIN10_RS5) { RTC_LOG(LS_INFO) - << "Skipping WgcWindowCaptureTests on Windows versions < RS5."; + << "Skipping WgcCapturerWinTests on Windows versions < RS5."; GTEST_SKIP(); } diff --git a/webrtc.gni b/webrtc.gni index ac6c325cbc..1d76567029 100644 --- a/webrtc.gni +++ b/webrtc.gni @@ -207,8 +207,8 @@ declare_args() { rtc_win_undef_unicode = false # When set to true, a capturer implementation that uses the - # Windows.Graphics.Capture APIs will be available for use. These APIs are - # available in the Win 10 SDK v10.0.19041. + # Windows.Graphics.Capture APIs will be available for use. This introduces a + # dependency on the Win 10 SDK v10.0.17763.0. rtc_enable_win_wgc = false }