chromium/ui/display/win/test/virtual_display_util_win.cc

// Copyright 2023 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/win/test/virtual_display_util_win.h"

#include <algorithm>
#include <iterator>

#include "base/containers/flat_tree.h"
#include "base/logging.h"
#include "third_party/win_virtual_display/driver/public/properties.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/win/display_config_helper.h"
#include "ui/display/win/screen_win.h"

namespace display::test {

namespace {

// Comparer for gfx:Size for use in sorting algorithms.
struct SizeCompare {
  bool operator()(const gfx::Size& a, const gfx::Size& b) const {
    if (a.width() == b.width()) {
      return a.height() < b.height();
    }
    return a.width() < b.width();
  }
};

// Returns true if the current environment is detected to be headless.
bool IsHeadless() {
  DISPLAY_DEVICE adapter{};
  adapter.cb = sizeof(adapter);
  for (DWORD i = 0;
       EnumDisplayDevices(nullptr, i, &adapter, EDD_GET_DEVICE_INTERFACE_NAME);
       i++) {
    if (adapter.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) {
      // If any driver is considered attached, then not headless.
      // Note the default stub driver ("Microsoft Basic Display Driver") is
      // never considered "attached".
      return false;
    }
  }
  return true;
}

// Sets the default display resolution to the specified size.
bool SetDisplayResolution(const gfx::Size& size) {
  DEVMODE mode;
  mode.dmSize = sizeof(DEVMODE);
  mode.dmPelsWidth = size.width();
  mode.dmPelsHeight = size.height();
  mode.dmFields = DM_PELSWIDTH | DM_PELSHEIGHT;
  LONG settings_result = ::ChangeDisplaySettings(&mode, 0);
  LOG_IF(ERROR, settings_result != DISP_CHANGE_SUCCESSFUL)
      << "ChangeDisplaySettings failed with result: " << settings_result;
  return settings_result == DISP_CHANGE_SUCCESSFUL;
}

// Creates a comma separated list of display strings (for debug /logs).
std::string JoinDisplayStrings(const std::vector<display::Display>& displays) {
  std::vector<std::string> parts;
  for (const auto& display : displays) {
    parts.push_back(display.ToString());
  }
  return base::JoinString(parts, ", ");
}

}  // namespace

struct DisplayParams {
  explicit DisplayParams(MonitorConfig config) : monitor_config(config) {}
  MonitorConfig monitor_config;
};

VirtualDisplayUtilWin::VirtualDisplayUtilWin(Screen* screen)
    : screen_(screen), is_headless_(IsHeadless()) {
  screen_->AddObserver(this);
  initial_displays_ = screen_->GetAllDisplays();
  if (IsAPIAvailable()) {
    ResetDisplays();
  }
}

VirtualDisplayUtilWin::~VirtualDisplayUtilWin() {
  if (IsAPIAvailable()) {
    driver_controller_.Reset();
    if (virtual_displays_.size() > 0) {
      current_config_ = DriverProperties();
      StartWaiting();
    }
  }
  screen_->RemoveObserver(this);

  std::vector<display::Display> current_displays = screen_->GetAllDisplays();

  // Restore the display to the initial size and wait for displays to update.
  // This is necessary because Windows may change the resolution of the default
  // display (Microsoft Basic Display) while virtual displays exists and does
  // not automatically restore the original resolution.
  if (is_headless_) {
    CHECK_EQ(initial_displays_.size(), 1u);
    CHECK_EQ(current_displays.size(), 1u);
    const gfx::Size& initial_size = initial_displays_.front().size();
    bool set_resolution_result = SetDisplayResolution(initial_size);
    CHECK(set_resolution_result) << "SetDisplayResolution failed.";
    DisplayConfigWaiter waiter(screen_);
    waiter.WaitForDisplaySizes(std::vector<gfx::Size>{initial_size});

    // Basic check to ensure that the # of displays and resolutions match what
    // was in place when the utility was constructed. Helps prevent lingering
    // changes that may impact other tests running on the bot (i.e.
    // crbug.com/341931537).
    CHECK_EQ(current_displays.size(), initial_displays_.size())
        << "# of displays mismatch after driver was closed.";

    std::multiset<gfx::Size, SizeCompare> initial_sizes, current_sizes;
    for (const auto& display : screen_->GetAllDisplays()) {
      current_sizes.insert(display.size());
    }
    for (const auto& display : initial_displays_) {
      initial_sizes.insert(display.size());
    }
    CHECK(initial_sizes == current_sizes)
        << "Display resolution does not match initial config. Initial: "
        << JoinDisplayStrings(initial_displays_)
        << ", current: " << JoinDisplayStrings(current_displays);
  }
}

// static
bool VirtualDisplayUtilWin::IsAPIAvailable() {
  return DisplayDriverController::IsDriverInstalled();
}

int64_t VirtualDisplayUtilWin::AddDisplay(uint8_t id,
                                          const DisplayParams& display_params) {
  if (virtual_displays_.find(id) != virtual_displays_.end()) {
    LOG(ERROR) << "Duplicate virtual display ID added: " << id;
    return kInvalidDisplayId;
  }
  std::vector<MonitorConfig> monitors;
  monitors = current_config_.requested_configs();
  MonitorConfig new_config = display_params.monitor_config;
  new_config.set_product_code(id);
  monitors.push_back(new_config);
  if (!SetDriverProperties(DriverProperties(monitors))) {
    return kInvalidDisplayId;
  }
  StartWaiting();
  auto created_display = virtual_displays_.find(id);
  CHECK(created_display != virtual_displays_.end());
  return created_display->second;
}

void VirtualDisplayUtilWin::RemoveDisplay(int64_t display_id) {
  auto it = std::find_if(
      virtual_displays_.begin(), virtual_displays_.end(),
      [&display_id](const std::pair<unsigned short, int64_t>& obj) {
        return obj.second == display_id;
      });
  if (it == virtual_displays_.end()) {
    LOG(WARNING) << "Display ID " << display_id << " is not a virtual display.";
    return;
  }
  std::vector<MonitorConfig> monitors = current_config_.requested_configs();
  std::erase_if(monitors, [&it](MonitorConfig& c) {
    return c.product_code() == it->first;
  });
  if (SetDriverProperties(DriverProperties(monitors))) {
    StartWaiting();
  }
}

void VirtualDisplayUtilWin::ResetDisplays() {
  // Internal virtual display ID used for replacing a headless stub display.
  // This is arbitrarily chosen to be the max value that
  // MonitorConfig::set_product_code() supports.
  constexpr unsigned short kHeadlessDisplayId = 65535;
  DriverProperties prev_config = current_config_;
  DriverProperties new_config{};
  if (is_headless_) {
    LOG(INFO) << "Headless detected, setting base virtual display.";
    // In a headless environment, when no working display adapter is attached,
    // windows defaults to a pseudo/stub display. This causes the first call to
    // AddDisplay() to not add a display, but *replace* the stub with our
    // virtualized adapter. Therefore, we replace the default stub display with
    // our own virtual one. See:
    // https://learn.microsoft.com/en-us/windows-hardware/drivers/display/support-for-headless-systems
    std::vector<MonitorConfig> configs{k1024x768.monitor_config};
    configs[0].set_product_code(kHeadlessDisplayId);
    new_config = DriverProperties(configs);
  }
  SetDriverProperties(new_config);
  if (current_config_.requested_configs().size() !=
      prev_config.requested_configs().size()) {
    StartWaiting();
  }
}

void VirtualDisplayUtilWin::OnDisplayAdded(
    const display::Display& new_display) {
  std::vector<MonitorConfig> requested = current_config_.requested_configs();
  HMONITOR monitor = ::MonitorFromPoint(
      win::ScreenWin::DIPToScreenPoint(new_display.work_area().CenterPoint())
          .ToPOINT(),
      MONITOR_DEFAULTTONEAREST);
  std::optional<DISPLAYCONFIG_PATH_INFO> path_info =
      ::display::win::GetDisplayConfigPathInfo(monitor);
  if (::display::win::GetDisplayManufacturerId(path_info) ==
      kDriverMonitorManufacturer) {
    UINT16 product_code = ::display::win::GetDisplayProductCode(path_info);
    auto it = virtual_displays_.find(product_code);
    // Should never detect multiple displays with the same product code.
    CHECK(it == virtual_displays_.end())
        << "Detected duplicate virtual display product code:" << product_code;
    virtual_displays_[product_code] = new_display.id();
  }
  OnDisplayAddedOrRemoved(new_display.id());
}

void VirtualDisplayUtilWin::OnDisplaysRemoved(
    const display::Displays& removed_displays) {
  for (const auto& display : removed_displays) {
    base::EraseIf(virtual_displays_,
                  [&display](const std::pair<unsigned short, int64_t>& obj) {
                    return obj.second == display.id();
                  });
    OnDisplayAddedOrRemoved(display.id());
  }
}

bool VirtualDisplayUtilWin::SetDriverProperties(DriverProperties properties) {
  if (!driver_controller_.SetDisplayConfig(properties)) {
    LOG(ERROR) << "SetDisplayConfig failed: Failed to set display properties.";
    return false;
  }
  current_config_ = properties;
  return true;
}

void VirtualDisplayUtilWin::OnDisplayAddedOrRemoved(int64_t id) {
  if (virtual_displays_.size() != current_config_.requested_configs().size()) {
    return;
  }
  StopWaiting();
}

void VirtualDisplayUtilWin::StartWaiting() {
  CHECK(!run_loop_);
  run_loop_ = std::make_unique<base::RunLoop>();
  if (virtual_displays_.size() != current_config_.requested_configs().size()) {
    run_loop_->Run();
  }
  run_loop_.reset();
}

void VirtualDisplayUtilWin::StopWaiting() {
  CHECK(run_loop_);
  run_loop_->Quit();
}

const DisplayParams VirtualDisplayUtilWin::k1920x1080 =
    DisplayParams(MonitorConfig::k1920x1080);
const DisplayParams VirtualDisplayUtilWin::k1024x768 =
    DisplayParams(MonitorConfig::k1024x768);

// VirtualDisplayUtil definitions:
const DisplayParams VirtualDisplayUtil::k1920x1080 =
    VirtualDisplayUtilWin::k1920x1080;
const DisplayParams VirtualDisplayUtil::k1024x768 =
    VirtualDisplayUtilWin::k1024x768;

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

DisplayConfigWaiter::DisplayConfigWaiter(Screen* screen) : screen_(screen) {
  screen_->AddObserver(this);
}

DisplayConfigWaiter::~DisplayConfigWaiter() {
  screen_->RemoveObserver(this);
}

void DisplayConfigWaiter::WaitForDisplaySizes(std::vector<gfx::Size> sizes) {
  wait_for_sizes_ = std::move(sizes);
  CHECK(!run_loop_);
  run_loop_ = std::make_unique<base::RunLoop>();
  if (!IsWaitConditionMet()) {
    run_loop_->Run();
  }
  run_loop_.reset();
}

void DisplayConfigWaiter::OnDisplayAdded(const display::Display& new_display) {
  if (IsWaitConditionMet()) {
    run_loop_->Quit();
  }
}

void DisplayConfigWaiter::OnDisplaysRemoved(
    const display::Displays& removed_displays) {
  if (IsWaitConditionMet()) {
    run_loop_->Quit();
  }
}

bool DisplayConfigWaiter::IsWaitConditionMet() {
  std::multiset<gfx::Size, SizeCompare> expected_sizes, current_sizes;
  for (display::Display display : screen_->GetAllDisplays()) {
    current_sizes.insert(display.size());
  }
  for (const auto& size : wait_for_sizes_) {
    expected_sizes.insert(size);
  }
  return expected_sizes == current_sizes;
}

}  // namespace display::test