910e62b5创建于 1月15日历史提交
// 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 "ui/display/linux/test/virtual_display_util_linux.h"

#include <algorithm>
#include <limits>
#include <vector>

#include "base/environment.h"
#include "base/nix/xdg_util.h"
#include "ui/display/display.h"
#include "ui/display/display_list.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/gfx/x/connection.h"
#include "ui/gfx/x/randr.h"
#include "ui/gfx/x/randr_output_manager.h"

namespace {

// Appends a new screen with `resolution` and `dpi` to the specified desktop
// `layout`. Arranges horizontally left to right.
void AppendScreen(x11::RandRMonitorLayout& layout,
                  const gfx::Size& resolution,
                  const gfx::Vector2d& dpi) {
  // Find the rightmost screen layout.
  const x11::RandRMonitorConfig* rightmost_layout = nullptr;
  for (const auto& screen : layout.configs) {
    if (rightmost_layout == nullptr ||
        screen.rect().right() > rightmost_layout->rect().right()) {
      rightmost_layout = &screen;
    }
  }
  layout.configs.emplace_back(
      std::nullopt,
      gfx::Rect(rightmost_layout->rect().right() + 1,
                rightmost_layout->rect().y(), resolution.width(),
                resolution.height()),
      dpi);
}
}  // namespace

