diff --git a/ui/ozone/platform/wayland/host/wayland_output_manager.cc b/ui/ozone/platform/wayland/host/wayland_output_manager.cc index 828f64a0ed837e..c5516a88acfe9a 100644 --- a/ui/ozone/platform/wayland/host/wayland_output_manager.cc +++ b/ui/ozone/platform/wayland/host/wayland_output_manager.cc @@ -53,6 +53,14 @@ void WaylandOutputManager::RemoveWaylandOutput(uint32_t output_id) { if (output_it == output_list_.end()) return; + // Remove WaylandOutput in following order : + // 1. from `WaylandSurface::entered_outputs_` + // 2. from `WaylandScreen::display_list_` + // 3. from `WaylandOutputManager::output_list_` + auto* wayland_window_manager = connection_->wayland_window_manager(); + for (auto* window : wayland_window_manager->GetAllWindows()) + window->RemoveEnteredOutput(output_id); + if (wayland_screen_) wayland_screen_->OnOutputRemoved(output_id); output_list_.erase(output_it); diff --git a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc index 016e8274102c7f..4b2502d40ff6ca 100644 --- a/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc +++ b/ui/ozone/platform/wayland/host/wayland_screen_unittest.cc @@ -141,6 +141,77 @@ TEST_P(WaylandScreenTest, OutputBaseTest) { 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) { + wl::TestOutput* output1 = server_.output(); + 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 + gfx::Rect output2_rect(output1_rect.right(), 0, 800, 600); + output2->SetRect(output2_rect); + output2->Flush(); + Sync(); + + // Add a third display. + wl::TestOutput* output3 = server_.CreateAndInitializeOutput(); + Sync(); + // The third display is located to the right of second display + gfx::Rect output3_rect(output2_rect.right(), 0, 800, 600); + output3->SetRect(output3_rect); + output3->Flush(); + Sync(); + + EXPECT_EQ(3u, platform_screen_->GetAllDisplays().size()); + + wl::MockSurface* surface = server_.GetObject( + window_->root_surface()->GetSurfaceId()); + ASSERT_TRUE(surface); + + wl_surface_send_enter(surface->resource(), output1->resource()); + wl_surface_send_enter(surface->resource(), output2->resource()); + Sync(); + // The window entered two outputs + auto entered_outputs = window_->root_surface()->entered_outputs(); + EXPECT_EQ(2u, entered_outputs.size()); + + wl_surface_send_enter(surface->resource(), output3->resource()); + Sync(); + // The window entered three outputs + entered_outputs = window_->root_surface()->entered_outputs(); + EXPECT_EQ(3u, entered_outputs.size()); + + // Destroy third display + output3->DestroyGlobal(); + Sync(); + entered_outputs = window_->root_surface()->entered_outputs(); + EXPECT_EQ(2u, entered_outputs.size()); + + // Destroy second display + output2->DestroyGlobal(); + Sync(); + entered_outputs = window_->root_surface()->entered_outputs(); + EXPECT_EQ(1u, entered_outputs.size()); + + // Add a second display. + output2 = server_.CreateAndInitializeOutput(); + Sync(); + // The second display is located to the right of first display + output2->SetRect(output2_rect); + output2->Flush(); + Sync(); + + wl_surface_send_enter(surface->resource(), output2->resource()); + Sync(); + + // The window entered two outputs + entered_outputs = window_->root_surface()->entered_outputs(); + EXPECT_EQ(2u, entered_outputs.size()); +} + TEST_P(WaylandScreenTest, MultipleOutputsAddedAndRemoved) { TestDisplayObserver observer; platform_screen_->AddObserver(&observer); diff --git a/ui/ozone/platform/wayland/host/wayland_surface.cc b/ui/ozone/platform/wayland/host/wayland_surface.cc index 0b2dd20bfc0a43..9077f8c26c75c2 100644 --- a/ui/ozone/platform/wayland/host/wayland_surface.cc +++ b/ui/ozone/platform/wayland/host/wayland_surface.cc @@ -352,8 +352,9 @@ void WaylandSurface::Enter(void* data, auto* const surface = static_cast(data); DCHECK(surface); - surface->entered_outputs_.emplace_back( - static_cast(wl_output_get_user_data(output))); + auto* wayland_output = + static_cast(wl_output_get_user_data(output)); + surface->entered_outputs_.emplace_back(wayland_output); if (surface->root_window_) surface->root_window_->OnEnteredOutputIdAdded(); @@ -366,20 +367,33 @@ void WaylandSurface::Leave(void* data, auto* const surface = static_cast(data); DCHECK(surface); - auto entered_outputs_it_ = std::find( - surface->entered_outputs_.begin(), surface->entered_outputs_.end(), - static_cast(wl_output_get_user_data(output))); - // Workaround: when a user switches physical output between two displays, - // a surface does not necessarily receive enter events immediately or until - // a user resizes/moves it. This means that switching output between + auto* wayland_output = + static_cast(wl_output_get_user_data(output)); + surface->RemoveEnteredOutput(wayland_output->output_id()); +} + +void WaylandSurface::RemoveEnteredOutput(uint32_t output_id) { + if (entered_outputs().empty()) + return; + + auto entered_outputs_it_ = std::find_if( + entered_outputs_.begin(), entered_outputs_.end(), + [&output_id](auto* it) { return it->output_id() == output_id; }); + + // The `entered_outputs_` list should be updated, + // 1. for wl_surface::leave, when a user switches physical output between two + // displays, a surface does not necessarily receive enter events immediately + // or until a user resizes/moves it. This means that switching output between // displays in a single output mode results in leave events, but the surface // might not have received enter event before. Thus, remove the id of the // output that the surface leaves only if it was stored before. - if (entered_outputs_it_ != surface->entered_outputs_.end()) - surface->entered_outputs_.erase(entered_outputs_it_); + // 2. for wl_registry::global_remove, when wl_output is removed by a server + // after the display is unplugged or switched off. + if (entered_outputs_it_ != entered_outputs_.end()) + entered_outputs_.erase(entered_outputs_it_); - if (surface->root_window_) - surface->root_window_->OnEnteredOutputIdRemoved(); + if (root_window_) + root_window_->OnEnteredOutputIdRemoved(); } // static diff --git a/ui/ozone/platform/wayland/host/wayland_surface.h b/ui/ozone/platform/wayland/host/wayland_surface.h index ef3efefa028f74..e025bfd80b00f9 100644 --- a/ui/ozone/platform/wayland/host/wayland_surface.h +++ b/ui/ozone/platform/wayland/host/wayland_surface.h @@ -122,6 +122,10 @@ class WaylandSurface { // |parent|. Callers take ownership of the wl_subsurface. wl::Object CreateSubsurface(WaylandSurface* parent); + // When display is removed, the WaylandOutput from `entered_outputs_` should + // be removed. + void RemoveEnteredOutput(uint32_t id); + private: // Holds information about each explicit synchronization buffer release. struct ExplicitReleaseInfo { diff --git a/ui/ozone/platform/wayland/host/wayland_window.cc b/ui/ozone/platform/wayland/host/wayland_window.cc index f7a4583dea03c8..45a130dd0dbfda 100644 --- a/ui/ozone/platform/wayland/host/wayland_window.cc +++ b/ui/ozone/platform/wayland/host/wayland_window.cc @@ -181,6 +181,10 @@ void WaylandWindow::SetPointerFocus(bool focus) { UpdateCursorShape(cursor_); } +void WaylandWindow::RemoveEnteredOutput(uint32_t output_id) { + root_surface_->RemoveEnteredOutput(output_id); +} + bool WaylandWindow::StartDrag(const ui::OSExchangeData& data, int operation, gfx::NativeCursor cursor, diff --git a/ui/ozone/platform/wayland/host/wayland_window.h b/ui/ozone/platform/wayland/host/wayland_window.h index 600a15ce711585..7eedd3883edd5a 100644 --- a/ui/ozone/platform/wayland/host/wayland_window.h +++ b/ui/ozone/platform/wayland/host/wayland_window.h @@ -127,6 +127,9 @@ class WaylandWindow : public PlatformWindow, update_visual_size_immediately_ = update_immediately; } + // Remove WaylandOutput associated with WaylandSurface of this window. + void RemoveEnteredOutput(uint32_t output_id); + // WmDragHandler bool StartDrag(const ui::OSExchangeData& data, int operation,