Add GetTopLeft to WgcCaptureSource to facilitate cursor capture.

This change disables native cursor capture in WgcCapturerWin to better
support the existing idiom of wrapping a capturer in a
DesktopAndCursorComposer. That means we also need to set the top_left
property of output DesktopFrames, so I've also implemented GetTopLeft in
WgcCaptureSource to facilitate this. I've also added a few unit tests
for WgcCaptureSource.

Bug: webrtc:12654
Change-Id: I5c9988a6f8548b584451b073ac29fbb482e09e2e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/215102
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Jamie Walch <jamiewalch@chromium.org>
Commit-Queue: Austin Orion <auorion@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#33821}
This commit is contained in:
Austin Orion 2021-04-22 13:22:25 -07:00 committed by Commit Bot
parent 70efbb839b
commit 66241e4fa4
11 changed files with 202 additions and 13 deletions

View File

@ -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",

View File

@ -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) {

View File

@ -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

View File

@ -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);
}

View File

@ -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);

View File

@ -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<HWND>(GetSourceId()), &window_rect))
return DesktopVector();
return window_rect.top_left();
}
bool WgcWindowSource::IsCapturable() {
if (!IsWindowValidAndVisible(reinterpret_cast<HWND>(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;

View File

@ -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:

View File

@ -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 <windows.graphics.capture.h>
#include <wrl/client.h>
#include <utility>
#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<SourceType> {
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>(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<DesktopCapturer::SourceId>(window_info_.hwnd);
source_factory_ = std::make_unique<WgcWindowSourceFactory>();
}
void SetUpForScreenSource() {
source_id_ = kFullDesktopScreenId;
source_factory_ = std::make_unique<WgcScreenSourceFactory>();
}
protected:
std::unique_ptr<ScopedCOMInitializer> com_initializer_;
std::unique_ptr<WgcCaptureSourceFactory> source_factory_;
std::unique_ptr<WgcCaptureSource> 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<ABI::Windows::Graphics::Capture::IGraphicsCaptureItem>
item;
EXPECT_TRUE(SUCCEEDED(source_->GetCaptureItem(&item)));
EXPECT_TRUE(item);
}
INSTANTIATE_TEST_SUITE_P(SourceAgnostic,
WgcCaptureSourceTest,
::testing::Values(SourceType::kWindowSource,
SourceType::kScreenSource));
} // namespace webrtc

View File

@ -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));

View File

@ -77,7 +77,7 @@ class WgcCapturerWinTest : public ::testing::TestWithParam<CaptureType>,
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();
}

View File

@ -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
}