chromium/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc

// 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/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"

Values;

namespace ui {

namespace {

constexpr uint32_t kNumberOfDisplays =;
constexpr uint32_t kOutputWidth =;
constexpr uint32_t kOutputHeight =;

// Helper that gets the rightmost x coordinate for the given `output`.
int GetRightX(const wl::TestOutput* output) {}

class TestDisplayObserver : public display::DisplayObserver {};

}  // namespace

class WaylandScreenTest : public WaylandTest {};

// Tests whether a primary output has been initialized before PlatformScreen is
// created.
TEST_P(WaylandScreenTest, OutputBaseTest) {}

// In multi-monitor setup, the `entered_outputs_` list should be updated when
// the display is unplugged or switched off.
TEST_P(WaylandScreenTest, EnteredOutputListAfterDisplayRemoval) {}

TEST_P(WaylandScreenTest, MultipleOutputsAddedAndRemoved) {}

TEST_P(WaylandScreenTest, OutputPropertyChangesMissingLogicalSize) {}

TEST_P(WaylandScreenTest, OutputPropertyChangesPrimaryDisplayChanged) {}

TEST_P(WaylandScreenTest, OutputPropertyChangesOverscanInsets) {}

TEST_P(WaylandScreenTest, GetAcceleratedWidgetAtScreenPoint) {}

TEST_P(WaylandScreenTest, GetLocalProcessWidgetAtPoint) {}

TEST_P(WaylandScreenTest, GetDisplayMatching) {}

// Regression test for https://crbug.com/1362872.
TEST_P(WaylandScreenTest, GetPrimaryDisplayAfterRemoval) {}

TEST_P(WaylandScreenTest, GetDisplayForAcceleratedWidget) {}

TEST_P(WaylandScreenTest, GetCursorScreenPoint) {}

// Checks that the surface that backs the window receives new scale of the
// output that it is in.
TEST_P(WaylandScreenTest, SetWindowScale) {}

// Lacros uses screen coordinates so this test doesn't make any sense there.
#if !BUILDFLAG(IS_CHROMEOS_LACROS)

// 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) {}

#endif  // !BUILDFLAG(IS_CHROMEOS_LACROS)

// 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) {}

// 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(WaylandScreenTest, DualOutput) {}

// Regression test for crbug.com/1408304. Ensures that the WaylandScreen's
// internal output state is consistent when propagating change notifications to
// clients.
TEST_P(WaylandScreenTest, OutputStateIsConsistentWhenNotifyingObservers) {}

#if BUILDFLAG(IS_CHROMEOS_LACROS)

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) {
    auto* output = server->output();
    output->SetPhysicalAndLogicalBounds(kPhysicalBounds);
    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();
    output->SetLogicalInsets(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([&](wl::TestWaylandServerThread* server) {
    auto* output = server->output();
    output->SetLogicalSize(scaled_logical_size);
    output->SetDeviceScaleFactor(kNewScaleValue);
    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([&](wl::TestWaylandServerThread* server) {
    auto* output = server->output();
    output->SetPanelTransform(WL_OUTPUT_TRANSFORM_90);
    output->SetLogicalTransform(WL_OUTPUT_TRANSFORM_90);
    output->ApplyLogicalTranspose();
    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([&](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()->SetPhysicalAndLogicalBounds({kOrigin, kPhysicalSize});
  });

  // Inset is sent in logical coordinates.
  constexpr gfx::Insets kInsets = gfx::Insets::TLBR(10, 20, 30, 40);
  const gfx::Size scaled_logical_size =
      gfx::ScaleToRoundedSize(kPhysicalSize, 0.5);
  PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
    auto* output = server->output();
    output->SetLogicalSize(scaled_logical_size);
    output->SetDeviceScaleFactor(2);
    output->SetLogicalInsets(kInsets);
    // Display panel's natural orientation is in portrait, so it needs a
    // transform of 90 degrees to be in landscape.
    output->SetPanelTransform(WL_OUTPUT_TRANSFORM_90);
    // Begin with the logical transform at 0 degrees.
    output->SetLogicalTransform(WL_OUTPUT_TRANSFORM_NORMAL);
    output->ApplyLogicalTranspose();
    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.
  PostToServerAndWait([&](wl::TestWaylandServerThread* server) {
    auto* output = server->output();
    output->SetPanelTransform(WL_OUTPUT_TRANSFORM_180);
    output->SetLogicalTransform(WL_OUTPUT_TRANSFORM_90);
    output->ApplyLogicalTranspose();
    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);
}

TEST_P(WaylandAuraShellScreenTest, UseCorrectScreenBeforeEnterEvent) {
  // 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;

  PostToServerAndWait([&output1](wl::TestWaylandServerThread* server) {
    output1 = server->output();
    ASSERT_TRUE(output1);
  });

  // Add a second display with scale factor 2.
  PostToServerAndWait(
      [&output1, &output2](wl::TestWaylandServerThread* server) {
        output2 = server->CreateAndInitializeOutput(
            wl::TestOutputMetrics({GetRightX(output1), 0, 800, 600}));
        // Scale Factor 2.
        output2->SetLogicalSize({400, 300});
        output2->SetDeviceScaleFactor(2);
        ASSERT_TRUE(output2);
      });

  WaitForAllDisplaysReady();

  // Create a window on the 2nd display with scale factor 2.
  EXPECT_CALL(delegate_, OnAcceleratedWidgetAvailable(testing::_)).Times(1);
  PlatformWindowInitProperties properties;
  properties.bounds = gfx::Rect(GetRightX(output1), 0, 100, 100);
  properties.type = PlatformWindowType::kWindow;
  window_ =
      delegate_.CreateWaylandWindow(connection_.get(), std::move(properties));
  ASSERT_NE(widget_, gfx::kNullAcceleratedWidget);

  window_->Show(false);

  // Make sure that entered output is zero but the scale factor is correctly
  // set based on the bounds.
  EXPECT_EQ(window_->root_surface()->entered_outputs().size(), 0u);
}

#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

#if !BUILDFLAG(IS_CHROMEOS_LACROS)
INSTANTIATE_TEST_SUITE_P();

#else

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}));

#endif  // BUILDFLAG(IS_CHROMEOS_LACROS)

}  // namespace ui