blob: 2387522aa9f56c48ce28b7360bb81e9f8ef0117f [file] [log] [blame]
// Copyright 2018 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.h>
#include <wayland-server.h>
#include <memory>
#include "base/memory/raw_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/test/bind.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/display/types/display_constants.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/host/wayland_seat.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/mock_wayland_platform_window_delegate.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/test_zaura_shell.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(const TestDisplayObserver&) = delete;
TestDisplayObserver& operator=(const TestDisplayObserver&) = delete;
~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 OnDidRemoveDisplays() override {
if (did_remove_display_closure_)
did_remove_display_closure_.Run();
}
void OnDisplayMetricsChanged(const display::Display& display,
uint32_t changed_metrics) override {
changed_metrics_ = changed_metrics;
display_ = display;
}
void set_did_remove_display_closure(base::RepeatingClosure closure) {
did_remove_display_closure_ = std::move(closure);
}
private:
uint32_t changed_metrics_ = 0;
display::Display display_;
display::Display removed_display_;
base::RepeatingClosure did_remove_display_closure_{};
};
} // namespace
class WaylandScreenTest : public WaylandTest {
public:
WaylandScreenTest() = default;
WaylandScreenTest(const WaylandScreenTest&) = delete;
WaylandScreenTest& operator=(const WaylandScreenTest&) = delete;
~WaylandScreenTest() override = default;
void SetUp() override {
WaylandTest::SetUp();
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
auto* output = server->output();
output->SetRect({kOutputWidth, kOutputHeight});
output->SetScale(1);
output->Flush();
});
output_manager_ = connection_->wayland_output_manager();
ASSERT_TRUE(output_manager_);
EXPECT_TRUE(output_manager_->IsOutputReady());
platform_screen_ = output_manager_->CreateWaylandScreen();
output_manager_->InitWaylandScreen(platform_screen_.get());
}
protected:
std::unique_ptr<WaylandWindow> CreateWaylandWindowWithProperties(
const gfx::Rect& bounds,
PlatformWindowType window_type,
gfx::AcceleratedWidget parent_widget,
MockWaylandPlatformWindowDelegate* delegate) {
PlatformWindowInitProperties properties;
properties.bounds = bounds;
properties.type = window_type;
properties.parent_widget = parent_widget;
return delegate->CreateWaylandWindow(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);
}
raw_ptr<wl::TestOutput> output_ = nullptr;
raw_ptr<WaylandOutputManager> output_manager_ = nullptr;
std::unique_ptr<WaylandScreen> platform_screen_;
};
// 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));
}
// In multi-monitor setup, the `entered_outputs_` list should be updated when
// the display is unplugged or switched off.
TEST_P(WaylandScreenTest, EnteredOutputListAfterDisplayRemoval) {
// These have to be stored on the client thread, but must be used only on the
// server thread.
wl::TestOutput* output1 = nullptr;
wl::TestOutput* output2 = nullptr;
wl::TestOutput* output3 = nullptr;
gfx::Rect output1_rect;
PostToServerAndWait(
[&output1, &output1_rect](wl::TestWaylandServerThread* server) {
output1 = server->output();
ASSERT_TRUE(output1);
output1_rect = server->output()->GetRect();
});
// Add a second display.
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
output2 = server->CreateAndInitializeOutput();
ASSERT_TRUE(output2);
});
// The second display is located to the right of first display
gfx::Rect output2_rect(output1_rect.right(), 0, 800, 600);
PostToServerAndWait(
[output2, &output2_rect](wl::TestWaylandServerThread* server) {
output2->SetRect(output2_rect);
output2->Flush();
});
// Add a third display.
PostToServerAndWait([&output3](wl::TestWaylandServerThread* server) {
output3 = server->CreateAndInitializeOutput();
ASSERT_TRUE(output3);
});
// The third display is located to the right of second display
gfx::Rect output3_rect(output2_rect.right(), 0, 800, 600);
PostToServerAndWait(
[output3, &output3_rect](wl::TestWaylandServerThread* server) {
output3->SetRect(output3_rect);
output3->Flush();
});
WaitForAllDisplaysReady();
EXPECT_EQ(3u, platform_screen_->GetAllDisplays().size());
const uint32_t surface_id = window_->root_surface()->get_surface_id();
PostToServerAndWait(
[output1, output2, surface_id](wl::TestWaylandServerThread* server) {
auto* surface = server->GetObject<wl::MockSurface>(surface_id);
wl_surface_send_enter(surface->resource(), output1->resource());
wl_surface_send_enter(surface->resource(), output2->resource());
});
// The window entered two outputs
auto entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(2u, entered_outputs.size());
PostToServerAndWait(
[output3, surface_id](wl::TestWaylandServerThread* server) {
auto* surface = server->GetObject<wl::MockSurface>(surface_id);
wl_surface_send_enter(surface->resource(), output3->resource());
});
// The window entered three outputs
entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(3u, entered_outputs.size());
// Destroy third display
PostToServerAndWait([&output3](wl::TestWaylandServerThread* server) {
output3->DestroyGlobal();
output3 = nullptr;
});
entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(2u, entered_outputs.size());
// Destroy second display
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
output2->DestroyGlobal();
output2 = nullptr;
});
entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(1u, entered_outputs.size());
// Add a second display.
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
ASSERT_FALSE(output2);
output2 = server->CreateAndInitializeOutput();
ASSERT_TRUE(output2);
});
// The second display is located to the right of first display
PostToServerAndWait(
[output2, &output2_rect](wl::TestWaylandServerThread* server) {
output2->SetRect(output2_rect);
output2->Flush();
});
PostToServerAndWait(
[output2, surface_id](wl::TestWaylandServerThread* server) {
auto* surface = server->GetObject<wl::MockSurface>(surface_id);
wl_surface_send_enter(surface->resource(), output2->resource());
});
// The window entered two outputs
entered_outputs = window_->root_surface()->entered_outputs();
EXPECT_EQ(2u, entered_outputs.size());
}
TEST_P(WaylandScreenTest, MultipleOutputsAddedAndRemoved) {
// This has to be stored on the client thread, but must be used only on the
// server thread.
wl::TestOutput* output2 = nullptr;
TestDisplayObserver observer;
platform_screen_->AddObserver(&observer);
const int64_t old_primary_display_id =
platform_screen_->GetPrimaryDisplay().id();
gfx::Rect output1_rect;
PostToServerAndWait([&output1_rect](wl::TestWaylandServerThread* server) {
output1_rect = server->output()->GetRect();
});
ASSERT_FALSE(output1_rect.IsEmpty());
// Add a second display.
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
output2 = server->CreateAndInitializeOutput();
ASSERT_TRUE(output2);
});
// The second display is located to the right of first display like
// | || |.
gfx::Rect output2_rect(output1_rect.width(), 0, 800, 600);
PostToServerAndWait(
[output2, &output2_rect](wl::TestWaylandServerThread* server) {
output2->SetRect(output2_rect);
output2->Flush();
});
WaitForAllDisplaysReady();
// 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);
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
output2->DestroyGlobal();
output2 = nullptr;
});
ASSERT_FALSE(output2);
// 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.
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
ASSERT_FALSE(output2);
output2 = server->CreateAndInitializeOutput();
ASSERT_TRUE(output2);
});
// Updates rect again.
PostToServerAndWait(
[output2, &output2_rect](wl::TestWaylandServerThread* server) {
output2->SetRect(output2_rect);
output2->Flush();
});
WaitForAllDisplaysReady();
// 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);
output2_rect = gfx::Rect(0, 0, 1024, 768);
PostToServerAndWait([&output1_rect, &output2_rect,
output2](wl::TestWaylandServerThread* server) {
auto* output = server->output();
output->SetRect(output1_rect);
output->Flush();
output2->SetRect(output2_rect);
output2->Flush();
});
// Ensure that output2 is now the primary one.
EXPECT_EQ(platform_screen_->GetPrimaryDisplay().id(), added_display_id);
// Remove the primary display now.
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
output2->DestroyGlobal();
output2 = nullptr;
});
ASSERT_FALSE(output2);
// 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, OutputPropertyChangesMissingLogicalSize) {
TestDisplayObserver observer;
platform_screen_->AddObserver(&observer);
const uint32_t output_id = 7;
const int64_t display_id = 1ll << 34;
const gfx::Point origin(50, 70);
const gfx::Size physical_size(1200, 1600);
const wl_output_transform panel_transform = WL_OUTPUT_TRANSFORM_90;
const wl_output_transform logical_transform = WL_OUTPUT_TRANSFORM_NORMAL;
const gfx::Insets insets = gfx::Insets::TLBR(10, 20, 30, 40);
const float scale = 2;
// Test with missing logical size. Should fall back to calculating from
// physical size.
platform_screen_->OnOutputAddedOrUpdated(
{output_id, display_id, origin, gfx::Size(), physical_size, insets, scale,
panel_transform, logical_transform, "display"});
const display::Display new_display(observer.GetDisplay());
EXPECT_EQ(output_id, platform_screen_->GetOutputIdForDisplayId(display_id));
EXPECT_EQ(new_display.id(), display_id);
EXPECT_EQ(new_display.bounds(), gfx::Rect(origin, gfx::Size(800, 600)));
EXPECT_EQ(new_display.GetSizeInPixel(), gfx::Size(1600, 1200));
gfx::Rect expected_work_area(new_display.bounds());
expected_work_area.Inset(insets);
EXPECT_EQ(new_display.work_area(), expected_work_area);
EXPECT_EQ(new_display.panel_rotation(), display::Display::ROTATE_270);
EXPECT_EQ(new_display.rotation(), display::Display::ROTATE_0);
EXPECT_EQ(new_display.device_scale_factor(), scale);
EXPECT_EQ(new_display.label(), "display");
platform_screen_->RemoveObserver(&observer);
}
TEST_P(WaylandScreenTest, OutputPropertyChangesPrimaryDisplayChanged) {
TestDisplayObserver observer;
platform_screen_->AddObserver(&observer);
display::Display display1(1, gfx::Rect(0, 0, 800, 600));
display::Display display2(2, gfx::Rect(800, 0, 700, 500));
platform_screen_->OnOutputAddedOrUpdated(
{static_cast<uint32_t>(display1.id()), display1.id(),
display1.bounds().origin(), display1.size(), display1.GetSizeInPixel(),
display1.GetWorkAreaInsets(), display1.device_scale_factor(),
WL_OUTPUT_TRANSFORM_NORMAL, WL_OUTPUT_TRANSFORM_NORMAL, std::string()});
platform_screen_->OnOutputAddedOrUpdated(
{static_cast<uint32_t>(display2.id()), display2.id(),
display2.bounds().origin(), display2.size(), display2.GetSizeInPixel(),
display2.GetWorkAreaInsets(), display2.device_scale_factor(),
WL_OUTPUT_TRANSFORM_NORMAL, WL_OUTPUT_TRANSFORM_NORMAL, std::string()});
EXPECT_EQ(platform_screen_->GetPrimaryDisplay(), display1);
// Simulate setting display2 as primary by moving its origin to (0,0) and
// shifting display1 to its left.
display1.set_bounds(gfx::Rect(-800, 0, 800, 600));
display2.set_bounds(gfx::Rect(0, 0, 700, 500));
// Purposely send the output metrics out of order.
platform_screen_->OnOutputAddedOrUpdated(
{static_cast<uint32_t>(display2.id()), display2.id(),
display2.bounds().origin(), display2.size(), display2.GetSizeInPixel(),
display2.GetWorkAreaInsets(), display2.device_scale_factor(),
WL_OUTPUT_TRANSFORM_NORMAL, WL_OUTPUT_TRANSFORM_NORMAL, std::string()});
platform_screen_->OnOutputAddedOrUpdated(
{static_cast<uint32_t>(display1.id()), display1.id(),
display1.bounds().origin(), display1.size(), display1.GetSizeInPixel(),
display1.GetWorkAreaInsets(), display1.device_scale_factor(),
WL_OUTPUT_TRANSFORM_NORMAL, WL_OUTPUT_TRANSFORM_NORMAL, std::string()});
EXPECT_EQ(platform_screen_->GetPrimaryDisplay(), display2);
platform_screen_->RemoveObserver(&observer);
}
TEST_P(WaylandScreenTest, GetAcceleratedWidgetAtScreenPoint) {
const uint32_t surface_id = window_->root_surface()->get_surface_id();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
// Now, send enter event for the surface, which was created before.
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(surface_id);
ASSERT_TRUE(surface);
wl_surface_send_enter(surface->resource(), server->output()->resource());
});
// 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.
SetPointerFocusedWindow(window_.get());
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_->GetBoundsInDIP();
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);
MockWaylandPlatformWindowDelegate delegate;
auto menu_window_bounds =
gfx::Rect(window_->GetBoundsInDIP().width() - 10,
window_->GetBoundsInDIP().height() - 10, 100, 100);
std::unique_ptr<WaylandWindow> menu_window =
CreateWaylandWindowWithProperties(menu_window_bounds,
PlatformWindowType::kMenu,
window_->GetWidget(), &delegate);
// Imagine the mouse enters a menu window, which is located on top of the main
// window, and gathers focus.
SetPointerFocusedWindow(menu_window.get());
widget_at_screen_point = platform_screen_->GetAcceleratedWidgetAtScreenPoint(
gfx::Point(menu_window->GetBoundsInDIP().x() + 1,
menu_window->GetBoundsInDIP().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.
SetPointerFocusedWindow(window_.get());
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.
SetPointerFocusedWindow(nullptr);
// Part 2: test that the window is found when display's scale changes.
// Update scale.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
server->output()->SetScale(2);
server->output()->Flush();
});
auto menu_bounds = menu_window->GetBoundsInDIP();
auto point_in_screen = menu_bounds.origin();
SetPointerFocusedWindow(menu_window.get());
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.
SetPointerFocusedWindow(window_.get());
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);
}
TEST_P(WaylandScreenTest, GetDisplayMatching) {
TestDisplayObserver observer;
platform_screen_->AddObserver(&observer);
const display::Display primary_display =
platform_screen_->GetPrimaryDisplay();
// This has to be stored on the client thread, but must be used only on the
// server thread.
wl::TestOutput* output2 = nullptr;
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
output2 = server->CreateAndInitializeOutput();
ASSERT_TRUE(output2);
});
// Place it on the right side of the primary display.
const gfx::Rect output2_rect =
gfx::Rect(primary_display.bounds().width(), 0, 1024, 768);
PostToServerAndWait(
[output2, output2_rect](wl::TestWaylandServerThread* server) {
output2->SetRect(output2_rect);
output2->Flush();
});
WaitForAllDisplaysReady();
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)
PostToServerAndWait(
[output2, output2_rect](wl::TestWaylandServerThread* server) {
output2->SetRect(
gfx::Rect(gfx::Point(output2_rect.x(), output2_rect.y() + 700),
output2_rect.size()));
output2->Flush();
});
// 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);
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
output2->DestroyGlobal();
output2 = nullptr;
});
}
// Regression test for https://crbug.com/1362872.
TEST_P(WaylandScreenTest, GetPrimaryDisplayAfterRemoval) {
TestDisplayObserver observer;
platform_screen_->AddObserver(&observer);
const display::Display primary_display =
platform_screen_->GetPrimaryDisplay();
ASSERT_NE(primary_display.id(), display::kInvalidDisplayId);
ASSERT_EQ(1u, platform_screen_->GetAllDisplays().size());
// This results in an ASAN error unless GetPrimaryDisplay() is correctly
// implemented for empty display list. More details in the crbug above.
observer.set_did_remove_display_closure(base::BindLambdaForTesting([&]() {
ASSERT_EQ(0u, platform_screen_->GetAllDisplays().size());
auto display = platform_screen_->GetPrimaryDisplay();
EXPECT_EQ(display::kDefaultDisplayId, display.id());
}));
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
server->output()->DestroyGlobal();
});
platform_screen_->RemoveObserver(&observer);
}
TEST_P(WaylandScreenTest, GetDisplayForAcceleratedWidget) {
TestDisplayObserver observer;
platform_screen_->AddObserver(&observer);
const display::Display primary_display =
platform_screen_->GetPrimaryDisplay();
// Create an additional display. This has to be stored on the client thread,
// but must be used only on the server thread.
wl::TestOutput* output2 = nullptr;
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
output2 = server->CreateAndInitializeOutput();
ASSERT_TRUE(output2);
});
// Place it on the right side of the primary
// display.
const gfx::Rect output2_rect =
gfx::Rect(primary_display.bounds().width(), 0, 1024, 768);
PostToServerAndWait(
[output2, output2_rect](wl::TestWaylandServerThread* server) {
output2->SetRect(output2_rect);
output2->Flush();
});
WaitForAllDisplaysReady();
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());
const uint32_t surface_id = window_->root_surface()->get_surface_id();
// Now, send enter event for the surface, which was created before.
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
wl::MockSurface* surface = server->GetObject<wl::MockSurface>(surface_id);
ASSERT_TRUE(surface);
wl_surface_send_enter(surface->resource(), server->output()->resource());
});
// The id of the entered display must correspond to the primary output.
ValidateTheDisplayForWidget(widget, primary_display.id());
// Enter the second output now.
PostToServerAndWait(
[output2, surface_id](wl::TestWaylandServerThread* server) {
wl_surface_send_enter(
server->GetObject<wl::MockSurface>(surface_id)->resource(),
output2->resource());
});
// The id of the entered display must still correspond to the primary output.
ValidateTheDisplayForWidget(widget, primary_display.id());
// Leave the first output.
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
wl_surface_send_leave(
server->GetObject<wl::MockSurface>(surface_id)->resource(),
server->output()->resource());
});
// 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.
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
wl_surface_send_leave(
server->GetObject<wl::MockSurface>(surface_id)->resource(),
server->output()->resource());
});
// The id of the entered display must correspond to the second output.
ValidateTheDisplayForWidget(widget, secondary_display.id());
PostToServerAndWait([&output2](wl::TestWaylandServerThread* server) {
output2->DestroyGlobal();
output2 = nullptr;
});
}
TEST_P(WaylandScreenTest, GetCursorScreenPoint) {
MockWaylandPlatformWindowDelegate delegate;
std::unique_ptr<WaylandWindow> second_window =
CreateWaylandWindowWithProperties(gfx::Rect(0, 0, 1920, 1080),
PlatformWindowType::kWindow,
gfx::kNullAcceleratedWidget, &delegate);
const uint32_t surface_id = window_->root_surface()->get_surface_id();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* surface = server->GetObject<wl::MockSurface>(surface_id);
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);
});
ASSERT_TRUE(connection_->seat()->pointer());
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
wl::MockPointer* pointer = server->seat()->pointer();
auto* surface = server->GetObject<wl::MockSurface>(surface_id);
wl_pointer_send_enter(pointer->resource(), server->GetNextSerial(),
surface->resource(), 0, 0);
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_motion(pointer->resource(), server->GetNextTime(),
wl_fixed_from_int(10), wl_fixed_from_int(20));
wl_pointer_send_frame(pointer->resource());
});
// WaylandScreen must return the last pointer location.
EXPECT_EQ(gfx::Point(10, 20), platform_screen_->GetCursorScreenPoint());
const uint32_t second_surface_id =
second_window->root_surface()->get_surface_id();
PostToServerAndWait(
[surface_id, second_surface_id](wl::TestWaylandServerThread* server) {
wl::MockPointer* pointer = server->seat()->pointer();
auto* surface = server->GetObject<wl::MockSurface>(surface_id);
auto* second_surface =
server->GetObject<wl::MockSurface>(second_surface_id);
ASSERT_TRUE(second_surface);
// Now, leave the first surface and enter second one.
wl_pointer_send_leave(pointer->resource(), server->GetNextSerial(),
surface->resource());
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_enter(pointer->resource(), server->GetNextSerial(),
second_surface->resource(), 0, 0);
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_motion(pointer->resource(), server->GetNextTime(),
wl_fixed_from_int(20), wl_fixed_from_int(10));
wl_pointer_send_frame(pointer->resource());
});
// WaylandScreen must return the last pointer location.
EXPECT_EQ(gfx::Point(20, 10), platform_screen_->GetCursorScreenPoint());
// Clear pointer focus.
PostToServerAndWait([second_surface_id](wl::TestWaylandServerThread* server) {
wl::MockPointer* pointer = server->seat()->pointer();
auto* second_surface =
server->GetObject<wl::MockSurface>(second_surface_id);
wl_pointer_send_leave(pointer->resource(), server->GetNextSerial(),
second_surface->resource());
wl_pointer_send_frame(pointer->resource());
});
// 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->GetBoundsInDIP();
// 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_->GetBoundsInDIP() < 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);
const uint32_t menu_surface_id =
menu_window->root_surface()->get_surface_id();
PostToServerAndWait([menu_surface_id](wl::TestWaylandServerThread* server) {
auto* menu_surface = server->GetObject<wl::MockSurface>(menu_surface_id);
ASSERT_TRUE(menu_surface);
wl::MockPointer* pointer = server->seat()->pointer();
wl_pointer_send_enter(pointer->resource(), server->GetNextSerial(),
menu_surface->resource(), 0, 0);
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_motion(pointer->resource(), server->GetNextTime(),
wl_fixed_from_int(2), wl_fixed_from_int(1));
wl_pointer_send_frame(pointer->resource());
});
// 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.
PostToServerAndWait([menu_surface_id,
second_surface_id](wl::TestWaylandServerThread* server) {
auto* menu_surface = server->GetObject<wl::MockSurface>(menu_surface_id);
ASSERT_TRUE(menu_surface);
auto* second_surface =
server->GetObject<wl::MockSurface>(second_surface_id);
wl::MockPointer* pointer = server->seat()->pointer();
wl_pointer_send_leave(pointer->resource(), server->GetNextSerial(),
menu_surface->resource());
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_enter(pointer->resource(), server->GetNextSerial(),
second_surface->resource(), 0, 0);
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_motion(pointer->resource(), server->GetNextTime(),
wl_fixed_from_int(1912), wl_fixed_from_int(1071));
wl_pointer_send_frame(pointer->resource());
});
// 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());
PostToServerAndWait([second_surface_id](wl::TestWaylandServerThread* server) {
wl::MockPointer* pointer = server->seat()->pointer();
auto* second_surface =
server->GetObject<wl::MockSurface>(second_surface_id);
wl_pointer_send_leave(pointer->resource(), server->GetNextSerial(),
second_surface->resource());
wl_pointer_send_frame(pointer->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->GetBoundsInDIP();
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);
const uint32_t nested_menu_surface_id =
nested_menu_window->root_surface()->get_surface_id();
PostToServerAndWait(
[nested_menu_surface_id](wl::TestWaylandServerThread* server) {
wl::MockPointer* pointer = server->seat()->pointer();
auto* nested_menu_surface =
server->GetObject<wl::MockSurface>(nested_menu_surface_id);
ASSERT_TRUE(nested_menu_surface);
wl_pointer_send_enter(pointer->resource(), server->GetNextSerial(),
nested_menu_surface->resource(), 0, 0);
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_motion(pointer->resource(), server->GetNextTime(),
wl_fixed_from_int(2), wl_fixed_from_int(3));
wl_pointer_send_frame(pointer->resource());
});
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.
PostToServerAndWait([nested_menu_surface_id,
menu_surface_id](wl::TestWaylandServerThread* server) {
wl::MockPointer* pointer = server->seat()->pointer();
auto* nested_menu_surface =
server->GetObject<wl::MockSurface>(nested_menu_surface_id);
auto* menu_surface = server->GetObject<wl::MockSurface>(menu_surface_id);
wl_pointer_send_leave(pointer->resource(), server->GetNextSerial(),
nested_menu_surface->resource());
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_enter(pointer->resource(), server->GetNextSerial(),
menu_surface->resource(), 0, 0);
wl_pointer_send_frame(pointer->resource());
wl_pointer_send_motion(pointer->resource(), server->GetNextTime(),
wl_fixed_from_int(2), wl_fixed_from_int(1));
wl_pointer_send_frame(pointer->resource());
});
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) {
constexpr int32_t kTripleScale = 3;
const uint32_t surface_id = window_->root_surface()->get_surface_id();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* output = server->output();
// Place the window onto the output.
wl_surface_send_enter(
server->GetObject<wl::MockSurface>(surface_id)->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.
output->SetScale(kTripleScale);
output->Flush();
});
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.
constexpr int32_t kDoubleScale = 2;
// Question ourselves before questioning others!
EXPECT_NE(kForcedUIScale, kDoubleScale);
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
server->output()->SetScale(kDoubleScale);
server->output()->Flush();
});
EXPECT_EQ(window_->window_scale(), kDoubleScale);
EXPECT_EQ(window_->ui_scale_, kForcedUIScale);
display::Display::ResetForceDeviceScaleFactorForTesting();
}
// Regression test for https://crbug.com/1346534.
//
// Scenario: With (at least) one output connected and a surface, with no output
// associated yet, ie: wl_surface.enter event not received yet for that surface,
// which implies in its scale being set to the primary output's scale at its
// initialization, any primary output scale update (or other properties that
// lead to scale change) must be propagated to the window.
TEST_P(WaylandScreenTest, SetWindowScaleWithoutEnteredOutput) {
// Test pre-conditions: single output setup whereas |output_| is the primary
// output managed by |output_manager_|, with initial scale == 1.
ASSERT_EQ(1u, output_manager_->GetAllOutputs().size());
// Ensure |surface_| has not entered any wl_output. Assuming |window_| has
// been already initialized with |output_|'s scale.
const uint32_t surface_id = window_->root_surface()->get_surface_id();
PostToServerAndWait([surface_id](wl::TestWaylandServerThread* server) {
auto* output = server->output();
ASSERT_TRUE(output);
ASSERT_EQ(1, output->GetScale());
wl_surface_send_leave(
server->GetObject<wl::MockSurface>(surface_id)->resource(),
server->output()->resource());
});
EXPECT_FALSE(window_->GetPreferredEnteredOutputId());
// Change |output_|'s scale and make sure |window_|'s scale is update
// accordingly.
PostToServerAndWait([](wl::TestWaylandServerThread* server) {
server->output()->SetScale(2);
server->output()->Flush();
});
EXPECT_EQ(window_->window_scale(), 2);
EXPECT_EQ(window_->ui_scale(), 2);
}
// Checks that output transform is properly translated into Display orientation.
// The first one is counter-clockwise, while the latter is clockwise.
TEST_P(WaylandScreenTest, Transform) {
constexpr std::pair<wl_output_transform, display::Display::Rotation>
kTestData[] = {
{WL_OUTPUT_TRANSFORM_NORMAL, display::Display::ROTATE_0},
{WL_OUTPUT_TRANSFORM_90, display::Display::ROTATE_270},
{WL_OUTPUT_TRANSFORM_180, display::Display::ROTATE_180},
{WL_OUTPUT_TRANSFORM_270, display::Display::ROTATE_90},
// Flipped transforms are not supported.
{WL_OUTPUT_TRANSFORM_FLIPPED, display::Display::ROTATE_0},
{WL_OUTPUT_TRANSFORM_FLIPPED_90, display::Display::ROTATE_0},
{WL_OUTPUT_TRANSFORM_FLIPPED_180, display::Display::ROTATE_0},
{WL_OUTPUT_TRANSFORM_FLIPPED_270, display::Display::ROTATE_0},
};
for (const auto& [transform, expected_rotation] : kTestData) {
PostToServerAndWait(
[new_transform = transform](wl::TestWaylandServerThread* server) {
server->output()->SetTransform(new_transform);
server->output()->Flush();
});
auto main_display = platform_screen_->GetPrimaryDisplay();
EXPECT_EQ(main_display.rotation(), expected_rotation);
}
}
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 {
// This can be set on the client thread as the server is not running yet.
ASSERT_FALSE(server_.IsRunning());
server_.set_output_delegate(this);
WaylandTest::SetUp();
output_manager_ = connection_->wayland_output_manager();
ASSERT_TRUE(output_manager_);
}
void TearDown() override {
WaylandTest::TearDown();
PostToServerAndWait(
[output = aux_output_](wl::TestWaylandServerThread* server) {
output->DestroyGlobal();
server->set_output_delegate(nullptr);
});
aux_output_ = nullptr;
primary_output_ = nullptr;
}
protected:
// wl::TestWaylandServerThread::OutputDelegate:
void SetupOutputs(wl::TestOutput* primary) override {
// This happens before the server starts to run.
ASSERT_FALSE(server_.IsRunning());
// 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});
}
// Must only be accessed on the server thread.
raw_ptr<wl::TestOutput> primary_output_ = nullptr;
raw_ptr<wl::TestOutput> aux_output_ = nullptr;
raw_ptr<WaylandOutputManager> output_manager_ = nullptr;
};
} // 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());
// Send wl_output configuration events for the first advertised wl_output
// object. ie: |primary_output_| at server side.
PostToServerAndWait(
[output = primary_output_](wl::TestWaylandServerThread* server) {
output->SetRect({800, 0, kOutputWidth, kOutputHeight});
output->SetScale(1);
output->Flush();
});
// And make sure it makes its way into the WaylandScreen's display list at
// client side.
EXPECT_EQ(2u, screen_->GetAllDisplays().size());
}
class WaylandAuraShellScreenTest : public WaylandScreenTest {
public:
void SetUp() override {
WaylandScreenTest::SetUp();
// Submit surfaces in pixel coordinates when aura_shell is used.
// TODO(oshima): Do this in all tests with ash_shell.
connection_->set_surface_submission_in_pixel_coordinates(true);
}
};
TEST_P(WaylandAuraShellScreenTest, OutputPropertyChanges) {
TestDisplayObserver observer;
platform_screen_->AddObserver(&observer);
constexpr gfx::Rect kPhysicalBounds{800, 600};
PostToServerAndWait([kPhysicalBounds](wl::TestWaylandServerThread* server) {
server->output()->SetRect(kPhysicalBounds);
server->output()->Flush();
});
uint32_t changed_values = display::DisplayObserver::DISPLAY_METRIC_BOUNDS |
display::DisplayObserver::DISPLAY_METRIC_WORK_AREA;
EXPECT_EQ(observer.GetAndClearChangedMetrics(), changed_values);
constexpr gfx::Rect kExpectedBounds{800, 600};
EXPECT_EQ(observer.GetDisplay().bounds(), kExpectedBounds);
constexpr gfx::Size expected_size_in_pixels{800, 600};
EXPECT_EQ(observer.GetDisplay().GetSizeInPixel(), expected_size_in_pixels);
EXPECT_EQ(observer.GetDisplay().work_area(), kExpectedBounds);
// Test work area.
constexpr gfx::Rect kNewWorkArea{10, 20, 700, 500};
const gfx::Insets expected_inset = kExpectedBounds.InsetsFrom(kNewWorkArea);
PostToServerAndWait([expected_inset](wl::TestWaylandServerThread* server) {
auto* output = server->output();
ASSERT_TRUE(output->GetAuraOutput());
output->GetAuraOutput()->SetInsets(expected_inset);
output->Flush();
});
changed_values = display::DisplayObserver::DISPLAY_METRIC_WORK_AREA;
EXPECT_EQ(observer.GetAndClearChangedMetrics(), changed_values);
// Bounds should be unchanged.
EXPECT_EQ(observer.GetDisplay().bounds(), kExpectedBounds);
EXPECT_EQ(observer.GetDisplay().GetSizeInPixel(), expected_size_in_pixels);
// Work area should have new value.
EXPECT_EQ(observer.GetDisplay().work_area(), kNewWorkArea);
// Test scaling.
constexpr int32_t kNewScaleValue = 2;
const gfx::Size scaled_logical_size =
gfx::ScaleToRoundedSize(kPhysicalBounds.size(), 1.f / kNewScaleValue);
PostToServerAndWait(
[scaled_logical_size](wl::TestWaylandServerThread* server) {
auto* output = server->output();
output->xdg_output()->SetLogicalSize(scaled_logical_size);
output->Flush();
});
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(), kNewScaleValue);
// Logical bounds should shrink due to scaling.
const gfx::Rect scaled_bounds{400, 300};
EXPECT_EQ(observer.GetDisplay().bounds(), scaled_bounds);
// Size in pixel should stay unscaled.
EXPECT_EQ(observer.GetDisplay().GetSizeInPixel(), expected_size_in_pixels);
gfx::Rect scaled_work_area(scaled_bounds);
scaled_work_area.Inset(expected_inset);
EXPECT_EQ(observer.GetDisplay().work_area(), scaled_work_area);
// Test rotation.
PostToServerAndWait(
[scaled_logical_size](wl::TestWaylandServerThread* server) {
gfx::Size transposed = scaled_logical_size;
transposed.Transpose();
auto* output = server->output();
output->SetTransform(WL_OUTPUT_TRANSFORM_90);
output->xdg_output()->SetLogicalSize(transposed);
output->Flush();
});
changed_values = display::DisplayObserver::DISPLAY_METRIC_WORK_AREA |
display::DisplayObserver::DISPLAY_METRIC_BOUNDS |
display::DisplayObserver::DISPLAY_METRIC_ROTATION;
EXPECT_EQ(observer.GetAndClearChangedMetrics(), changed_values);
// Logical bounds should now be rotated to portrait.
const gfx::Rect rotated_bounds{300, 400};
EXPECT_EQ(observer.GetDisplay().bounds(), rotated_bounds);
// Size in pixel gets rotated too, but stays unscaled.
const gfx::Size rotated_size_in_pixels{600, 800};
EXPECT_EQ(observer.GetDisplay().GetSizeInPixel(), rotated_size_in_pixels);
gfx::Rect rotated_work_area(rotated_bounds);
rotated_work_area.Inset(expected_inset);
EXPECT_EQ(observer.GetDisplay().work_area(), rotated_work_area);
EXPECT_EQ(observer.GetDisplay().panel_rotation(),
display::Display::Rotation::ROTATE_270);
EXPECT_EQ(observer.GetDisplay().rotation(),
display::Display::Rotation::ROTATE_270);
platform_screen_->RemoveObserver(&observer);
}
// Regression test for crbug.com/1310981.
// Some devices use display panels built in portrait orientation, but are used
// in landscape orientation. Thus their physical bounds are in portrait
// orientation along with an offset transform, which differs from the usual
// landscape oriented bounds.
TEST_P(WaylandAuraShellScreenTest,
OutputPropertyChangesWithPortraitPanelRotation) {
TestDisplayObserver observer;
platform_screen_->AddObserver(&observer);
// wl_output.geometry origin is set in DIP screen coordinates.
constexpr gfx::Point kOrigin(50, 70);
constexpr gfx::Size kPhysicalSize(1200, 1600);
PostToServerAndWait(
[kOrigin, kPhysicalSize](wl::TestWaylandServerThread* server) {
// wl_output.mode size is sent in physical coordinates, so it has
// portrait dimensions for a display panel with portrait natural
// orientation.
server->output()->SetRect({kOrigin, kPhysicalSize});
});
// Inset is sent in logical coordinates.
constexpr gfx::Insets kInsets = gfx::Insets::TLBR(10, 20, 30, 40);
gfx::Size scaled_logical_size = gfx::ScaleToRoundedSize(kPhysicalSize, 0.5);
scaled_logical_size.Transpose();
PostToServerAndWait([kInsets, scaled_logical_size](
wl::TestWaylandServerThread* server) {
auto* output = server->output();
ASSERT_TRUE(output->GetAuraOutput());
output->GetAuraOutput()->SetInsets(kInsets);
// Display panel's natural orientation is in portrait, so it needs a
// transform of 90 degrees to be in landscape.
output->SetTransform(WL_OUTPUT_TRANSFORM_90);
// Begin with the logical transform at 0 degrees.
output->GetAuraOutput()->SetLogicalTransform(WL_OUTPUT_TRANSFORM_NORMAL);
output->xdg_output()->SetLogicalSize(scaled_logical_size);
output->Flush();
});
uint32_t changed_values =
display::DisplayObserver::DISPLAY_METRIC_BOUNDS |
display::DisplayObserver::DISPLAY_METRIC_WORK_AREA |
display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR |
display::DisplayObserver::DISPLAY_METRIC_ROTATION;
EXPECT_EQ(observer.GetAndClearChangedMetrics(), changed_values);
// Logical bounds should be in landscape.
const gfx::Rect kExpectedBounds(kOrigin, gfx::Size(800, 600));
EXPECT_EQ(observer.GetDisplay().bounds(), kExpectedBounds);
const gfx::Size expected_size_in_pixels(1600, 1200);
EXPECT_EQ(observer.GetDisplay().GetSizeInPixel(), expected_size_in_pixels);
gfx::Rect expected_work_area(kExpectedBounds);
expected_work_area.Inset(kInsets);
EXPECT_EQ(observer.GetDisplay().work_area(), expected_work_area);
// Panel rotation and display rotation should have an offset.
EXPECT_EQ(observer.GetDisplay().panel_rotation(),
display::Display::Rotation::ROTATE_270);
EXPECT_EQ(observer.GetDisplay().rotation(),
display::Display::Rotation::ROTATE_0);
// Further rotate the display to logical portrait orientation, which is 180
// with the natural orientation offset.
scaled_logical_size.Transpose();
PostToServerAndWait(
[scaled_logical_size](wl::TestWaylandServerThread* server) {
auto* output = server->output();
output->SetTransform(WL_OUTPUT_TRANSFORM_180);
output->GetAuraOutput()->SetLogicalTransform(WL_OUTPUT_TRANSFORM_90);
output->xdg_output()->SetLogicalSize(scaled_logical_size);
output->Flush();
});
changed_values = display::DisplayObserver::DISPLAY_METRIC_BOUNDS |
display::DisplayObserver::DISPLAY_METRIC_WORK_AREA |
display::DisplayObserver::DISPLAY_METRIC_ROTATION;
EXPECT_EQ(observer.GetAndClearChangedMetrics(), changed_values);
// Logical bounds should now be portrait.
const gfx::Rect portrait_bounds(kOrigin, gfx::Size(600, 800));
EXPECT_EQ(observer.GetDisplay().bounds(), portrait_bounds);
const gfx::Size portrait_size_in_pixels(1200, 1600);
EXPECT_EQ(observer.GetDisplay().GetSizeInPixel(), portrait_size_in_pixels);
gfx::Rect portrait_work_area(portrait_bounds);
portrait_work_area.Inset(kInsets);
EXPECT_EQ(observer.GetDisplay().work_area(), portrait_work_area);
// Panel rotation and display rotation should still have an offset.
EXPECT_EQ(observer.GetDisplay().panel_rotation(),
display::Display::Rotation::ROTATE_180);
EXPECT_EQ(observer.GetDisplay().rotation(),
display::Display::Rotation::ROTATE_270);
platform_screen_->RemoveObserver(&observer);
}
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
WaylandScreenTest,
Values(wl::ServerConfig{}));
INSTANTIATE_TEST_SUITE_P(
XdgVersionStableTestWithAuraShell,
WaylandScreenTest,
Values(wl::ServerConfig{
.enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
INSTANTIATE_TEST_SUITE_P(
XdgVersionStableTest,
WaylandAuraShellScreenTest,
Values(wl::ServerConfig{
.enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
INSTANTIATE_TEST_SUITE_P(XdgVersionStableTest,
LazilyConfiguredScreenTest,
Values(wl::ServerConfig{}));
INSTANTIATE_TEST_SUITE_P(
XdgVersionStableTestWithAuraShell,
LazilyConfiguredScreenTest,
Values(wl::ServerConfig{
.enable_aura_shell = wl::EnableAuraShellProtocol::kEnabled}));
} // namespace ui