// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/exo/wayland/zaura_output_manager_v2.h"
#include "base/bit_cast.h"
#include "components/exo/wayland/output_controller_test_api.h"
#include "components/exo/wayland/output_metrics.h"
#include "components/exo/wayland/server_util.h"
#include "components/exo/wayland/test/test_client.h"
#include "components/exo/wayland/test/wayland_server_test.h"
#include "components/exo/wayland/wayland_display_output.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace exo::wayland {
namespace {
using ::testing::_;
using ::testing::ExpectationSet;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::StrEq;
class MockGlobalsObserver : public clients::Globals::TestObserver {
public:
MOCK_METHOD(void,
OnRegistryGlobal,
(uint32_t id, const char* interface, uint32_t version),
(override));
MOCK_METHOD(void, OnRegistryGlobalRemove, (uint32_t id), (override));
};
class MockAuraOutputManagerListener {
public:
static void OnDone(void* data, zaura_output_manager_v2* output_manager) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnDone();
}
static void OnDisplayId(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
uint32_t display_id_hi,
uint32_t display_id_lo) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnDisplayId(
output_name, display_id_hi, display_id_lo);
}
static void OnLogicalPosition(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
int32_t x,
int32_t y) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnLogicalPosition(
output_name, x, y);
}
static void OnLogicalSize(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
int32_t width,
int32_t height) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnLogicalSize(
output_name, width, height);
}
static void OnPhysicalSize(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
int32_t width,
int32_t height) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnPhysicalSize(
output_name, width, height);
}
static void OnInsets(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
int32_t top,
int32_t left,
int32_t bottom,
int32_t right) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnInsets(
output_name, top, left, bottom, right);
}
static void OnDeviceScaleFactor(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
uint32_t scale_as_uint) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnDeviceScaleFactor(
output_name, scale_as_uint);
}
static void OnLogicalTransform(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
int32_t transform) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnLogicalTransform(
output_name, transform);
}
static void OnPanelTransform(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
int32_t transform) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnPanelTransform(
output_name, transform);
}
static void OnName(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
const char* name) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnName(output_name,
name);
}
static void OnDescription(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
const char* description) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnDescription(
output_name, description);
}
static void OnOverscanInsets(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name,
int32_t top,
int32_t left,
int32_t bottom,
int32_t right) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnOverscanInsets(
output_name, top, left, bottom, right);
}
static void OnActivated(void* data,
zaura_output_manager_v2* output_manager,
uint32_t output_name) {
static_cast<MockAuraOutputManagerListener*>(data)->MockOnActivated(
output_name);
}
MOCK_METHOD(void, MockOnDone, ());
MOCK_METHOD(void,
MockOnDisplayId,
(uint32_t output_name,
uint32_t display_id_hi,
uint32_t display_id_lo));
MOCK_METHOD(void,
MockOnLogicalPosition,
(uint32_t output_name, int32_t x, int32_t y));
MOCK_METHOD(void,
MockOnLogicalSize,
(uint32_t output_name, int32_t width, int32_t height));
MOCK_METHOD(void,
MockOnPhysicalSize,
(uint32_t output_name, int32_t width, int32_t height));
MOCK_METHOD(void,
MockOnInsets,
(uint32_t output_name,
int32_t top,
int32_t left,
int32_t bottom,
int32_t right));
MOCK_METHOD(void,
MockOnDeviceScaleFactor,
(uint32_t output_name, uint32_t scale_as_uint));
MOCK_METHOD(void,
MockOnLogicalTransform,
(uint32_t output_name, int32_t transform));
MOCK_METHOD(void,
MockOnPanelTransform,
(uint32_t output_name, int32_t transform));
MOCK_METHOD(void, MockOnName, (uint32_t output_name, const char* name));
MOCK_METHOD(void,
MockOnDescription,
(uint32_t output_name, const char* description));
MOCK_METHOD(void,
MockOnOverscanInsets,
(uint32_t output_name,
int32_t top,
int32_t left,
int32_t bottom,
int32_t right));
MOCK_METHOD(void, MockOnActivated, (uint32_t output_name));
};
// Server test asserting clients receive the events specified by the
// aura_output_manager_v2 interface in the order expected.
class AuraOutputManagerV2Test : public test::WaylandServerTest {
public:
// test::WaylandServerTest:
std::unique_ptr<test::TestClient> InitOnClientThread() override {
auto test_client = test::WaylandServerTest::InitOnClientThread();
test_client->globals().set_observer_for_testing(&mock_globals_observer_);
static constexpr zaura_output_manager_v2_listener
zaura_output_manager_v2_listener = {
&MockAuraOutputManagerListener::OnDone,
&MockAuraOutputManagerListener::OnDisplayId,
&MockAuraOutputManagerListener::OnLogicalPosition,
&MockAuraOutputManagerListener::OnLogicalSize,
&MockAuraOutputManagerListener::OnPhysicalSize,
&MockAuraOutputManagerListener::OnInsets,
&MockAuraOutputManagerListener::OnDeviceScaleFactor,
&MockAuraOutputManagerListener::OnLogicalTransform,
&MockAuraOutputManagerListener::OnPanelTransform,
&MockAuraOutputManagerListener::OnName,
&MockAuraOutputManagerListener::OnDescription,
&MockAuraOutputManagerListener::OnOverscanInsets,
&MockAuraOutputManagerListener::OnActivated};
zaura_output_manager_v2_add_listener(test_client->aura_output_manager_v2(),
&zaura_output_manager_v2_listener,
&mock_aura_output_manager_);
return test_client;
}
protected:
void ExpectMetrics(uint32_t output_name,
const OutputMetrics& metrics,
ExpectationSet& expectations) {
expectations +=
EXPECT_CALL(mock_aura_output_manager_,
MockOnDisplayId(output_name, metrics.display_id.high,
metrics.display_id.low));
expectations += EXPECT_CALL(
mock_aura_output_manager_,
MockOnLogicalPosition(output_name, metrics.logical_origin.x(),
metrics.logical_origin.y()));
expectations +=
EXPECT_CALL(mock_aura_output_manager_,
MockOnLogicalSize(output_name, metrics.logical_size.width(),
metrics.logical_size.height()));
expectations += EXPECT_CALL(
mock_aura_output_manager_,
MockOnPhysicalSize(output_name, metrics.physical_size_px.width(),
metrics.physical_size_px.height()));
expectations +=
EXPECT_CALL(mock_aura_output_manager_,
MockOnInsets(output_name, metrics.logical_insets.top(),
metrics.logical_insets.left(),
metrics.logical_insets.bottom(),
metrics.logical_insets.right()));
expectations += EXPECT_CALL(
mock_aura_output_manager_,
MockOnDeviceScaleFactor(output_name, base::bit_cast<uint32_t>(
metrics.device_scale_factor)));
expectations += EXPECT_CALL(
mock_aura_output_manager_,
MockOnLogicalTransform(output_name, metrics.logical_transform));
expectations +=
EXPECT_CALL(mock_aura_output_manager_,
MockOnPanelTransform(output_name, metrics.panel_transform));
expectations +=
EXPECT_CALL(mock_aura_output_manager_,
MockOnOverscanInsets(
output_name, metrics.physical_overscan_insets.top(),
metrics.physical_overscan_insets.left(),
metrics.physical_overscan_insets.bottom(),
metrics.physical_overscan_insets.right()));
}
NiceMock<MockAuraOutputManagerListener> mock_aura_output_manager_;
NiceMock<MockGlobalsObserver> mock_globals_observer_;
};
} // namespace
TEST_F(AuraOutputManagerV2Test, ActiveOutputMetricsUpdate) {
// Start with a single display and round-trip with client to clear the event
// queue.
UpdateDisplay("800x600");
PostToClientAndWait([] {});
const auto* screen = display::Screen::GetScreen();
ASSERT_EQ(1u, screen->GetAllDisplays().size());
const int64_t primary_id = screen->GetAllDisplays()[0].id();
auto* output_controller = server_->output_controller_for_testing();
OutputControllerTestApi output_controller_test_api(*output_controller);
const WaylandDisplayOutput* primary_output =
output_controller_test_api.GetWaylandDisplayOutput(primary_id);
const uint32_t primary_output_name =
wl_global_get_name(primary_output->global(), client_resource_.get());
// Update the display, expect to see updated metrics only followed by done.
OutputMetrics metrics = primary_output->metrics();
metrics.physical_size_px.SetSize(1200, 800);
metrics.logical_size.SetSize(1200, 800);
ExpectationSet expected_events;
expected_events += EXPECT_CALL(mock_globals_observer_,
OnRegistryGlobal(_, StrEq("wl_output"), _))
.Times(0);
expected_events +=
EXPECT_CALL(mock_globals_observer_, OnRegistryGlobalRemove(_)).Times(0);
ExpectMetrics(primary_output_name, metrics, expected_events);
EXPECT_CALL(mock_aura_output_manager_, MockOnDone()).After(expected_events);
UpdateDisplay("1200x800");
PostToClientAndWait([] {});
Mock::VerifyAndClearExpectations(&mock_aura_output_manager_);
Mock::VerifyAndClearExpectations(&mock_globals_observer_);
// Subsequent updates should send new updates as expected.
metrics = primary_output->metrics();
metrics.physical_size_px.SetSize(1600, 1200);
metrics.logical_size.SetSize(1600, 1200);
ExpectationSet new_expected_events;
new_expected_events += EXPECT_CALL(mock_globals_observer_,
OnRegistryGlobal(_, StrEq("wl_output"), _))
.Times(0);
new_expected_events +=
EXPECT_CALL(mock_globals_observer_, OnRegistryGlobalRemove(_)).Times(0);
ExpectMetrics(primary_output_name, metrics, new_expected_events);
EXPECT_CALL(mock_aura_output_manager_, MockOnDone())
.After(new_expected_events);
UpdateDisplay("1600x1200");
PostToClientAndWait([] {});
}
TEST_F(AuraOutputManagerV2Test, ActiveOutputsAdded) {
// Start with a single display and round-trip with client to clear the event
// queue.
UpdateDisplay("800x600");
const auto* screen = display::Screen::GetScreen();
ASSERT_EQ(1u, screen->GetAllDisplays().size());
PostToClientAndWait([](test::TestClient* client) {
ASSERT_EQ(1u, client->globals().outputs.size());
});
// Add two new displays to the configuration, events for two new outputs and
// their corresponding metrics should be propagated to clients.
ExpectationSet expected_events;
expected_events += EXPECT_CALL(mock_globals_observer_,
OnRegistryGlobal(_, StrEq("wl_output"), _))
.Times(2);
expected_events +=
EXPECT_CALL(mock_globals_observer_, OnRegistryGlobalRemove(_)).Times(0);
expected_events +=
EXPECT_CALL(mock_aura_output_manager_, MockOnLogicalSize(_, 1200, 800));
expected_events +=
EXPECT_CALL(mock_aura_output_manager_, MockOnLogicalSize(_, 1600, 1200));
EXPECT_CALL(mock_aura_output_manager_, MockOnDone()).After(expected_events);
UpdateDisplay("800x600,1200x800,1600x1200");
ASSERT_EQ(3u, screen->GetAllDisplays().size());
PostToClientAndWait([](test::TestClient* client) {
ASSERT_EQ(3u, client->globals().outputs.size());
});
}
TEST_F(AuraOutputManagerV2Test, ActiveOutputsRemoved) {
// Start multiple displays and round-trip with client to clear the event
// queue.
UpdateDisplay("800x600,1200x800,1600x1200");
const auto* screen = display::Screen::GetScreen();
ASSERT_EQ(3u, screen->GetAllDisplays().size());
PostToClientAndWait([](test::TestClient* client) {
ASSERT_EQ(3u, client->globals().outputs.size());
});
const int64_t secondary_id = screen->GetAllDisplays()[1].id();
const int64_t tertiary_id = screen->GetAllDisplays()[2].id();
auto* output_controller = server_->output_controller_for_testing();
OutputControllerTestApi output_controller_test_api(*output_controller);
const uint64_t secondary_output_name = wl_global_get_name(
output_controller_test_api.GetWaylandDisplayOutput(secondary_id)
->global(),
client_resource_.get());
const uint64_t tertiary_output_name = wl_global_get_name(
output_controller_test_api.GetWaylandDisplayOutput(tertiary_id)->global(),
client_resource_.get());
// Remove two displays from the configuration, events for the two removals
// should be propagated to clients.
ExpectationSet expected_events;
expected_events += EXPECT_CALL(mock_globals_observer_,
OnRegistryGlobal(_, StrEq("wl_output"), _))
.Times(0);
expected_events += EXPECT_CALL(mock_globals_observer_,
OnRegistryGlobalRemove(secondary_output_name));
expected_events += EXPECT_CALL(mock_globals_observer_,
OnRegistryGlobalRemove(tertiary_output_name));
expected_events +=
EXPECT_CALL(mock_aura_output_manager_, MockOnLogicalSize(_, _, _))
.Times(0);
EXPECT_CALL(mock_aura_output_manager_, MockOnDone()).After(expected_events);
UpdateDisplay("800x600");
ASSERT_EQ(1u, screen->GetAllDisplays().size());
// TODO(tluk): Update Globals to correctly handle wl_registry.global_remove
// events.
PostToClientAndWait([] {});
}
TEST_F(AuraOutputManagerV2Test, ActivateDisplay) {
// Start with a two displays and round-trip with client to clear the event
// queue.
auto* output_controller = server_->output_controller_for_testing();
OutputControllerTestApi output_controller_test_api(*output_controller);
UpdateDisplay("800x600,800x600");
auto* screen = display::Screen::GetScreen();
ASSERT_EQ(2u, screen->GetAllDisplays().size());
PostToClientAndWait([](test::TestClient* client) {
ASSERT_EQ(2u, client->globals().outputs.size());
});
const int64_t primary_id = screen->GetAllDisplays()[0].id();
const uint64_t primary_output_name = wl_global_get_name(
output_controller_test_api.GetWaylandDisplayOutput(primary_id)->global(),
client_resource_.get());
const int64_t secondary_id = screen->GetAllDisplays()[1].id();
const uint64_t secondary_output_name = wl_global_get_name(
output_controller_test_api.GetWaylandDisplayOutput(secondary_id)
->global(),
client_resource_.get());
// Force activation on the secondary display.
EXPECT_NE(secondary_id,
output_controller_test_api.GetDispatchedActivatedDisplayId());
screen->SetDisplayForNewWindows(secondary_id);
EXPECT_EQ(secondary_id,
output_controller_test_api.GetDispatchedActivatedDisplayId());
EXPECT_CALL(mock_aura_output_manager_,
MockOnActivated(secondary_output_name));
PostToClientAndWait([] {});
// Force activation back to the primary display.
screen->SetDisplayForNewWindows(primary_id);
EXPECT_EQ(primary_id,
output_controller_test_api.GetDispatchedActivatedDisplayId());
EXPECT_CALL(mock_aura_output_manager_, MockOnActivated(primary_output_name));
PostToClientAndWait([] {});
}
} // namespace exo::wayland