| // Copyright 2018 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 <wayland-server.h> |
| #include <memory> |
| |
| #include "base/strings/stringprintf.h" |
| #include "base/test/scoped_command_line.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/display/display.h" |
| #include "ui/display/display_observer.h" |
| #include "ui/display/display_switches.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/native_widget_types.h" |
| #include "ui/ozone/platform/wayland/host/wayland_connection.h" |
| #include "ui/ozone/platform/wayland/host/wayland_output.h" |
| #include "ui/ozone/platform/wayland/host/wayland_output_manager.h" |
| #include "ui/ozone/platform/wayland/host/wayland_screen.h" |
| #include "ui/ozone/platform/wayland/test/mock_pointer.h" |
| #include "ui/ozone/platform/wayland/test/mock_surface.h" |
| #include "ui/ozone/platform/wayland/test/test_output.h" |
| #include "ui/ozone/platform/wayland/test/test_wayland_server_thread.h" |
| #include "ui/ozone/platform/wayland/test/wayland_test.h" |
| #include "ui/platform_window/platform_window_init_properties.h" |
| |
| using ::testing::Values; |
| |
| namespace ui { |
| |
| namespace { |
| |
| constexpr uint32_t kNumberOfDisplays = 1; |
| constexpr uint32_t kOutputWidth = 1024; |
| constexpr uint32_t kOutputHeight = 768; |
| |
| class TestDisplayObserver : public display::DisplayObserver { |
| public: |
| TestDisplayObserver() {} |
| ~TestDisplayObserver() override {} |
| |
| display::Display GetDisplay() { return std::move(display_); } |
| display::Display GetRemovedDisplay() { return std::move(removed_display_); } |
| uint32_t GetAndClearChangedMetrics() { |
| uint32_t changed_metrics = changed_metrics_; |
| changed_metrics_ = 0; |
| return changed_metrics; |
| } |
| |
| // display::DisplayObserver: |
| void OnDisplayAdded(const display::Display& new_display) override { |
| display_ = new_display; |
| } |
| |
| void OnDisplayRemoved(const display::Display& old_display) override { |
| removed_display_ = old_display; |
| } |
| |
| void OnDisplayMetricsChanged(const display::Display& display, |
| uint32_t changed_metrics) override { |
| changed_metrics_ = changed_metrics; |
| display_ = display; |
| } |
| |
| private: |
| uint32_t changed_metrics_ = 0; |
| display::Display display_; |
| display::Display removed_display_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TestDisplayObserver); |
| }; |
| |
| } // namespace |
| |
| class WaylandScreenTest : public WaylandTest { |
| public: |
| WaylandScreenTest() = default; |
| ~WaylandScreenTest() override = default; |
| |
| void SetUp() override { |
| output_ = server_.output(); |
| |
| WaylandTest::SetUp(); |
| |
| output_->SetRect({kOutputWidth, kOutputHeight}); |
| output_->SetScale(1); |
| output_->Flush(); |
| Sync(); |
| |
| output_manager_ = connection_->wayland_output_manager(); |
| ASSERT_TRUE(output_manager_); |
| |
| EXPECT_TRUE(output_manager_->IsOutputReady()); |
| platform_screen_ = output_manager_->CreateWaylandScreen(); |
| } |
| |
| protected: |
| std::unique_ptr<WaylandWindow> CreateWaylandWindowWithProperties( |
| const gfx::Rect& bounds, |
| PlatformWindowType window_type, |
| gfx::AcceleratedWidget parent_widget, |
| MockPlatformWindowDelegate* delegate) { |
| PlatformWindowInitProperties properties; |
| properties.bounds = bounds; |
| properties.type = window_type; |
| properties.parent_widget = parent_widget; |
| return WaylandWindow::Create(delegate, connection_.get(), |
| std::move(properties)); |
| } |
| |
| void ValidateTheDisplayForWidget(gfx::AcceleratedWidget widget, |
| int64_t expected_display_id) { |
| display::Display display_for_widget = |
| platform_screen_->GetDisplayForAcceleratedWidget(widget); |
| EXPECT_EQ(display_for_widget.id(), expected_display_id); |
| } |
| |
| wl::TestOutput* output_ = nullptr; |
| WaylandOutputManager* output_manager_ = nullptr; |
| |
| std::unique_ptr<WaylandScreen> platform_screen_; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(WaylandScreenTest); |
| }; |
| |
| // Tests whether a primary output has been initialized before PlatformScreen is |
| // created. |
| TEST_P(WaylandScreenTest, OutputBaseTest) { |
| // IsPrimaryOutputReady and PlatformScreen creation is done in the |
| // initialization part of the tests. |
| |
| // Ensure there is only one display, which is the primary one. |
| auto& all_displays = platform_screen_->GetAllDisplays(); |
| EXPECT_EQ(all_displays.size(), kNumberOfDisplays); |
| |
| // Ensure the size property of the primary display. |
| EXPECT_EQ(platform_screen_->GetPrimaryDisplay().bounds(), |
| gfx::Rect(0, 0, kOutputWidth, kOutputHeight)); |
| } |
| |
| TEST_P(WaylandScreenTest, MultipleOutputsAddedAndRemoved) { |
| TestDisplayObserver observer; |
| platform_screen_->AddObserver(&observer); |
| |
| const int64_t old_primary_display_id = |
| platform_screen_->GetPrimaryDisplay().id(); |
| gfx::Rect output1_rect = server_.output()->GetRect(); |
| |
| // Add a second display. |
| wl::TestOutput* output2 = server_.CreateAndInitializeOutput(); |
| |
| Sync(); |
| |
| // The second display is located to the right of first display like |
| // | || |. |
| gfx::Rect output2_rect(output1_rect.width(), 0, 800, 600); |
| output2->SetRect(output2_rect); |
| output2->Flush(); |
| |
| Sync(); |
| |
| // Ensure that second display is not a primary one and have a different id. |
| int64_t added_display_id = observer.GetDisplay().id(); |
| EXPECT_NE(platform_screen_->GetPrimaryDisplay().id(), added_display_id); |
| |
| output2->DestroyGlobal(); |
| |
| Sync(); |
| |
| // Ensure that removed display has correct id. |
| int64_t removed_display_id = observer.GetRemovedDisplay().id(); |
| EXPECT_EQ(added_display_id, removed_display_id); |
| |
| // Create another display again. |
| output2 = server_.CreateAndInitializeOutput(); |
| |
| Sync(); |
| |
| // Updates rect again. |
| output2->SetRect(output2_rect); |
| output2->Flush(); |
| |
| Sync(); |
| |
| // The newly added display is not a primary yet. |
| added_display_id = observer.GetDisplay().id(); |
| EXPECT_NE(platform_screen_->GetPrimaryDisplay().id(), added_display_id); |
| |
| // Now, rearrange displays so that second display becomes the primary one. |
| output1_rect = gfx::Rect(1024, 0, 1024, 768); |
| output_->SetRect(output1_rect); |
| output_->Flush(); |
| |
| output2_rect = gfx::Rect(0, 0, 1024, 768); |
| output2->SetRect(output2_rect); |
| output2->Flush(); |
| |
| Sync(); |
| |
| // Ensure that output2 is now the primary one. |
| EXPECT_EQ(platform_screen_->GetPrimaryDisplay().id(), added_display_id); |
| |
| // Remove the primary display now. |
| output2->DestroyGlobal(); |
| |
| Sync(); |
| |
| // Ensure that output1 is a primary display now. |
| EXPECT_EQ(platform_screen_->GetPrimaryDisplay().id(), old_primary_display_id); |
| // Ensure that the removed display was the one, which was a primary display. |
| EXPECT_EQ(observer.GetRemovedDisplay().id(), added_display_id); |
| |
| platform_screen_->RemoveObserver(&observer); |
| } |
| |
| TEST_P(WaylandScreenTest, OutputPropertyChanges) { |
| TestDisplayObserver observer; |
| platform_screen_->AddObserver(&observer); |
| |
| gfx::Rect new_rect{100, 100}; |
| output_->SetRect(new_rect); |
| output_->Flush(); |
| |
| Sync(); |
| |
| uint32_t changed_values = display::DisplayObserver::DISPLAY_METRIC_BOUNDS | |
| display::DisplayObserver::DISPLAY_METRIC_WORK_AREA; |
| EXPECT_EQ(observer.GetAndClearChangedMetrics(), changed_values); |
| EXPECT_EQ(observer.GetDisplay().bounds(), new_rect); |
| |
| const int32_t new_scale_value = 2; |
| output_->SetScale(new_scale_value); |
| output_->Flush(); |
| |
| Sync(); |
| |
| changed_values = |
| display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR | |
| display::DisplayObserver::DISPLAY_METRIC_WORK_AREA | |
| display::DisplayObserver::DISPLAY_METRIC_BOUNDS; |
| EXPECT_EQ(observer.GetAndClearChangedMetrics(), changed_values); |
| EXPECT_EQ(observer.GetDisplay().device_scale_factor(), new_scale_value); |
| EXPECT_EQ(observer.GetDisplay().bounds(), gfx::Rect(50, 50)); |
| |
| platform_screen_->RemoveObserver(&observer); |
| } |
| |
| TEST_P(WaylandScreenTest, GetAcceleratedWidgetAtScreenPoint) { |
| // Now, send enter event for the surface, which was created before. |
| wl::MockSurface* surface = server_.GetObject<wl::MockSurface>( |
| window_->root_surface()->GetSurfaceId()); |
| ASSERT_TRUE(surface); |
| wl_surface_send_enter(surface->resource(), output_->resource()); |
| |
| Sync(); |
| |
| // If there is no focused window (focus is set whenever a pointer enters any |
| // of the windows), there must be kNullAcceleratedWidget returned. There is no |
| // real way to determine what window is located on a certain screen point in |
| // Wayland. |
| gfx::AcceleratedWidget widget_at_screen_point = |
| platform_screen_->GetAcceleratedWidgetAtScreenPoint(gfx::Point(10, 10)); |
| EXPECT_EQ(widget_at_screen_point, gfx::kNullAcceleratedWidget); |
| |
| // Set a focus to the main window. Now, that focused window must be returned. |
| window_->SetPointerFocus(true); |
| widget_at_screen_point = |
| platform_screen_->GetAcceleratedWidgetAtScreenPoint(gfx::Point(10, 10)); |
| EXPECT_EQ(widget_at_screen_point, window_->GetWidget()); |
| |
| // Getting a widget at a screen point outside its bounds, must result in a |
| // null widget. |
| const gfx::Rect window_bounds = window_->GetBounds(); |
| widget_at_screen_point = platform_screen_->GetAcceleratedWidgetAtScreenPoint( |
| gfx::Point(window_bounds.width() + 1, window_bounds.height() + 1)); |
| EXPECT_EQ(widget_at_screen_point, gfx::kNullAcceleratedWidget); |
| |
| MockPlatformWindowDelegate delegate; |
| auto menu_window_bounds = |
| gfx::Rect(window_->GetBounds().width() - 10, |
| window_->GetBounds().height() - 10, 100, 100); |
| std::unique_ptr<WaylandWindow> menu_window = |
| CreateWaylandWindowWithProperties(menu_window_bounds, |
| PlatformWindowType::kMenu, |
| window_->GetWidget(), &delegate); |
| |
| Sync(); |
| |
| // Imagine the mouse enters a menu window, which is located on top of the main |
| // window, and gathers focus. |
| window_->SetPointerFocus(false); |
| menu_window->SetPointerFocus(true); |
| widget_at_screen_point = |
| platform_screen_->GetAcceleratedWidgetAtScreenPoint(gfx::Point( |
| menu_window->GetBounds().x() + 1, menu_window->GetBounds().y() + 1)); |
| EXPECT_EQ(widget_at_screen_point, menu_window->GetWidget()); |
| |
| // Whenever a mouse pointer leaves the menu window, the accelerated widget |
| // of that focused window must be returned. |
| window_->SetPointerFocus(true); |
| menu_window->SetPointerFocus(false); |
| widget_at_screen_point = |
| platform_screen_->GetAcceleratedWidgetAtScreenPoint(gfx::Point(0, 0)); |
| EXPECT_EQ(widget_at_screen_point, window_->GetWidget()); |
| |
| // Reset the focus to avoid crash on dtor as long as there is no real pointer |
| // object. |
| window_->SetPointerFocus(false); |
| |
| // Part 2: test that the window is found when display's scale changes. |
| // Update scale. |
| output_->SetScale(2); |
| output_->Flush(); |
| |
| Sync(); |
| |
| auto menu_bounds_px = menu_window->GetBounds(); |
| // Translate the point to dip. |
| auto point_in_screen = |
| gfx::ScaleToRoundedPoint(menu_bounds_px.origin(), 1.f / 2); |
| menu_window->SetPointerFocus(true); |
| widget_at_screen_point = |
| platform_screen_->GetAcceleratedWidgetAtScreenPoint(point_in_screen); |
| EXPECT_EQ(widget_at_screen_point, menu_window->GetWidget()); |
| } |
| |
| TEST_P(WaylandScreenTest, GetLocalProcessWidgetAtPoint) { |
| gfx::Point point(10, 10); |
| EXPECT_EQ(platform_screen_->GetLocalProcessWidgetAtPoint(point, {}), |
| gfx::kNullAcceleratedWidget); |
| |
| // Set a focus to the main window. Now, that focused window must be returned. |
| window_->SetPointerFocus(true); |
| EXPECT_EQ(platform_screen_->GetLocalProcessWidgetAtPoint(point, {}), |
| window_->GetWidget()); |
| |
| // Null widget must be returned when the focused window is part of the |
| // |ignore| list. |
| gfx::AcceleratedWidget w = window_->GetWidget(); |
| EXPECT_EQ( |
| platform_screen_->GetLocalProcessWidgetAtPoint(point, {w - 1, w, w + 1}), |
| gfx::kNullAcceleratedWidget); |
| |
| // Reset the focus to avoid crash on dtor as long as there is no real pointer |
| // object. |
| window_->SetPointerFocus(false); |
| } |
| |
| TEST_P(WaylandScreenTest, GetDisplayMatching) { |
| TestDisplayObserver observer; |
| platform_screen_->AddObserver(&observer); |
| |
| const display::Display primary_display = |
| platform_screen_->GetPrimaryDisplay(); |
| |
| wl::TestOutput* output2 = server_.CreateAndInitializeOutput(); |
| |
| Sync(); |
| |
| // Place it on the right side of the primary display. |
| const gfx::Rect output2_rect = |
| gfx::Rect(primary_display.bounds().width(), 0, 1024, 768); |
| output2->SetRect(output2_rect); |
| output2->Flush(); |
| |
| Sync(); |
| |
| const display::Display second_display = observer.GetDisplay(); |
| EXPECT_EQ(second_display.bounds(), output2_rect); |
| |
| // We have two displays: display1(0:0,1024x768) and display2(1024:0,1024x768). |
| EXPECT_EQ( |
| primary_display.id(), |
| platform_screen_->GetDisplayMatching(gfx::Rect(0, 0, 100, 100)).id()); |
| EXPECT_EQ( |
| second_display.id(), |
| platform_screen_->GetDisplayMatching(gfx::Rect(1024, 0, 10, 10)).id()); |
| |
| // More pixels on second display. |
| EXPECT_EQ( |
| second_display.id(), |
| platform_screen_->GetDisplayMatching(gfx::Rect(1020, 0, 10, 10)).id()); |
| |
| // More pixels on first display. |
| EXPECT_EQ( |
| primary_display.id(), |
| platform_screen_->GetDisplayMatching(gfx::Rect(1018, 0, 10, 10)).id()); |
| |
| // Half pixels on second and half on primary. |
| EXPECT_EQ( |
| primary_display.id(), |
| platform_screen_->GetDisplayMatching(gfx::Rect(1019, 0, 10, 10)).id()); |
| |
| // Place second display 700 pixels below along y axis (1024:700,1024x768) |
| output2->SetRect( |
| gfx::Rect(gfx::Point(output2_rect.x(), output2_rect.y() + 700), |
| output2_rect.size())); |
| output2->Flush(); |
| |
| Sync(); |
| |
| // The match rect is located outside the displays. Primary display must be |
| // returned. |
| EXPECT_EQ( |
| primary_display.id(), |
| platform_screen_->GetDisplayMatching(gfx::Rect(1024, 0, 10, 10)).id()); |
| |
| // At least some of the pixels are located on the display. |
| EXPECT_EQ( |
| primary_display.id(), |
| platform_screen_->GetDisplayMatching(gfx::Rect(1023, 0, 10, 10)).id()); |
| |
| // Most of pixels are located on second display. |
| EXPECT_EQ( |
| second_display.id(), |
| platform_screen_->GetDisplayMatching(gfx::Rect(1023, 695, 10, 10)).id()); |
| |
| // Empty rect results in primary display. |
| EXPECT_EQ(primary_display.id(), |
| platform_screen_->GetDisplayMatching(gfx::Rect(0, 0, 0, 0)).id()); |
| |
| platform_screen_->RemoveObserver(&observer); |
| output2->DestroyGlobal(); |
| Sync(); |
| } |
| |
| TEST_P(WaylandScreenTest, GetDisplayForAcceleratedWidget) { |
| TestDisplayObserver observer; |
| platform_screen_->AddObserver(&observer); |
| |
| const display::Display primary_display = |
| platform_screen_->GetPrimaryDisplay(); |
| |
| // Create an additional display. |
| wl::TestOutput* output2 = server_.CreateAndInitializeOutput(); |
| |
| Sync(); |
| |
| // Place it on the right side of the primary |
| // display. |
| const gfx::Rect output2_rect = |
| gfx::Rect(primary_display.bounds().width(), 0, 1024, 768); |
| output2->SetRect(output2_rect); |
| output2->Flush(); |
| |
| Sync(); |
| |
| const display::Display secondary_display = observer.GetDisplay(); |
| EXPECT_EQ(secondary_display.bounds(), output2_rect); |
| |
| const gfx::AcceleratedWidget widget = window_->GetWidget(); |
| // There must be a primary display used if the window has not received an |
| // enter event yet. |
| ValidateTheDisplayForWidget(widget, primary_display.id()); |
| |
| // Now, send enter event for the surface, which was created before. |
| wl::MockSurface* surface = server_.GetObject<wl::MockSurface>( |
| window_->root_surface()->GetSurfaceId()); |
| ASSERT_TRUE(surface); |
| wl_surface_send_enter(surface->resource(), output_->resource()); |
| |
| Sync(); |
| |
| // The id of the entered display must correspond to the primary output. |
| ValidateTheDisplayForWidget(widget, primary_display.id()); |
| |
| Sync(); |
| |
| // Enter the second output now. |
| wl_surface_send_enter(surface->resource(), output2->resource()); |
| |
| Sync(); |
| |
| // The id of the entered display must still correspond to the primary output. |
| ValidateTheDisplayForWidget(widget, primary_display.id()); |
| |
| // Leave the first output. |
| wl_surface_send_leave(surface->resource(), output_->resource()); |
| |
| Sync(); |
| |
| // The id of the entered display must correspond to the second output. |
| ValidateTheDisplayForWidget(widget, secondary_display.id()); |
| |
| // Leaving the same output twice (check comment in |
| // WaylandWindow::OnEnteredOutputIdRemoved), must be ok and nothing must |
| // change. |
| wl_surface_send_leave(surface->resource(), output_->resource()); |
| |
| Sync(); |
| |
| // The id of the entered display must correspond to the second output. |
| ValidateTheDisplayForWidget(widget, secondary_display.id()); |
| |
| output2->DestroyGlobal(); |
| Sync(); |
| } |
| |
| TEST_P(WaylandScreenTest, GetCursorScreenPoint) { |
| MockPlatformWindowDelegate delegate; |
| std::unique_ptr<WaylandWindow> second_window = |
| CreateWaylandWindowWithProperties(gfx::Rect(0, 0, 1920, 1080), |
| PlatformWindowType::kWindow, |
| gfx::kNullAcceleratedWidget, &delegate); |
| |
| auto* surface = server_.GetObject<wl::MockSurface>( |
| window_->root_surface()->GetSurfaceId()); |
| ASSERT_TRUE(surface); |
| |
| // Announce pointer capability so that WaylandPointer is created on the client |
| // side. |
| wl_seat_send_capabilities(server_.seat()->resource(), |
| WL_SEAT_CAPABILITY_POINTER); |
| |
| Sync(); |
| |
| wl::MockPointer* pointer = server_.seat()->pointer(); |
| ASSERT_TRUE(pointer); |
| |
| uint32_t serial = 0; |
| uint32_t time = 1002; |
| wl_pointer_send_enter(pointer->resource(), ++serial, surface->resource(), 0, |
| 0); |
| wl_pointer_send_motion(pointer->resource(), ++time, wl_fixed_from_int(10), |
| wl_fixed_from_int(20)); |
| |
| Sync(); |
| |
| // WaylandScreen must return the last pointer location. |
| EXPECT_EQ(gfx::Point(10, 20), platform_screen_->GetCursorScreenPoint()); |
| |
| auto* second_surface = server_.GetObject<wl::MockSurface>( |
| second_window->root_surface()->GetSurfaceId()); |
| ASSERT_TRUE(second_surface); |
| // Now, leave the first surface and enter second one. |
| wl_pointer_send_leave(pointer->resource(), ++serial, surface->resource()); |
| wl_pointer_send_enter(pointer->resource(), ++serial, |
| second_surface->resource(), 0, 0); |
| wl_pointer_send_motion(pointer->resource(), ++time, wl_fixed_from_int(20), |
| wl_fixed_from_int(10)); |
| |
| Sync(); |
| |
| // WaylandScreen must return the last pointer location. |
| EXPECT_EQ(gfx::Point(20, 10), platform_screen_->GetCursorScreenPoint()); |
| |
| // Clear pointer focus. |
| wl_pointer_send_leave(pointer->resource(), ++serial, |
| second_surface->resource()); |
| |
| Sync(); |
| |
| // WaylandScreen must return a point, which is located outside of bounds of |
| // any window. Basically, it means that it takes the largest window and adds |
| // 10 pixels to its width and height, and returns the value. |
| const gfx::Rect second_window_bounds = second_window->GetBounds(); |
| // A second window has largest bounds. Thus, these bounds must be taken as a |
| // ground for the point outside any of the surfaces. |
| ASSERT_TRUE(window_->GetBounds() < second_window_bounds); |
| EXPECT_EQ(gfx::Point(second_window_bounds.width() + 10, |
| second_window_bounds.height() + 10), |
| platform_screen_->GetCursorScreenPoint()); |
| |
| // Create a menu window now and ensure cursor position is always sent in |
| // regards to that window bounds. |
| std::unique_ptr<WaylandWindow> menu_window = |
| CreateWaylandWindowWithProperties( |
| gfx::Rect(second_window_bounds.width() - 10, |
| second_window_bounds.height() - 10, 10, 20), |
| PlatformWindowType::kMenu, second_window->GetWidget(), &delegate); |
| |
| Sync(); |
| |
| auto* menu_surface = server_.GetObject<wl::MockSurface>( |
| menu_window->root_surface()->GetSurfaceId()); |
| ASSERT_TRUE(menu_surface); |
| |
| wl_pointer_send_enter(pointer->resource(), ++serial, menu_surface->resource(), |
| 0, 0); |
| wl_pointer_send_motion(pointer->resource(), ++time, wl_fixed_from_int(2), |
| wl_fixed_from_int(1)); |
| |
| Sync(); |
| |
| // The cursor screen point must be converted to the top-level window |
| // coordinates as long as Wayland doesn't provide global coordinates of |
| // surfaces and Chromium assumes those windows are always located at origin |
| // (0,0). For more information, check the comment in |
| // WaylandWindow::UpdateCursorPositionFromEvent. |
| EXPECT_EQ(gfx::Point(1912, 1071), platform_screen_->GetCursorScreenPoint()); |
| |
| // Leave the menu window and enter the top level window. |
| wl_pointer_send_leave(pointer->resource(), ++serial, |
| menu_surface->resource()); |
| wl_pointer_send_enter(pointer->resource(), ++serial, |
| second_surface->resource(), 0, 0); |
| wl_pointer_send_motion(pointer->resource(), ++time, wl_fixed_from_int(1912), |
| wl_fixed_from_int(1071)); |
| |
| Sync(); |
| |
| // WaylandWindow::UpdateCursorPositionFromEvent mustn't convert this point, |
| // because it has already been located on the top-level window. |
| EXPECT_EQ(gfx::Point(1912, 1071), platform_screen_->GetCursorScreenPoint()); |
| |
| wl_pointer_send_leave(pointer->resource(), ++serial, |
| second_surface->resource()); |
| |
| // Now, create a nested menu window and make sure that the cursor screen point |
| // still has been correct. The location of the window is on the right side of |
| // the main menu window. |
| const gfx::Rect menu_window_bounds = menu_window->GetBounds(); |
| std::unique_ptr<WaylandWindow> nested_menu_window = |
| CreateWaylandWindowWithProperties( |
| gfx::Rect(menu_window_bounds.x() + menu_window_bounds.width(), |
| menu_window_bounds.y() + 2, 10, 20), |
| PlatformWindowType::kMenu, second_window->GetWidget(), &delegate); |
| |
| Sync(); |
| |
| auto* nested_menu_surface = server_.GetObject<wl::MockSurface>( |
| nested_menu_window->root_surface()->GetSurfaceId()); |
| ASSERT_TRUE(nested_menu_surface); |
| |
| wl_pointer_send_enter(pointer->resource(), ++serial, |
| nested_menu_surface->resource(), 0, 0); |
| wl_pointer_send_motion(pointer->resource(), ++time, wl_fixed_from_int(2), |
| wl_fixed_from_int(3)); |
| |
| Sync(); |
| |
| EXPECT_EQ(gfx::Point(1922, 1075), platform_screen_->GetCursorScreenPoint()); |
| |
| // Leave the nested surface and enter main menu surface. The cursor screen |
| // point still must be reported correctly. |
| wl_pointer_send_leave(pointer->resource(), ++serial, |
| nested_menu_surface->resource()); |
| wl_pointer_send_enter(pointer->resource(), ++serial, menu_surface->resource(), |
| 0, 0); |
| wl_pointer_send_motion(pointer->resource(), ++time, wl_fixed_from_int(2), |
| wl_fixed_from_int(1)); |
| |
| Sync(); |
| |
| EXPECT_EQ(gfx::Point(1912, 1071), platform_screen_->GetCursorScreenPoint()); |
| } |
| |
| // Checks that the surface that backs the window receives new scale of the |
| // output that it is in. |
| TEST_P(WaylandScreenTest, SetWindowScale) { |
| // Place the window onto the output. |
| wl_surface_send_enter(surface_->resource(), output_->resource()); |
| |
| // Change the scale of the output. Windows looking into that output must get |
| // the new scale and update scale of their buffers. The default UI scale |
| // equals the output scale. |
| const int32_t kTripleScale = 3; |
| output_->SetScale(kTripleScale); |
| output_->Flush(); |
| |
| Sync(); |
| |
| EXPECT_EQ(window_->window_scale(), kTripleScale); |
| EXPECT_EQ(window_->ui_scale_, kTripleScale); |
| |
| // Now simulate the --force-device-scale-factor=1.5 |
| const float kForcedUIScale = 1.5; |
| base::test::ScopedCommandLine command_line; |
| command_line.GetProcessCommandLine()->AppendSwitchASCII( |
| switches::kForceDeviceScaleFactor, |
| base::StringPrintf("%.1f", kForcedUIScale)); |
| display::Display::ResetForceDeviceScaleFactorForTesting(); |
| |
| // Change the scale of the output again. Windows must update scale of |
| // their buffers but the UI scale must get the forced value. |
| const int32_t kDoubleScale = 2; |
| // Question ourselves before questioning others! |
| EXPECT_NE(kForcedUIScale, kDoubleScale); |
| output_->SetScale(kDoubleScale); |
| output_->Flush(); |
| |
| Sync(); |
| |
| EXPECT_EQ(window_->window_scale(), kDoubleScale); |
| EXPECT_EQ(window_->ui_scale_, kForcedUIScale); |
| |
| display::Display::ResetForceDeviceScaleFactorForTesting(); |
| } |
| |
| // Tests that WaylandScreen updates list of displays with additional fractional |
| // scale by taking only decimal part of it and updating the displays using their |
| // existing scale + fractional part. This fractional part comes from GNOME's |
| // accessibility feature called "Large Text". |
| TEST_P(WaylandScreenTest, SetAdditionalScale) { |
| TestDisplayObserver observer; |
| platform_screen_->AddObserver(&observer); |
| |
| const display::Display primary_display = |
| platform_screen_->GetPrimaryDisplay(); |
| |
| wl::TestOutput* output2 = server_.CreateAndInitializeOutput(); |
| |
| Sync(); |
| |
| // Place it on the right side of the primary display. |
| const gfx::Rect output2_rect = |
| gfx::Rect(primary_display.bounds().width(), 0, 1024, 768); |
| output2->SetRect(output2_rect); |
| output2->Flush(); |
| |
| Sync(); |
| |
| const std::vector<float> scales = {0.2, 0.7, 1.3, 1.6, 1.8, 2.3, 2.9, 3.5}; |
| // Pretend GNOME updates scale and sets fractional scale (Large Text feature). |
| for (auto scale : scales) { |
| platform_screen_->SetDeviceScaleFactor(scale); |
| for (auto& display : platform_screen_->GetAllDisplays()) { |
| float whole = 0; |
| // WaylandScreen will get decimal part and use the integer part provided |
| // by wl_output. |
| float expected_scale = std::modf(scale, &whole) + 1.f; |
| EXPECT_EQ(expected_scale, display.device_scale_factor()); |
| } |
| } |
| } |
| |
| namespace { |
| |
| class LazilyConfiguredScreenTest |
| : public WaylandTest, |
| public wl::TestWaylandServerThread::OutputDelegate { |
| public: |
| LazilyConfiguredScreenTest() = default; |
| LazilyConfiguredScreenTest(const LazilyConfiguredScreenTest&) = delete; |
| LazilyConfiguredScreenTest& operator=(const LazilyConfiguredScreenTest&) = |
| delete; |
| ~LazilyConfiguredScreenTest() override = default; |
| |
| void SetUp() override { |
| // Being the server's output delegate allows LazilyConfiguredScreenTest to |
| // manipulate wl_outputs during the server's global objects initialization |
| // phase. See SetupOutputs() function below. |
| server_.set_output_delegate(this); |
| |
| WaylandTest::SetUp(); |
| |
| output_manager_ = connection_->wayland_output_manager(); |
| ASSERT_TRUE(output_manager_); |
| } |
| |
| void TearDown() override { |
| WaylandTest::TearDown(); |
| server_.set_output_delegate(nullptr); |
| } |
| |
| protected: |
| // wl::TestWaylandServerThread::OutputDelegate: |
| void SetupOutputs(wl::TestOutput* primary) override { |
| // Keep the first wl_output announced "unconfigured" and just caches it for |
| // now, so we can exercise WaylandOutputManager::IsOutputReady() function |
| // when wl_output events come in unordered. |
| primary_output_ = primary; |
| |
| // Create/announce a second wl_output object and makes it the first one to |
| // get configuration events (eg: geometry, done, etc). This is achieved by |
| // setting its bounds here. |
| aux_output_ = server_.CreateAndInitializeOutput(); |
| aux_output_->SetRect({0, 0, 800, 600}); |
| } |
| |
| wl::TestOutput* primary_output_ = nullptr; |
| wl::TestOutput* aux_output_ = nullptr; |
| WaylandOutputManager* output_manager_ = nullptr; |
| bool auto_configure; |
| }; |
| |
| } // namespace |
| |
| // Ensures WaylandOutputManager and WaylandScreen properly handle scenarios |
| // where multiple wl_output objects are announced but not "configured" (ie: |
| // size, position, mode, etc sent to client) at bind time. |
| TEST_P(LazilyConfiguredScreenTest, DualOutput) { |
| // Ensure WaylandScreen got properly created and fed with a single display |
| // object, ie: |aux_output_| at server side. |
| EXPECT_TRUE(output_manager_->IsOutputReady()); |
| EXPECT_TRUE(screen_); |
| EXPECT_EQ(1u, screen_->GetAllDisplays().size()); |
| Sync(); |
| |
| // Send wl_output configuration events for the first advertised wl_output |
| // object. ie: |primary_output_| at server side. |
| primary_output_->SetRect({800, 0, kOutputWidth, kOutputHeight}); |
| primary_output_->SetScale(1); |
| primary_output_->Flush(); |
| Sync(); |
| |
| // And make sure it makes its way into the WaylandScreen's display list at |
| // client side. |
| EXPECT_EQ(2u, screen_->GetAllDisplays().size()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, |
| WaylandScreenTest, |
| Values(wl::ServerConfig{ |
| .shell_version = wl::ShellVersion::kStable})); |
| INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test, |
| WaylandScreenTest, |
| Values(wl::ServerConfig{ |
| .shell_version = wl::ShellVersion::kV6})); |
| INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest, |
| LazilyConfiguredScreenTest, |
| Values(wl::ServerConfig{ |
| .shell_version = wl::ShellVersion::kStable})); |
| INSTANTIATE_TEST_SUITE_P(XdgVersionV6Test, |
| LazilyConfiguredScreenTest, |
| Values(wl::ServerConfig{ |
| .shell_version = wl::ShellVersion::kV6})); |
| |
| } // namespace ui |