// 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/output_controller.h"
#include <wayland-server-core.h>
#include "components/exo/wayland/output_controller_test_api.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 "ui/base/wayland/wayland_display_util.h"
namespace exo::wayland {
namespace {
class MockOutputControllerDelegate : public OutputController::Delegate {
public:
MockOutputControllerDelegate() : wayland_display_(wl_display_create()) {}
~MockOutputControllerDelegate() override {
wl_display_destroy(wayland_display_.ExtractAsDangling());
}
// OutputController::Delegate:
wl_display* GetWaylandDisplay() override { return wayland_display_; }
MOCK_METHOD(void, Flush, (), (override));
private:
raw_ptr<wl_display> wayland_display_;
};
} // namespace
class OutputControllerTest : public test::WaylandServerTest {
public:
OutputControllerTest() = default;
OutputControllerTest(const OutputControllerTest&) = delete;
OutputControllerTest& operator=(const OutputControllerTest&) = delete;
~OutputControllerTest() override = default;
protected:
MockOutputControllerDelegate delegate_;
};
TEST_F(OutputControllerTest, OutputControllerInitialization) {
UpdateDisplay("800x600,800x600");
const auto* screen = display::Screen::GetScreen();
const int64_t primary_id = screen->GetAllDisplays()[0].id();
const int64_t secondary_id = screen->GetAllDisplays()[1].id();
ASSERT_EQ(2u, screen->GetAllDisplays().size());
// OutputController should reflect the display state in display manager after
// initialization.
EXPECT_CALL(delegate_, Flush()).Times(1);
OutputController output_controller(&delegate_);
OutputControllerTestApi output_controller_test_api(output_controller);
EXPECT_EQ(2u, output_controller_test_api.GetOutputMap().size());
EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(primary_id));
EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(secondary_id));
}
TEST_F(OutputControllerTest, OutputControllerRemoveDisplay) {
UpdateDisplay("800x600,800x600");
const auto* screen = display::Screen::GetScreen();
const int64_t primary_id = screen->GetAllDisplays()[0].id();
const int64_t secondary_id = screen->GetAllDisplays()[1].id();
ASSERT_EQ(2u, screen->GetAllDisplays().size());
EXPECT_CALL(delegate_, Flush()).Times(2);
OutputController output_controller(&delegate_);
OutputControllerTestApi output_controller_test_api(output_controller);
EXPECT_EQ(2u, output_controller_test_api.GetOutputMap().size());
EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(primary_id));
EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(secondary_id));
// Remove the secondary display.
UpdateDisplay("800x600");
EXPECT_EQ(1u, output_controller_test_api.GetOutputMap().size());
EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(primary_id));
}
TEST_F(OutputControllerTest, OutputControllerAddDisplay) {
UpdateDisplay("800x600");
const auto* screen = display::Screen::GetScreen();
const int64_t primary_id = screen->GetAllDisplays()[0].id();
ASSERT_EQ(1u, screen->GetAllDisplays().size());
EXPECT_CALL(delegate_, Flush()).Times(2);
OutputController output_controller(&delegate_);
OutputControllerTestApi output_controller_test_api(output_controller);
EXPECT_EQ(1u, output_controller_test_api.GetOutputMap().size());
EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(primary_id));
// Add a second display.
UpdateDisplay("800x600,800x600");
const int64_t secondary_id = screen->GetAllDisplays()[1].id();
ASSERT_EQ(2u, screen->GetAllDisplays().size());
EXPECT_EQ(2u, output_controller_test_api.GetOutputMap().size());
EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(primary_id));
EXPECT_TRUE(output_controller_test_api.GetWaylandDisplayOutput(secondary_id));
}
// Regression test for b/323403137. Tests that exo will respect display
// activation events in the case observers trigger activation updates before the
// controller has had the opportunity to update output state.
TEST_F(OutputControllerTest, ActiveDisplay) {
// Activates a second display when added to the system.
class SecondaryDisplayActivator : public display::DisplayManagerObserver {
public:
SecondaryDisplayActivator() {
display_manager_observation_.Observe(
ash::Shell::Get()->display_manager());
}
// display::DisplayManagerObserver:
void OnDidProcessDisplayChanges(
const DisplayConfigurationChange& configuration_change) override {
auto* screen = display::Screen::GetScreen();
EXPECT_EQ(2u, screen->GetAllDisplays().size());
screen->SetDisplayForNewWindows(screen->GetAllDisplays()[1].id());
}
private:
base::ScopedObservation<display::DisplayManager,
display::DisplayManagerObserver>
display_manager_observation_{this};
};
// Setup the environment with a single display.
UpdateDisplay("800x600");
auto* screen = display::Screen::GetScreen();
const int64_t primary_id = screen->GetAllDisplays()[0].id();
ASSERT_EQ(1u, screen->GetAllDisplays().size());
OutputController output_controller(&delegate_);
OutputControllerTestApi output_controller_test_api(output_controller);
// Force an activation notification for the primary display.
screen->SetDisplayForNewWindows(primary_id);
EXPECT_EQ(primary_id,
output_controller_test_api.GetDispatchedActivatedDisplayId());
// Update the display manager observer ordering such that the display
// activator is notified before the output controller.
SecondaryDisplayActivator secondary_display_activator;
output_controller_test_api.ResetDisplayManagerObservation();
// Add a secondary display. The display activator will send a display
// activation notification before the controller processes the display update.
// Make sure the activation event is preserved.
UpdateDisplay("800x600,800x600");
ASSERT_EQ(2u, screen->GetAllDisplays().size());
const int64_t secondary_id = screen->GetAllDisplays()[1].id();
EXPECT_EQ(secondary_id,
output_controller_test_api.GetDispatchedActivatedDisplayId());
}
} // namespace exo::wayland