namespace display::test {

VirtualDisplayUtilLinux::VirtualDisplayUtilLinux(Screen* screen)
    : screen_(screen),
      randr_output_manager_(std::make_unique<x11::RandROutputManager>(
          /*output_name_prefix=*/"VDU_")),
      initial_layout_(randr_output_manager_->GetLayout()),
      current_layout_(initial_layout_) {
  CHECK(screen_);
  screen_->AddObserver(this);
}
VirtualDisplayUtilLinux::~VirtualDisplayUtilLinux() {
  ResetDisplays();
  screen_->RemoveObserver(this);
}

// static
bool VirtualDisplayUtilLinux::IsAPIAvailable() {
  // Check if XRandR is available with a sufficient number of connected outputs.
  // Skip base::nix::GetSessionType(...), which may return kTty instead of kX11
  // in SSH sessions with virtualized X11 environments.

  constexpr auto kConnected = static_cast<x11::RandR::RandRConnection>(0);
  constexpr auto kDisabled = static_cast<x11::RandR::Crtc>(0);
  x11::Connection* x11_connection = x11::Connection::Get();
  if (!x11_connection) {
    LOG(ERROR) << "X11 is not present.";
    return false;
  }
  x11::RandR& xrandr = x11_connection->randr();
  if (!xrandr.present()) {
    LOG(ERROR) << "XRandR is not present.";
    return false;
  }
  x11::Response<x11::RandR::GetScreenResourcesCurrentReply> screen_resources =
      xrandr.GetScreenResourcesCurrent({x11_connection->default_screen().root})
          .Sync();
  if (!screen_resources.reply) {
    LOG(ERROR) << "GetScreenResourcesCurrent failed.";
    return false;
  }
  int connected_and_disabled_outputs = 0;
  for (const auto& output : screen_resources.reply->outputs) {
    std::unique_ptr<x11::RandR::GetOutputInfoReply> output_reply =
        xrandr.GetOutputInfo(output, screen_resources.reply->config_timestamp)
            .Sync()
            .reply;
    if (output_reply && output_reply->connection == kConnected &&
        output_reply->crtc == kDisabled) {
      connected_and_disabled_outputs++;
    }
  }
  return connected_and_disabled_outputs >= kMaxDisplays;
}

int64_t VirtualDisplayUtilLinux::AddDisplay(
    const DisplayParams& display_params) {
  if (current_layout_.configs.size() - initial_layout_.configs.size() >
      kMaxDisplays) {
    LOG(ERROR) << "Cannot exceed " << kMaxDisplays << " virtual displays.";
    return kInvalidDisplayId;
  }
  CHECK(!current_layout_.configs.empty());
  last_requested_layout_ = current_layout_;
  AppendScreen(last_requested_layout_, display_params.resolution,
               display_params.dpi);
  randr_output_manager_->SetLayout(last_requested_layout_);
  size_t initial_detected_displays = detected_added_display_ids_.size();
  StartWaiting();
  CHECK_EQ(detected_added_display_ids_.size(), initial_detected_displays + 1u)
      << "Did not detect exactly one new display.";
  // Reconcile the added resizer display ID to the detected display::Display id.
  int64_t new_display_id = detected_added_display_ids_.back();
  x11::RandRMonitorLayout prev_layout = current_layout_;
  current_layout_ = randr_output_manager_->GetLayout();
  for (const auto& layout : current_layout_.configs) {
    auto was_added =
        std::find_if(prev_layout.configs.begin(), prev_layout.configs.end(),
                     [&](const x11::RandRMonitorConfig& prev) {
                       return prev.rect() == layout.rect();
                     });
    if (was_added == prev_layout.configs.end()) {
      display_id_to_randr_id_[new_display_id] = *layout.id();
    }
  }
  return new_display_id;
}

void VirtualDisplayUtilLinux::RemoveDisplay(int64_t display_id) {
  if (!display_id_to_randr_id_.contains(display_id)) {
    LOG(ERROR) << "Invalid display_id. Missing mapping for " << display_id
               << " to randr ID.";
    return;
  }
  last_requested_layout_ = current_layout_;
  x11::RandRMonitorConfig::ScreenId randr_id =
      display_id_to_randr_id_[display_id];
  std::erase_if(last_requested_layout_.configs,
                [&](const x11::RandRMonitorConfig& layout) {
                  return layout.id() == randr_id;
                });
  randr_output_manager_->SetLayout(last_requested_layout_);
  StartWaiting();
}

void VirtualDisplayUtilLinux::ResetDisplays() {
  last_requested_layout_ = initial_layout_;
  randr_output_manager_->SetLayout(last_requested_layout_);
  StartWaiting();
  current_layout_ = randr_output_manager_->GetLayout();
}

void VirtualDisplayUtilLinux::OnDisplayAdded(
    const display::Display& new_display) {
  detected_added_display_ids_.push_back(new_display.id());
  OnDisplayAddedOrRemoved(new_display.id());
}

void VirtualDisplayUtilLinux::OnDisplaysRemoved(
    const display::Displays& removed_displays) {
  for (const auto& display : removed_displays) {
    base::EraseIf(
        display_id_to_randr_id_,
        [&](std::pair<DisplayId, x11::RandRMonitorConfig::ScreenId>& pair) {
          return pair.first == display.id();
        });
    base::EraseIf(detected_added_display_ids_,
                  [&](DisplayId& id) { return id == display.id(); });
    OnDisplayAddedOrRemoved(display.id());
  }
}

void VirtualDisplayUtilLinux::OnDisplayAddedOrRemoved(int64_t id) {
  if (!RequestedLayoutIsSet()) {
    return;
  }
  StopWaiting();
}

bool VirtualDisplayUtilLinux::RequestedLayoutIsSet() {
  // Checks that the number of virtual displays (delta of last requested layout
  // minus initial layout) is equal to the number of detected virtual displays.
  return last_requested_layout_.configs.size() -
             initial_layout_.configs.size() ==
         detected_added_display_ids_.size();
}

void VirtualDisplayUtilLinux::StartWaiting() {
  CHECK(!run_loop_);
  if (RequestedLayoutIsSet()) {
    return;
  }
  run_loop_ = std::make_unique<base::RunLoop>();
  run_loop_->Run();
  run_loop_.reset();
}
void VirtualDisplayUtilLinux::StopWaiting() {
  CHECK(run_loop_);
  run_loop_->Quit();
}

// static
std::unique_ptr<VirtualDisplayUtil> VirtualDisplayUtil::TryCreate(
    Screen* screen) {
  if (!VirtualDisplayUtilLinux::IsAPIAvailable()) {
    return nullptr;
  }
  return std::make_unique<VirtualDisplayUtilLinux>(screen);
}

}  // namespace display::test