| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/exo/wayland/output_controller.h" |
| |
| #include <aura-output-management-server-protocol.h> |
| #include <secure-output-unstable-v1-server-protocol.h> |
| #include <wayland-server-core.h> |
| #include <xdg-output-unstable-v1-server-protocol.h> |
| |
| #include <algorithm> |
| |
| #include "ash/shell.h" |
| #include "base/containers/contains.h" |
| #include "components/exo/wayland/output_configuration_change.h" |
| #include "components/exo/wayland/wayland_display_output.h" |
| #include "components/exo/wayland/wl_output.h" |
| #include "components/exo/wayland/zaura_output_manager.h" |
| #include "components/exo/wayland/zaura_output_manager_v2.h" |
| #include "components/exo/wayland/zcr_secure_output.h" |
| #include "components/exo/wayland/zxdg_output_manager.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| |
| namespace exo::wayland { |
| |
| OutputController::OutputController(Delegate* delegate) : delegate_(delegate) { |
| // Clients need to bind aura output manager globals before binding any of the |
| // wl_output globals. This is necessary to ensure clients won't receive events |
| // for outputs before receiving events for the aura output manager. |
| wl_display* display = delegate_->GetWaylandDisplay(); |
| aura_output_manager_v2_ = |
| std::make_unique<AuraOutputManagerV2>(base::BindRepeating( |
| &OutputController::GetActiveOutputs, base::Unretained(this))); |
| wl_global_create(display, &zaura_output_manager_v2_interface, |
| kZAuraOutputManagerV2Version, aura_output_manager_v2_.get(), |
| bind_aura_output_manager_v2); |
| wl_global_create(display, &zaura_output_manager_interface, |
| kZAuraOutputManagerVersion, nullptr, |
| bind_aura_output_manager); |
| |
| // Populate the initial output state. |
| OnDidProcessDisplayChanges( |
| {ash::Shell::Get()->display_manager()->active_display_list(), |
| Displays(), |
| {}}); |
| |
| wl_global_create(display, &zcr_secure_output_v1_interface, |
| /*version=*/1, nullptr, bind_secure_output); |
| wl_global_create(display, &zxdg_output_manager_v1_interface, |
| /*version=*/3, nullptr, bind_zxdg_output_manager); |
| |
| display_manager_observation_.Observe(ash::Shell::Get()->display_manager()); |
| ash::Shell::Get()->AddShellObserver(this); |
| } |
| |
| OutputController::~OutputController() { |
| ash::Shell::Get()->RemoveShellObserver(this); |
| } |
| |
| void OutputController::OnDidProcessDisplayChanges( |
| const DisplayConfigurationChange& configuration_change) { |
| // Process added displays before removed displays to ensure exo does not leave |
| // clients in a temporary state where no outputs are present. |
| for (const display::Display& added_display : |
| configuration_change.added_displays) { |
| auto output = std::make_unique<WaylandDisplayOutput>(added_display); |
| output->set_global(wl_global_create(delegate_->GetWaylandDisplay(), |
| &wl_output_interface, kWlOutputVersion, |
| output.get(), bind_output)); |
| CHECK_EQ(outputs_.count(added_display.id()), 0u); |
| outputs_.insert(std::make_pair(added_display.id(), std::move(output))); |
| } |
| |
| for (const auto& change : configuration_change.display_metrics_changes) { |
| if (auto* wayland_display_output = |
| GetWaylandDisplayOutput(change.display->id())) { |
| wayland_display_output->SendDisplayMetricsChanges(change.display.get(), |
| change.changed_metrics); |
| } |
| } |
| |
| // Propagate display configuration changes to aura output manager clients. |
| const bool needs_done = |
| ProcessDisplayChangesForAuraOutputManager(configuration_change); |
| |
| // Remove outputs after propagating config changes as the WaylandDisplayOutput |
| // object may be needed during change notification propagation. |
| for (const display::Display& removed_display : |
| configuration_change.removed_displays) { |
| // There should always be at least one display tracked by Exo. |
| CHECK(outputs_.size() > 1); |
| CHECK_EQ(outputs_.count(removed_display.id()), 1u); |
| std::unique_ptr<WaylandDisplayOutput> output = |
| std::move(outputs_[removed_display.id()]); |
| outputs_.erase(removed_display.id()); |
| output.release()->OnDisplayRemoved(); |
| } |
| |
| if (needs_done) { |
| aura_output_manager_v2_->SendDone(); |
| } |
| |
| UpdateActivatedDisplayIfNecessary(); |
| |
| // Flush updated outputs to clients immediately. |
| // TODO(crbug.com/40943061): Exo should be updated to automatically flush |
| // buffers at the end of task processing if necessary. |
| delegate_->Flush(); |
| } |
| |
| void OutputController::OnDisplayForNewWindowsChanged() { |
| UpdateActivatedDisplayIfNecessary(); |
| } |
| |
| wl_resource* OutputController::GetOutputResource(wl_client* client, |
| int64_t display_id) { |
| CHECK_NE(display_id, display::kInvalidDisplayId); |
| WaylandDisplayOutput* wayland_display_output = |
| GetWaylandDisplayOutput(display_id); |
| return wayland_display_output |
| ? wayland_display_output->GetOutputResourceForClient(client) |
| : nullptr; |
| } |
| |
| WaylandDisplayOutput* OutputController::GetWaylandDisplayOutput( |
| int64_t display_id) { |
| CHECK_NE(display_id, display::kInvalidDisplayId); |
| auto iter = outputs_.find(display_id); |
| return iter == outputs_.end() ? nullptr : iter->second.get(); |
| } |
| |
| WaylandOutputList OutputController::GetActiveOutputs() const { |
| WaylandOutputList output_list; |
| std::ranges::transform(outputs_, std::back_inserter(output_list), |
| [](auto& pair) { return pair.second.get(); }); |
| return output_list; |
| } |
| |
| bool OutputController::ProcessDisplayChangesForAuraOutputManager( |
| const DisplayConfigurationChange& configuration_change) { |
| OutputConfigurationChange output_config_change; |
| |
| std::ranges::transform(configuration_change.added_displays, |
| std::back_inserter(output_config_change.added_outputs), |
| [this](const display::Display& display) { |
| return GetWaylandDisplayOutput(display.id()); |
| }); |
| std::ranges::transform( |
| configuration_change.removed_displays, |
| std::back_inserter(output_config_change.removed_outputs), |
| [this](const display::Display& display) { |
| return GetWaylandDisplayOutput(display.id()); |
| }); |
| for (const DisplayMetricsChange& change : |
| configuration_change.display_metrics_changes) { |
| // Added displays may appear in both added and changed lists, ensure added |
| // outputs are not represented in both added and changed lists. |
| if (!base::Contains(configuration_change.added_displays, |
| change.display.get())) { |
| output_config_change.changed_outputs.emplace_back( |
| GetWaylandDisplayOutput(change.display->id()), |
| change.changed_metrics); |
| } |
| } |
| |
| return aura_output_manager_v2_->OnDidProcessDisplayChanges( |
| output_config_change); |
| } |
| |
| void OutputController::UpdateActivatedDisplayIfNecessary() { |
| const int64_t current_active_display_id = |
| display::Screen::Get()->GetDisplayForNewWindows().id(); |
| if (dispatched_activated_display_id_ == current_active_display_id) { |
| return; |
| } |
| |
| // Since the ordering of observers on the display manager is not well defined |
| // there may be observers that respond to display changes before the |
| // controller, resulting in attempted activations of outputs that have not yet |
| // been created (see b/323403137). |
| // TODO(tluk): Explore solutions that may improve ordering guarantees without |
| // relying on state-tracking within the controller class. |
| auto output_pair = outputs_.find(current_active_display_id); |
| if (output_pair != outputs_.end()) { |
| output_pair->second->SendOutputActivated(); |
| aura_output_manager_v2_->SendOutputActivated(*output_pair->second); |
| dispatched_activated_display_id_ = current_active_display_id; |
| } |
| } |
| |
| } // namespace exo::wayland |