123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497 |
- // Copyright 2021 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "components/exo/wayland/zcr_remote_shell_impl.h"
- #include <wayland-server-core.h>
- #include <wayland-server.h>
- #include <memory>
- #include <vector>
- #include "ash/shell.h"
- #include "ash/test/ash_test_base.h"
- #include "ash/wm/tablet_mode/tablet_mode_controller.h"
- #include "base/bind.h"
- #include "base/posix/unix_domain_socket.h"
- #include "components/exo/display.h"
- #include "components/exo/shell_surface.h"
- #include "components/exo/test/exo_test_base.h"
- #include "components/exo/test/shell_surface_builder.h"
- #include "components/exo/wayland/server_util.h"
- #include "ui/display/display.h"
- #include "ui/display/screen.h"
- #include "ui/views/widget/widget.h"
- namespace exo {
- namespace wayland {
- namespace {
- const int kDefaultWindowLength = 100;
- enum class RemoteShellEventType { kSendBoundsChanged, kSendWorkspaceInfo };
- struct WlDisplayDeleter {
- void operator()(wl_display* ptr) const { wl_display_destroy(ptr); }
- };
- using ScopedWlDisplay = std::unique_ptr<wl_display, WlDisplayDeleter>;
- struct WlClientDeleter {
- void operator()(wl_client* ptr) const { wl_client_destroy(ptr); }
- };
- using ScopedWlClient = std::unique_ptr<wl_client, WlClientDeleter>;
- struct WlResourceDeleter {
- void operator()(wl_resource* ptr) const { wl_resource_destroy(ptr); }
- };
- using ScopedWlResource = std::unique_ptr<wl_resource, WlResourceDeleter>;
- } // namespace
- class WaylandRemoteShellTest : public test::ExoTestBase {
- public:
- WaylandRemoteShellTest()
- : test::ExoTestBase(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
- WaylandRemoteShellTest(const WaylandRemoteShellTest&) = delete;
- WaylandRemoteShellTest& operator=(const WaylandRemoteShellTest&) = delete;
- // test::ExoTestBase:
- void SetUp() override {
- test::ExoTestBase::SetUp();
- ResetEventRecords();
- UpdateDisplay("800x600");
- base::CreateSocketPair(&reader_, &writer_);
- wl_display_.reset(wl_display_create());
- wl_client_.reset(wl_client_create(wl_display_.get(), reader_.release()));
- // Use 0 as the id here to avoid the id conflict (i.e. let wayland library
- // choose the id from available ids.) Otherwise that will cause memory leak.
- wl_shell_resource_.reset(wl_resource_create(wl_client_.get(),
- &zcr_remote_shell_v2_interface,
- /*version=*/1, /*id=*/0));
- wl_remote_surface_resource_.reset(
- wl_resource_create(wl_client(), &zcr_remote_surface_v2_interface,
- /*version=*/1, /*id=*/0));
- display_ = std::make_unique<Display>();
- shell_ = std::make_unique<WaylandRemoteShell>(
- display_.get(), wl_shell_resource_.get(),
- base::BindRepeating(
- [](int64_t) { return static_cast<wl_resource*>(nullptr); }),
- test_event_mapping_,
- /*use_default_scale_cancellation_default=*/true);
- }
- void TearDown() override {
- shell_.reset();
- display_.reset();
- test::ExoTestBase::TearDown();
- }
- void EnableTabletMode(bool enable) {
- ash::Shell::Get()->tablet_mode_controller()->SetEnabledForTest(enable);
- }
- std::unique_ptr<ClientControlledShellSurface::Delegate> CreateDelegate() {
- return shell()->CreateShellSurfaceDelegate(
- wl_remote_surface_resource_.get());
- }
- void ResetEventRecords() {
- remote_shell_event_sequence_.clear();
- remote_shell_requested_bounds_changes_.clear();
- }
- WaylandRemoteShell* shell() { return shell_.get(); }
- wl_client* wl_client() { return wl_client_.get(); }
- wl_resource* wl_remote_surface() { return wl_remote_surface_resource_.get(); }
- static std::vector<RemoteShellEventType> remote_shell_event_sequence() {
- return remote_shell_event_sequence_;
- }
- static std::vector<WaylandRemoteShell::BoundsChangeData>
- remote_shell_requested_bounds_changes() {
- return remote_shell_requested_bounds_changes_;
- }
- static int last_desktop_focus_state() { return last_desktop_focus_state_; }
- private:
- std::unique_ptr<Display> display_;
- base::ScopedFD reader_, writer_;
- ScopedWlDisplay wl_display_;
- ScopedWlClient wl_client_;
- ScopedWlResource wl_shell_resource_;
- ScopedWlResource wl_remote_surface_resource_;
- std::unique_ptr<WaylandRemoteShell> shell_;
- static std::vector<RemoteShellEventType> remote_shell_event_sequence_;
- static std::vector<WaylandRemoteShell::BoundsChangeData>
- remote_shell_requested_bounds_changes_;
- static uint32_t last_desktop_focus_state_;
- const WaylandRemoteShellEventMapping test_event_mapping_ = {
- /*send_window_geometry_changed=*/+[](struct wl_resource*,
- int32_t,
- int32_t,
- int32_t,
- int32_t) {},
- /*send_change_zoom_level=*/+[](struct wl_resource*, int32_t) {},
- /*send_state_type_changed=*/+[](struct wl_resource*, uint32_t) {},
- /*send_bounds_changed_in_output=*/
- +[](struct wl_resource*,
- struct wl_resource*,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- uint32_t) {},
- /*send_bounds_changed=*/
- +[](struct wl_resource*,
- uint32_t display_id_hi,
- uint32_t display_id_lo,
- int32_t x,
- int32_t y,
- int32_t width,
- int32_t height,
- uint32_t reason) {
- remote_shell_event_sequence_.push_back(
- RemoteShellEventType::kSendBoundsChanged);
- remote_shell_requested_bounds_changes_.emplace_back(
- (((int64_t)display_id_hi << 32) | display_id_lo),
- gfx::Rect(x, y, width, height),
- static_cast<zcr_remote_surface_v1_bounds_change_reason>(reason));
- },
- /*send_activated=*/
- +[](struct wl_resource*, struct wl_resource*, struct wl_resource*) {},
- /*send_desktop_focus_state_changed=*/
- +[](struct wl_resource*, uint32_t state) {
- last_desktop_focus_state_ = state;
- },
- /*send_workspace_info=*/
- +[](struct wl_resource*,
- uint32_t,
- uint32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- int32_t,
- uint32_t,
- struct wl_array*) {
- remote_shell_event_sequence_.push_back(
- RemoteShellEventType::kSendWorkspaceInfo);
- },
- /*send_drag_finished=*/
- +[](struct wl_resource*, int32_t, int32_t, int32_t) {},
- /*send_drag_started=*/+[](struct wl_resource*, uint32_t) {},
- /*send_layout_mode=*/+[](struct wl_resource*, uint32_t) {},
- /*send_default_device_scale_factor=*/+[](struct wl_resource*, int32_t) {},
- /*send_configure=*/+[](struct wl_resource*, uint32_t) {},
- /*bounds_changed_in_output_since_version=*/0,
- /*desktop_focus_state_changed_since_version=*/0,
- /*layout_mode_since_version=*/0,
- /*default_device_scale_factor_since_version=*/0,
- /*change_zoom_level_since_version=*/0,
- /*send_workspace_info_since_version=*/0,
- /*set_use_default_scale_cancellation_since_version=*/0,
- };
- };
- std::vector<RemoteShellEventType>
- WaylandRemoteShellTest::remote_shell_event_sequence_;
- std::vector<WaylandRemoteShell::BoundsChangeData>
- WaylandRemoteShellTest::remote_shell_requested_bounds_changes_;
- uint32_t WaylandRemoteShellTest::last_desktop_focus_state_ = 0;
- // Test that all bounds change requests are deferred while the tablet transition
- // is happening until it's finished.
- TEST_F(WaylandRemoteShellTest, TabletTransition) {
- auto shell_surface = exo::test::ShellSurfaceBuilder({256, 256})
- .SetDelegate(CreateDelegate())
- .BuildClientControlledShellSurface();
- auto* surface = shell_surface->root_surface();
- auto* const widget = shell_surface->GetWidget();
- auto* const window = widget->GetNativeWindow();
- // Snap window.
- ash::WindowSnapWMEvent event(ash::WM_EVENT_SNAP_PRIMARY);
- ash::WindowState::Get(window)->OnWMEvent(&event);
- shell_surface->SetSnappedToPrimary();
- shell_surface->SetGeometry(gfx::Rect(0, 0, 400, 520));
- surface->Commit();
- // Enable tablet mode.
- ResetEventRecords();
- EnableTabletMode(true);
- task_environment()->FastForwardBy(base::Seconds(1));
- task_environment()->RunUntilIdle();
- const auto expected_sequence = std::vector<RemoteShellEventType>{
- RemoteShellEventType::kSendWorkspaceInfo,
- RemoteShellEventType::kSendBoundsChanged};
- EXPECT_EQ(expected_sequence, remote_shell_event_sequence());
- // TODO(b/236432849): Add a reasonable bounds check.
- }
- // Verifies bounds change events and workspace info events are triggered with
- // proper values and in proper order when display zoom happens. A bounds change
- // event must be triggered only for PIP.
- TEST_F(WaylandRemoteShellTest, DisplayZoom) {
- // Test a restored window first.
- auto shell_surface =
- exo::test::ShellSurfaceBuilder({256, 256})
- .SetDelegate(CreateDelegate())
- .SetWindowState(chromeos::WindowStateType::kNormal)
- .SetGeometry({100, 100, kDefaultWindowLength, kDefaultWindowLength})
- .BuildClientControlledShellSurface();
- auto* surface = shell_surface->root_surface();
- auto* window = shell_surface->GetWidget()->GetNativeWindow();
- const display::Display& display =
- display::Screen::GetScreen()->GetDisplayNearestWindow(window);
- ResetEventRecords();
- ash::Shell::Get()->display_manager()->ZoomDisplay(display.id(), /*up=*/true);
- task_environment()->RunUntilIdle();
- const auto expected_sequence_for_restored = std::vector<RemoteShellEventType>{
- RemoteShellEventType::kSendWorkspaceInfo};
- EXPECT_EQ(expected_sequence_for_restored, remote_shell_event_sequence());
- // Test a maximized window.
- shell_surface->SetMaximized();
- surface->Commit();
- ResetEventRecords();
- ash::Shell::Get()->display_manager()->ZoomDisplay(display.id(), /*up=*/true);
- task_environment()->RunUntilIdle();
- const auto expected_sequence_for_maximized =
- std::vector<RemoteShellEventType>{
- RemoteShellEventType::kSendWorkspaceInfo};
- EXPECT_EQ(expected_sequence_for_maximized, remote_shell_event_sequence());
- // Test a PIP window.
- shell_surface->SetPip();
- // Place PIP at the bottom-right corner so the position will be adjusted with
- // display size change. This means no bounds change event is triggered if PIP
- // is at the top-left corner, but this is fine as the position doesn't need
- // to be adjusted on the client side.
- shell_surface->SetGeometry(
- gfx::Rect(display.bounds().right() - kDefaultWindowLength,
- display.bounds().bottom() - kDefaultWindowLength,
- kDefaultWindowLength, kDefaultWindowLength));
- surface->Commit();
- ResetEventRecords();
- ash::Shell::Get()->display_manager()->ZoomDisplay(display.id(), /*up=*/true);
- task_environment()->RunUntilIdle();
- const auto expected_sequence_for_pip = std::vector<RemoteShellEventType>{
- RemoteShellEventType::kSendWorkspaceInfo,
- RemoteShellEventType::kSendBoundsChanged};
- EXPECT_EQ(remote_shell_event_sequence(), expected_sequence_for_pip);
- ASSERT_EQ(1UL, remote_shell_requested_bounds_changes().size());
- const auto bounds_change = remote_shell_requested_bounds_changes()[0];
- EXPECT_EQ(display.id(), bounds_change.display_id);
- // Verify that the new bounds is scaled larger in pixels.
- EXPECT_GT(kDefaultWindowLength, bounds_change.bounds_in_display.width());
- EXPECT_GT(kDefaultWindowLength, bounds_change.bounds_in_display.height());
- EXPECT_EQ(ZCR_REMOTE_SURFACE_V1_BOUNDS_CHANGE_REASON_PIP,
- bounds_change.reason);
- }
- // Verifies bounds change events and workspace info events are triggered with
- // proper values and in proper order when display rotation happens. A bounds
- // change event must be triggered only for PIP.
- TEST_F(WaylandRemoteShellTest, DisplayRotation) {
- // Test a restored window first.
- auto shell_surface =
- exo::test::ShellSurfaceBuilder({256, 256})
- .SetDelegate(CreateDelegate())
- .SetWindowState(chromeos::WindowStateType::kNormal)
- .SetGeometry({100, 100, kDefaultWindowLength, kDefaultWindowLength})
- .BuildClientControlledShellSurface();
- auto* surface = shell_surface->root_surface();
- auto* window = shell_surface->GetWidget()->GetNativeWindow();
- const display::Display& display =
- display::Screen::GetScreen()->GetDisplayNearestWindow(window);
- ResetEventRecords();
- ash::Shell::Get()->display_manager()->SetDisplayRotation(
- display.id(), display::Display::ROTATE_90,
- display::Display::RotationSource::ACCELEROMETER);
- task_environment()->RunUntilIdle();
- const auto expected_sequence_for_restored = std::vector<RemoteShellEventType>{
- RemoteShellEventType::kSendWorkspaceInfo};
- EXPECT_EQ(expected_sequence_for_restored, remote_shell_event_sequence());
- // Test a maximized window.
- shell_surface->SetMaximized();
- surface->Commit();
- ResetEventRecords();
- ash::Shell::Get()->display_manager()->SetDisplayRotation(
- display.id(), display::Display::ROTATE_180,
- display::Display::RotationSource::ACCELEROMETER);
- task_environment()->RunUntilIdle();
- const auto expected_sequence_for_maximized =
- std::vector<RemoteShellEventType>{
- RemoteShellEventType::kSendWorkspaceInfo,
- RemoteShellEventType::kSendBoundsChanged};
- EXPECT_EQ(expected_sequence_for_maximized, remote_shell_event_sequence());
- // Test a PIP window.
- shell_surface->SetPip();
- // Place PIP at the bottom-right corner so the position will be adjusted with
- // display rotation.
- shell_surface->SetGeometry(
- gfx::Rect(display.bounds().right(), display.bounds().bottom(),
- kDefaultWindowLength, kDefaultWindowLength));
- surface->Commit();
- const gfx::Rect bounds = window->GetBoundsInScreen();
- const int right_inset = display.bounds().right() - bounds.right();
- const int bottom_inset = display.bounds().bottom() - bounds.bottom();
- ResetEventRecords();
- ash::Shell::Get()->display_manager()->SetDisplayRotation(
- display.id(), display::Display::ROTATE_270,
- display::Display::RotationSource::ACCELEROMETER);
- task_environment()->RunUntilIdle();
- const auto expected_sequence_for_pip = std::vector<RemoteShellEventType>{
- RemoteShellEventType::kSendWorkspaceInfo,
- RemoteShellEventType::kSendBoundsChanged};
- EXPECT_EQ(expected_sequence_for_pip, remote_shell_event_sequence());
- ASSERT_EQ(1UL, remote_shell_requested_bounds_changes().size());
- const auto bounds_change = remote_shell_requested_bounds_changes()[0];
- EXPECT_EQ(display.id(), bounds_change.display_id);
- const display::Display& rotated_display =
- display::Screen::GetScreen()->GetDisplayNearestWindow(window);
- const int expected_x =
- rotated_display.bounds().right() - right_inset - kDefaultWindowLength;
- const int expected_y =
- rotated_display.bounds().bottom() - bottom_inset - kDefaultWindowLength;
- const gfx::Rect expected_bounds = gfx::Rect(
- expected_x, expected_y, kDefaultWindowLength, kDefaultWindowLength);
- EXPECT_EQ(expected_bounds, bounds_change.bounds_in_display);
- EXPECT_EQ(ZCR_REMOTE_SURFACE_V1_BOUNDS_CHANGE_REASON_PIP,
- bounds_change.reason);
- }
- // Removing secandary display and re-reconnect it restores the bounds of
- // windows on secandary display. This test verifies bounds change events
- // and workspace info events are triggered with proper values and in
- // proper order.
- TEST_F(WaylandRemoteShellTest, DisplayRemovalAddition) {
- auto shell_surface = exo::test::ShellSurfaceBuilder(
- {kDefaultWindowLength, kDefaultWindowLength})
- .SetDelegate(CreateDelegate())
- .BuildClientControlledShellSurface();
- auto* surface = shell_surface->root_surface();
- // Add secondary display with a different scale factor.
- UpdateDisplay("800x600,800x600*2");
- auto* display_manager = ash::Shell::Get()->display_manager();
- const int64_t primary_display_id = display_manager->GetDisplayAt(0).id();
- const int64_t secondary_display_id = display_manager->GetDisplayAt(1).id();
- display::ManagedDisplayInfo primary_display_info =
- display_manager->GetDisplayInfo(primary_display_id);
- display::ManagedDisplayInfo secondary_display_info =
- display_manager->GetDisplayInfo(secondary_display_id);
- // Move the window to the secandary display.
- const int initial_x = 100;
- const int initial_y = 100;
- shell_surface->SetBounds(secondary_display_id,
- gfx::Rect(initial_x, initial_y, kDefaultWindowLength,
- kDefaultWindowLength));
- surface->Commit();
- // Disconnect secondary display.
- ResetEventRecords();
- std::vector<display::ManagedDisplayInfo> display_info_list;
- display_info_list.push_back(primary_display_info);
- display_manager->OnNativeDisplaysChanged(display_info_list);
- task_environment()->RunUntilIdle();
- const auto event_sequence_disconnect = std::vector<RemoteShellEventType>{
- RemoteShellEventType::kSendWorkspaceInfo,
- RemoteShellEventType::kSendBoundsChanged};
- EXPECT_EQ(remote_shell_event_sequence(), event_sequence_disconnect);
- ASSERT_EQ(1UL, remote_shell_requested_bounds_changes().size());
- const auto bounds_change = remote_shell_requested_bounds_changes()[0];
- EXPECT_EQ(bounds_change.display_id, primary_display_id);
- // Verify the new bounds is scaled in pixles with the scale factor of the
- // primary display.
- const gfx::Rect expected_bounds_after_disconnection = gfx::Rect(
- initial_x, initial_y, kDefaultWindowLength / 2, kDefaultWindowLength / 2);
- EXPECT_EQ(expected_bounds_after_disconnection,
- bounds_change.bounds_in_display);
- EXPECT_EQ(ZCR_REMOTE_SURFACE_V1_BOUNDS_CHANGE_REASON_MOVE,
- bounds_change.reason);
- // Reconnects the previously connected secondary display.
- ResetEventRecords();
- display_info_list.push_back(secondary_display_info);
- display_manager->OnNativeDisplaysChanged(display_info_list);
- task_environment()->RunUntilIdle();
- // Reconnecting the secondary display seems to cause two workspace info
- // events: One for a display metrics change for the primary display, and the
- // other for a display addition event of the secondary display.
- const auto event_sequence_reconnect = std::vector<RemoteShellEventType>{
- RemoteShellEventType::kSendWorkspaceInfo,
- RemoteShellEventType::kSendWorkspaceInfo,
- RemoteShellEventType::kSendBoundsChanged};
- EXPECT_EQ(event_sequence_reconnect, remote_shell_event_sequence());
- ASSERT_EQ(1UL, remote_shell_requested_bounds_changes().size());
- const auto bounds_change_to_secondary =
- remote_shell_requested_bounds_changes()[0];
- EXPECT_EQ(secondary_display_id, bounds_change_to_secondary.display_id);
- const gfx::Rect expected_bounds_after_reconnection = gfx::Rect(
- initial_x, initial_y, kDefaultWindowLength, kDefaultWindowLength);
- EXPECT_EQ(expected_bounds_after_reconnection,
- bounds_change_to_secondary.bounds_in_display);
- EXPECT_EQ(ZCR_REMOTE_SURFACE_V1_BOUNDS_CHANGE_REASON_MOVE,
- bounds_change_to_secondary.reason);
- }
- // Test that the desktop focus state event is called with the proper value in
- // response to window focus change.
- TEST_F(WaylandRemoteShellTest, DesktopFocusState) {
- auto client_controlled_shell_surface =
- exo::test::ShellSurfaceBuilder(
- {kDefaultWindowLength, kDefaultWindowLength})
- .SetDelegate(CreateDelegate())
- .SetNoCommit()
- .BuildClientControlledShellSurface();
- auto* surface = client_controlled_shell_surface->root_surface();
- SetSurfaceResource(surface, wl_remote_surface());
- surface->Commit();
- EXPECT_EQ(last_desktop_focus_state(), 2);
- client_controlled_shell_surface->SetMinimized();
- surface->Commit();
- EXPECT_EQ(last_desktop_focus_state(), 1);
- auto shell_surface = exo::test::ShellSurfaceBuilder(
- {kDefaultWindowLength, kDefaultWindowLength})
- .BuildShellSurface();
- auto* other_client_window = shell_surface->GetWidget()->GetNativeWindow();
- other_client_window->Show();
- other_client_window->Focus();
- EXPECT_EQ(last_desktop_focus_state(), 3);
- }
- } // namespace wayland
- } // namespace exo
|