Skip to content

Commit

Permalink
Add GetTopLeft to WgcCaptureSource to facilitate cursor capture.
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Jamie Walch <[email protected]>
Commit-Queue: Austin Orion <[email protected]>
Cr-Commit-Position: refs/heads/master@{#33821}
  • Loading branch information
auorion authored and Commit Bot committed Apr 23, 2021
1 parent 70efbb8 commit 66241e4
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 13 deletions.
5 changes: 4 additions & 1 deletion modules/desktop_capture/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 12 additions & 0 deletions modules/desktop_capture/win/screen_capture_utils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
4 changes: 4 additions & 0 deletions modules/desktop_capture/win/screen_capture_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion modules/desktop_capture/win/test_support/test_window.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
2 changes: 2 additions & 0 deletions modules/desktop_capture/win/test_support/test_window.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
33 changes: 25 additions & 8 deletions modules/desktop_capture/win/wgc_capture_source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions modules/desktop_capture/win/wgc_capture_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -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(
Expand Down Expand Up @@ -93,6 +95,7 @@ class WgcWindowSource final : public WgcCaptureSource {

~WgcWindowSource() override;

DesktopVector GetTopLeft() override;
bool IsCapturable() override;
bool FocusOnSource() override;

Expand All @@ -113,6 +116,7 @@ class WgcScreenSource final : public WgcCaptureSource {

~WgcScreenSource() override;

DesktopVector GetTopLeft() override;
bool IsCapturable() override;

private:
Expand Down
138 changes: 138 additions & 0 deletions modules/desktop_capture/win/wgc_capture_source_unittest.cc
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions modules/desktop_capture/win/wgc_capturer_win.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
2 changes: 1 addition & 1 deletion modules/desktop_capture/win/wgc_capturer_win_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
4 changes: 2 additions & 2 deletions webrtc.gni
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit 66241e4

Please sign in to comment.