| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <wayland-server-protocol-core.h> |
| |
| #include "ash/shell.h" |
| #include "ash/test/test_widget_builder.h" |
| #include "ash/wm/overview/overview_controller.h" |
| #include "ash/wm/overview/overview_test_util.h" |
| #include "base/memory/raw_ptr.h" |
| #include "chromeos/ui/base/app_types.h" |
| #include "chromeos/ui/base/window_properties.h" |
| #include "components/exo/display.h" |
| #include "components/exo/wayland/test/client_util.h" |
| #include "components/exo/wayland/test/server_util.h" |
| #include "components/exo/wayland/test/wayland_server_test.h" |
| #include "components/exo/wayland/xdg_shell.h" |
| #include "components/exo/xdg_shell_surface.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/aura/client/aura_constants.h" |
| #include "ui/compositor/scoped_animation_duration_scale_mode.h" |
| |
| namespace exo::wayland { |
| namespace { |
| |
| class ClientData : public test::TestClient::CustomData { |
| public: |
| struct TestShellSurface { |
| std::unique_ptr<wl_surface> surface; |
| std::unique_ptr<wl_shell_surface> shell_surface; |
| std::unique_ptr<xdg_toplevel> xdg_toplevel; |
| std::unique_ptr<xdg_surface> xdg_surface; |
| }; |
| std::vector<TestShellSurface> test_surfaces_list; |
| }; |
| |
| class WaylandAuraShellServerTest : public test::WaylandServerTest { |
| public: |
| struct TestSurfaceKey { |
| test::ResourceKey surface_key; |
| test::ResourceKey shell_surface_key; |
| }; |
| |
| WaylandAuraShellServerTest() = default; |
| |
| WaylandAuraShellServerTest(const WaylandAuraShellServerTest&) = delete; |
| WaylandAuraShellServerTest& operator=(const WaylandAuraShellServerTest&) = |
| delete; |
| |
| ~WaylandAuraShellServerTest() override = default; |
| |
| // test::WaylandServerTest: |
| void SetUp() override { |
| WaylandServerTest::SetUp(); |
| display_ = server_->GetDisplay(); |
| } |
| |
| void TearDown() override { WaylandServerTest::TearDown(); } |
| |
| std::vector<TestSurfaceKey> SetupClientSurfaces( |
| int num_test_surfaces_list = 1) { |
| std::vector<TestSurfaceKey> keys; |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto data = std::make_unique<ClientData>(); |
| for (int i = 0; i < num_test_surfaces_list; ++i) { |
| ClientData::TestShellSurface test_surface; |
| test_surface.surface.reset( |
| wl_compositor_create_surface(client->compositor())); |
| |
| test_surface.xdg_surface.reset(xdg_wm_base_get_xdg_surface( |
| client->globals().xdg_wm_base.get(), test_surface.surface.get())); |
| test_surface.xdg_toplevel.reset( |
| xdg_surface_get_toplevel(test_surface.xdg_surface.get())); |
| |
| keys.push_back(TestSurfaceKey{ |
| .surface_key = |
| test::client_util::GetResourceKey(test_surface.surface.get()), |
| .shell_surface_key = test::client_util::GetResourceKey( |
| test_surface.xdg_surface.get()), |
| }); |
| data->test_surfaces_list.push_back(std::move(test_surface)); |
| } |
| client->set_data(std::move(data)); |
| }); |
| |
| return keys; |
| } |
| |
| void AttachBufferToSurfaces() { |
| PostToClientAndWait([&](test::TestClient* client) { |
| ASSERT_TRUE(client->InitShmBufferFactory(256 * 256 * 4)); |
| |
| auto* data = client->GetDataAs<ClientData>(); |
| for (auto& test_surface : data->test_surfaces_list) { |
| auto buffer = client->shm_buffer_factory()->CreateBuffer(0, 64, 64); |
| wl_surface_attach(test_surface.surface.get(), buffer->resource(), 0, 0); |
| wl_surface_commit(test_surface.surface.get()); |
| } |
| }); |
| } |
| |
| struct ShellObserver { |
| // For focus. |
| raw_ptr<wl_surface> gained_active; |
| raw_ptr<wl_surface> lost_active; |
| int32_t activated_call_count = 0; |
| |
| // For overview. |
| int32_t overview_entered_call_count = 0; |
| int32_t overview_exited_call_count = 0; |
| }; |
| |
| const zaura_shell_listener kAuraShellListener = { |
| [](void* data, struct zaura_shell* zaura_shell, uint32_t layout_mode) {}, |
| [](void* data, struct zaura_shell* zaura_shell, uint32_t id) {}, |
| [](void* data, |
| struct zaura_shell* zaura_shell, |
| struct wl_array* desk_names) {}, |
| [](void* data, |
| struct zaura_shell* zaura_shell, |
| int32_t active_desk_index) {}, |
| [](void* data, |
| struct zaura_shell* zaura_shell, |
| struct wl_surface* gained_active, |
| struct wl_surface* lost_active) { |
| auto* observer = static_cast<ShellObserver*>(data); |
| observer->gained_active = gained_active; |
| observer->lost_active = lost_active; |
| observer->activated_call_count++; |
| }, |
| [](void* data, struct zaura_shell* zaura_shell) { |
| auto* observer = static_cast<ShellObserver*>(data); |
| observer->overview_entered_call_count++; |
| }, |
| [](void* data, struct zaura_shell* zaura_shell) { |
| auto* observer = static_cast<ShellObserver*>(data); |
| observer->overview_exited_call_count++; |
| }}; |
| |
| std::unique_ptr<ShellObserver> SetupShellObservation() { |
| auto observer = std::make_unique<ShellObserver>(); |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_shell_add_listener(client->aura_shell(), &kAuraShellListener, |
| observer.get()); |
| }); |
| return observer; |
| } |
| |
| Surface* GetClientSurface(test::ResourceKey surface_key) { |
| return test::server_util::GetUserDataForResource<Surface>(server_.get(), |
| surface_key); |
| } |
| |
| raw_ptr<Display, DanglingUntriaged> display_; |
| }; |
| |
| // Home screen -> any window |
| TEST_F(WaylandAuraShellServerTest, HasFocusedClientChangedSendActivated) { |
| auto keys = SetupClientSurfaces(); |
| auto observer = SetupShellObservation(); |
| |
| Surface* surface = GetClientSurface(keys[0].surface_key); |
| ASSERT_TRUE(surface); |
| |
| display_->seat()->OnWindowFocused(surface->window(), nullptr); |
| // Wait until all wayland events are sent. |
| PostToClientAndWait([]() {}); |
| EXPECT_TRUE(observer->gained_active != nullptr); |
| EXPECT_TRUE(observer->lost_active == nullptr); |
| EXPECT_EQ(1, observer->activated_call_count); |
| } |
| |
| // Exo client window -> Same exo client another window |
| TEST_F(WaylandAuraShellServerTest, FocusedClientChangedSendActivated) { |
| auto keys = SetupClientSurfaces(2); |
| auto observer = SetupShellObservation(); |
| |
| Surface* surface = GetClientSurface(keys[0].surface_key); |
| ASSERT_TRUE(surface); |
| |
| display_->seat()->OnWindowFocused(surface->window(), nullptr); |
| // Reset previous gained and lost active info. |
| observer->gained_active = nullptr; |
| observer->lost_active = nullptr; |
| |
| Surface* surface2 = GetClientSurface(keys[1].surface_key); |
| ASSERT_TRUE(surface2); |
| display_->seat()->OnWindowFocused(surface2->window(), surface->window()); |
| // Wait until all wayland events are sent. |
| PostToClientAndWait([]() {}); |
| |
| EXPECT_TRUE(observer->gained_active != nullptr); |
| EXPECT_TRUE(observer->lost_active != nullptr); |
| EXPECT_EQ(2, observer->activated_call_count); |
| } |
| |
| // Exo client window -> Chrome window |
| TEST_F(WaylandAuraShellServerTest, FocusedClientChangedToNonExoSendActivated) { |
| auto keys = SetupClientSurfaces(2); |
| auto observer = SetupShellObservation(); |
| |
| Surface* surface = GetClientSurface(keys[0].surface_key); |
| ASSERT_TRUE(surface); |
| display_->seat()->OnWindowFocused(surface->window(), nullptr); |
| |
| // Reset previous gained and lost active info. |
| observer->gained_active = nullptr; |
| observer->lost_active = nullptr; |
| |
| Surface* surface2 = GetClientSurface(keys[1].surface_key); |
| ASSERT_TRUE(surface2); |
| // Chrome surface doesn't have wayland resource. |
| SetSurfaceResource(surface2, nullptr); |
| display_->seat()->OnWindowFocused(surface2->window(), surface->window()); |
| // Wait until all wayland events are sent. |
| PostToClientAndWait([]() {}); |
| |
| EXPECT_TRUE(observer->gained_active == nullptr); |
| EXPECT_TRUE(observer->lost_active != nullptr); |
| EXPECT_EQ(2, observer->activated_call_count); |
| } |
| |
| // Chrome window -> Chrome window |
| TEST_F(WaylandAuraShellServerTest, |
| NonExoFocusedClientChangedNotSendingActivated) { |
| auto keys = SetupClientSurfaces(2); |
| auto observer = SetupShellObservation(); |
| |
| Surface* surface = GetClientSurface(keys[0].surface_key); |
| ASSERT_TRUE(surface); |
| |
| // Chrome surface doesn't have wayland resource. |
| SetSurfaceResource(surface, nullptr); |
| display_->seat()->OnWindowFocused(surface->window(), nullptr); |
| |
| // Reset previous gained and lost active info. |
| observer->gained_active = nullptr; |
| observer->lost_active = nullptr; |
| |
| Surface* surface2 = GetClientSurface(keys[1].surface_key); |
| ASSERT_TRUE(surface2); |
| // Chrome surface doesn't have wayland resource. |
| SetSurfaceResource(surface2, nullptr); |
| display_->seat()->OnWindowFocused(surface2->window(), surface->window()); |
| // Wait until all wayland events are sent. |
| PostToClientAndWait([]() {}); |
| |
| EXPECT_EQ(nullptr, observer->gained_active.get()); |
| EXPECT_EQ(nullptr, observer->lost_active.get()); |
| EXPECT_EQ(1, observer->activated_call_count); |
| } |
| |
| TEST_F(WaylandAuraShellServerTest, RotateFocus) { |
| auto keys = SetupClientSurfaces(); |
| AttachBufferToSurfaces(); |
| |
| struct RotateFocusListener { |
| uint32_t last_received_serial; |
| uint32_t last_received_direction; |
| uint32_t last_received_restart; |
| }; |
| RotateFocusListener listener; |
| |
| zaura_toplevel_listener listeners = { |
| [](void*, zaura_toplevel*, int32_t, int32_t, int32_t, int32_t, |
| wl_array*) {}, |
| [](void*, zaura_toplevel*, int32_t, int32_t) {}, |
| [](void*, zaura_toplevel*, uint32_t) {}, |
| [](void* data, zaura_toplevel*, uint32_t serial, uint32_t direction, |
| uint32_t restart) { |
| auto* listener = static_cast<RotateFocusListener*>(data); |
| listener->last_received_serial = serial; |
| listener->last_received_direction = direction; |
| listener->last_received_restart = restart; |
| }, |
| }; |
| |
| std::unique_ptr<zaura_toplevel> zaura_toplevel; |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->GetDataAs<ClientData>(); |
| |
| zaura_toplevel.reset(zaura_shell_get_aura_toplevel_for_xdg_toplevel( |
| client->globals().aura_shell.get(), |
| data->test_surfaces_list[0].xdg_toplevel.get())); |
| zaura_toplevel_add_listener(zaura_toplevel.get(), &listeners, &listener); |
| zaura_toplevel_set_supports_screen_coordinates(zaura_toplevel.get()); |
| }); |
| |
| auto* xdg_surface = |
| test::server_util::GetUserDataForResource<WaylandXdgSurface>( |
| server_.get(), keys[0].shell_surface_key); |
| ASSERT_TRUE(xdg_surface); |
| XdgShellSurface* shell_surface = xdg_surface->shell_surface.get(); |
| ASSERT_TRUE(shell_surface); |
| |
| PostToClientAndWait([]() {}); |
| |
| // Serial should be increasing. |
| uint32_t received_serial = 0; |
| |
| shell_surface->RotatePaneFocusFromView(nullptr, true, true); |
| PostToClientAndWait([]() {}); |
| EXPECT_EQ(ZAURA_TOPLEVEL_ROTATE_DIRECTION_FORWARD, |
| listener.last_received_direction); |
| EXPECT_EQ(ZAURA_TOPLEVEL_ROTATE_RESTART_STATE_RESTART, |
| listener.last_received_restart); |
| // No assertion for serial on the first run. We just need to ensure that it |
| // increases next time. |
| received_serial = listener.last_received_serial; |
| |
| shell_surface->RotatePaneFocusFromView(nullptr, false, true); |
| PostToClientAndWait([]() {}); |
| EXPECT_GT(listener.last_received_serial, received_serial); |
| EXPECT_EQ(ZAURA_TOPLEVEL_ROTATE_DIRECTION_BACKWARD, |
| listener.last_received_direction); |
| EXPECT_EQ(ZAURA_TOPLEVEL_ROTATE_RESTART_STATE_RESTART, |
| listener.last_received_restart); |
| received_serial = listener.last_received_serial; |
| |
| shell_surface->RotatePaneFocusFromView(nullptr, true, false); |
| PostToClientAndWait([]() {}); |
| EXPECT_GT(listener.last_received_serial, received_serial); |
| EXPECT_EQ(ZAURA_TOPLEVEL_ROTATE_DIRECTION_FORWARD, |
| listener.last_received_direction); |
| EXPECT_EQ(ZAURA_TOPLEVEL_ROTATE_RESTART_STATE_NO_RESTART, |
| listener.last_received_restart); |
| received_serial = listener.last_received_serial; |
| } |
| |
| TEST_F(WaylandAuraShellServerTest, AckRotateFocus) { |
| auto keys = SetupClientSurfaces(); |
| AttachBufferToSurfaces(); |
| |
| auto native_widget1 = ash::TestWidgetBuilder().BuildOwnsNativeWidget(); |
| auto native_widget2 = ash::TestWidgetBuilder().BuildOwnsNativeWidget(); |
| |
| std::unique_ptr<zaura_toplevel> zaura_toplevel; |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->GetDataAs<ClientData>(); |
| zaura_toplevel.reset(zaura_shell_get_aura_toplevel_for_xdg_toplevel( |
| client->globals().aura_shell.get(), |
| data->test_surfaces_list[0].xdg_toplevel.get())); |
| zaura_toplevel_set_supports_screen_coordinates(zaura_toplevel.get()); |
| }); |
| |
| uint32_t serial = 0; |
| |
| WaylandXdgSurface* xdg_surface = |
| test::server_util::GetUserDataForResource<WaylandXdgSurface>( |
| server_.get(), keys[0].shell_surface_key); |
| ASSERT_TRUE(xdg_surface); |
| xdg_surface->shell_surface->set_rotate_focus_callback( |
| base::BindLambdaForTesting( |
| [&serial](ash::FocusCycler::Direction, bool) { return serial; })); |
| |
| auto* focus_cycler = ash::Shell::Get()->focus_cycler(); |
| focus_cycler->AddWidget(native_widget1.get()); |
| focus_cycler->AddWidget(xdg_surface->shell_surface->GetWidget()); |
| focus_cycler->AddWidget(native_widget2.get()); |
| |
| focus_cycler->FocusWidget(xdg_surface->shell_surface->GetWidget()); |
| ASSERT_TRUE(xdg_surface->shell_surface->GetWidget()->IsActive()); |
| |
| // Handled should result in no change. |
| xdg_surface->shell_surface->RotatePaneFocusFromView(nullptr, true, true); |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_toplevel_ack_rotate_focus( |
| zaura_toplevel.get(), serial++, |
| ZAURA_TOPLEVEL_ROTATE_HANDLED_STATE_HANDLED); |
| }); |
| EXPECT_TRUE(xdg_surface->shell_surface->GetWidget()->IsActive()); |
| |
| // Unhandled should result in a rotation forward. |
| xdg_surface->shell_surface->RotatePaneFocusFromView(nullptr, true, true); |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_toplevel_ack_rotate_focus( |
| zaura_toplevel.get(), serial++, |
| ZAURA_TOPLEVEL_ROTATE_HANDLED_STATE_NOT_HANDLED); |
| }); |
| EXPECT_TRUE(native_widget2->IsActive()); |
| |
| // Reset |
| focus_cycler->FocusWidget(xdg_surface->shell_surface->GetWidget()); |
| ASSERT_TRUE(xdg_surface->shell_surface->GetWidget()->IsActive()); |
| |
| // Unhandled should result in a rotation backward. |
| xdg_surface->shell_surface->RotatePaneFocusFromView(nullptr, false, true); |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_toplevel_ack_rotate_focus( |
| zaura_toplevel.get(), serial++, |
| ZAURA_TOPLEVEL_ROTATE_HANDLED_STATE_NOT_HANDLED); |
| }); |
| EXPECT_TRUE(native_widget1->IsActive()); |
| } |
| |
| TEST_F(WaylandAuraShellServerTest, OverviewMode) { |
| auto* overview_controller = ash::Shell::Get()->overview_controller(); |
| const auto start_action = ash::OverviewStartAction::kTests; |
| const auto end_action = ash::OverviewEndAction::kTests; |
| |
| auto observer = SetupShellObservation(); |
| |
| // Need at least one window for overview animation. |
| auto native_widget = ash::TestWidgetBuilder().BuildOwnsNativeWidget(); |
| |
| ui::ScopedAnimationDurationScaleMode non_zero( |
| ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); |
| |
| // Test starting overview and letting the animation complete. |
| overview_controller->StartOverview(start_action); |
| ash::WaitForOverviewEnterAnimation(); |
| |
| // Wait until all wayland events are sent. |
| PostToClientAndWait([]() {}); |
| EXPECT_EQ(1, observer->overview_entered_call_count); |
| EXPECT_EQ(0, observer->overview_exited_call_count); |
| |
| // Test ending overview and letting the animation complete. |
| overview_controller->EndOverview(end_action); |
| ash::WaitForOverviewExitAnimation(); |
| PostToClientAndWait([]() {}); |
| EXPECT_EQ(1, observer->overview_entered_call_count); |
| EXPECT_EQ(1, observer->overview_exited_call_count); |
| |
| // Test starting overview but ending overview before the animation completes. |
| // We don't send a start signal. |
| overview_controller->StartOverview(start_action); |
| overview_controller->EndOverview(end_action); |
| ash::WaitForOverviewExitAnimation(); |
| PostToClientAndWait([]() {}); |
| EXPECT_EQ(1, observer->overview_entered_call_count); |
| EXPECT_EQ(2, observer->overview_exited_call_count); |
| |
| // Enter overview to prepare for the next test. |
| overview_controller->StartOverview(start_action); |
| ash::WaitForOverviewEnterAnimation(); |
| PostToClientAndWait([]() {}); |
| EXPECT_EQ(2, observer->overview_entered_call_count); |
| EXPECT_EQ(2, observer->overview_exited_call_count); |
| |
| // Test ending overview but ending overview before the animation completes. |
| // We don't send an end signal. |
| overview_controller->EndOverview(end_action); |
| overview_controller->StartOverview(start_action); |
| ash::WaitForOverviewEnterAnimation(); |
| PostToClientAndWait([]() {}); |
| EXPECT_EQ(3, observer->overview_entered_call_count); |
| EXPECT_EQ(2, observer->overview_exited_call_count); |
| } |
| |
| TEST_F(WaylandAuraShellServerTest, SetCanMaximizeAndFullscreen) { |
| auto keys = SetupClientSurfaces(); |
| AttachBufferToSurfaces(); |
| |
| std::unique_ptr<zaura_toplevel> zaura_toplevel; |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->GetDataAs<ClientData>(); |
| zaura_toplevel.reset(zaura_shell_get_aura_toplevel_for_xdg_toplevel( |
| client->globals().aura_shell.get(), |
| data->test_surfaces_list[0].xdg_toplevel.get())); |
| }); |
| |
| WaylandXdgSurface* xdg_surface = |
| test::server_util::GetUserDataForResource<WaylandXdgSurface>( |
| server_.get(), keys[0].shell_surface_key); |
| ASSERT_TRUE(xdg_surface); |
| |
| auto* widget = xdg_surface->shell_surface->GetWidget(); |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_toplevel_set_can_maximize(zaura_toplevel.get()); |
| }); |
| EXPECT_TRUE(widget->widget_delegate()->CanMaximize()); |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_toplevel_unset_can_maximize(zaura_toplevel.get()); |
| }); |
| EXPECT_FALSE(widget->widget_delegate()->CanMaximize()); |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_toplevel_set_can_fullscreen(zaura_toplevel.get()); |
| }); |
| EXPECT_TRUE(widget->widget_delegate()->CanFullscreen()); |
| |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_toplevel_unset_can_fullscreen(zaura_toplevel.get()); |
| }); |
| EXPECT_FALSE(widget->widget_delegate()->CanFullscreen()); |
| } |
| |
| // TODO(crbug.com/40284737): Re-enable this when flakiness is resolved. |
| TEST_F(WaylandAuraShellServerTest, DISABLED_SetUnSetFloat) { |
| UpdateDisplay("800x600"); |
| |
| auto keys = SetupClientSurfaces(); |
| AttachBufferToSurfaces(); |
| |
| std::unique_ptr<zaura_toplevel> zaura_toplevel; |
| PostToClientAndWait([&](test::TestClient* client) { |
| auto* data = client->GetDataAs<ClientData>(); |
| zaura_toplevel.reset(zaura_shell_get_aura_toplevel_for_xdg_toplevel( |
| client->globals().aura_shell.get(), |
| data->test_surfaces_list[0].xdg_toplevel.get())); |
| }); |
| |
| WaylandXdgSurface* xdg_surface = |
| test::server_util::GetUserDataForResource<WaylandXdgSurface>( |
| server_.get(), keys[0].shell_surface_key); |
| ASSERT_TRUE(xdg_surface); |
| |
| views::Widget* widget = xdg_surface->shell_surface->GetWidget(); |
| auto* window_state = ash::WindowState::Get(widget->GetNativeWindow()); |
| window_state->window()->SetProperty(chromeos::kAppTypeKey, |
| chromeos::AppType::LACROS); |
| ASSERT_FALSE(window_state->IsFloated()); |
| |
| // Location 0 is bottom right. Test that the window is floated and in the |
| // bottom right quadrant of the display. |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_toplevel_set_float_to_location(zaura_toplevel.get(), /*location=*/0u); |
| }); |
| EXPECT_TRUE(window_state->IsFloated()); |
| EXPECT_TRUE(gfx::Rect(400, 300, 400, 300) |
| .Contains(widget->GetWindowBoundsInScreen())); |
| |
| // Unfloat the window. |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_toplevel_unset_float(zaura_toplevel.get()); |
| }); |
| EXPECT_FALSE(window_state->IsFloated()); |
| |
| // Location 1 is bottom left. Test that the window is floated and in the |
| // bottom left quadrant of the display. |
| PostToClientAndWait([&](test::TestClient* client) { |
| zaura_toplevel_set_float_to_location(zaura_toplevel.get(), /*location=*/1u); |
| }); |
| EXPECT_TRUE(window_state->IsFloated()); |
| EXPECT_TRUE( |
| gfx::Rect(0, 300, 400, 300).Contains(widget->GetWindowBoundsInScreen())); |
| } |
| |
| class WaylandAuraOutputServerTest : public test::WaylandServerTest { |
| public: |
| struct AuraOutputObserver { |
| void Reset() { is_active = false; } |
| bool is_active = false; |
| }; |
| |
| WaylandAuraOutputServerTest() = default; |
| WaylandAuraOutputServerTest(const WaylandAuraOutputServerTest&) = delete; |
| WaylandAuraOutputServerTest& operator=(const WaylandAuraOutputServerTest&) = |
| delete; |
| ~WaylandAuraOutputServerTest() override = default; |
| |
| std::unique_ptr<AuraOutputObserver> SetupAuraOutput(wl_output* output) { |
| static constexpr zaura_output_listener kAuraOutputListener = { |
| [](void*, struct zaura_output*, uint32_t, uint32_t) {}, |
| [](void*, struct zaura_output*, uint32_t) {}, |
| [](void*, struct zaura_output*, uint32_t) {}, |
| [](void*, struct zaura_output*, int32_t, int32_t, int32_t, int32_t) {}, |
| [](void*, struct zaura_output*, int32_t) {}, |
| [](void*, struct zaura_output*, uint32_t, uint32_t) {}, |
| [](void* data, struct zaura_output* zaura_output) { |
| auto* observer = static_cast<AuraOutputObserver*>(data); |
| observer->is_active = true; |
| }}; |
| auto observer = std::make_unique<AuraOutputObserver>(); |
| PostToClientAndWait([&](test::TestClient* client) { |
| std::unique_ptr<zaura_output> aura_output( |
| zaura_shell_get_aura_output(client->aura_shell(), output)); |
| zaura_output_add_listener(aura_output.get(), &kAuraOutputListener, |
| observer.get()); |
| client->globals().aura_outputs.emplace_back(std::move(aura_output)); |
| }); |
| return observer; |
| } |
| }; |
| |
| TEST_F(WaylandAuraOutputServerTest, ActiveDisplay) { |
| UpdateDisplay("800x600,800x600"); |
| const auto* screen = display::Screen::GetScreen(); |
| ASSERT_EQ(2u, screen->GetAllDisplays().size()); |
| const int64_t primary_id = screen->GetAllDisplays()[0].id(); |
| const int64_t secondary_id = screen->GetAllDisplays()[1].id(); |
| |
| wl_output* primary_output = nullptr; |
| wl_output* secondary_output = nullptr; |
| PostToClientAndWait([&](test::TestClient* client) { |
| primary_output = client->globals().outputs[0].get(); |
| secondary_output = client->globals().outputs[1].get(); |
| }); |
| |
| // Create two widgets, one on the primary and the other on the secondary |
| // display. |
| auto* primary_widget = ash::TestWidgetBuilder() |
| .SetBounds({{100, 100}, {200, 200}}) |
| .BuildOwnedByNativeWidget(); |
| auto* secondary_widget = ash::TestWidgetBuilder() |
| .SetBounds({{900, 100}, {200, 200}}) |
| .BuildOwnedByNativeWidget(); |
| ASSERT_EQ( |
| screen->GetDisplayNearestWindow(primary_widget->GetNativeWindow()).id(), |
| primary_id); |
| ASSERT_EQ( |
| screen->GetDisplayNearestWindow(secondary_widget->GetNativeWindow()).id(), |
| secondary_id); |
| |
| // Initialize the aura output extensions and register observers. |
| auto primary_observer = SetupAuraOutput(primary_output); |
| auto secondary_observer = SetupAuraOutput(secondary_output); |
| EXPECT_FALSE(primary_observer->is_active); |
| EXPECT_FALSE(secondary_observer->is_active); |
| |
| // Activate the widget on the primary display. |
| primary_widget->Activate(); |
| PostToClientAndWait([]() {}); |
| EXPECT_TRUE(primary_observer->is_active); |
| EXPECT_FALSE(secondary_observer->is_active); |
| |
| primary_observer->Reset(); |
| secondary_observer->Reset(); |
| |
| // Activate the widget on the secondary display. |
| secondary_widget->Activate(); |
| PostToClientAndWait([]() {}); |
| EXPECT_FALSE(primary_observer->is_active); |
| EXPECT_TRUE(secondary_observer->is_active); |
| |
| primary_observer->Reset(); |
| secondary_observer->Reset(); |
| |
| // Ensure activating the widget on the primary display again correctly |
| // re-emits the activate event for the primary output. |
| primary_widget->Activate(); |
| PostToClientAndWait([]() {}); |
| EXPECT_TRUE(primary_observer->is_active); |
| EXPECT_FALSE(secondary_observer->is_active); |
| } |
| |
| } // namespace |
| } // namespace exo::wayland |