910e62b5创建于 1月15日历史提交
// Copyright 2025 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/screen_win_headless.h"

#include <windows.h>

#include <vector>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_number_conversions_win.h"
#include "base/strings/string_util.h"
#include "base/strings/string_util_win.h"
#include "components/headless/screen_info/headless_screen_info.h"
#include "ui/display/display_finder.h"
#include "ui/display/types/display_constants.h"
#include "ui/display/win/display_info.h"
#include "ui/display/win/dpi.h"
#include "ui/display/win/screen_win_display.h"

namespace display::win {

namespace {

// Headless display device names are fakes that look similar to the real display
// device names.
constexpr WCHAR kHeadlessDisplayDeviceNamePrefix[] = LR"(\\.\HEADLESS_DISPLAY)";

std::wstring GetHeadlessDisplayDeviceNameFromDisplayId(int64_t id) {
  return base::StrCat(
      {kHeadlessDisplayDeviceNamePrefix, base::NumberToWString(id)});
}

int64_t GetHeadlessDisplayIdFromMonitorInfo(const MONITORINFOEX& monitor_info) {
  CHECK(base::StartsWith(monitor_info.szDevice,
                         kHeadlessDisplayDeviceNamePrefix));
  int64_t id;
  CHECK(base::StringToInt64(
      &monitor_info.szDevice[std::size(kHeadlessDisplayDeviceNamePrefix) - 1],
      &id));

  return id;
}

gfx::Vector2dF GetDisplayPhysicalPixelsPerInch(float device_scaling_factor) {
  const int dpi = GetDPIFromScalingFactor(device_scaling_factor);
  return gfx::Vector2dF(dpi, dpi);
}

void SetHeadlessDisplayDeviceName(MONITORINFOEX& monitor_info,
                                  int64_t display_id) {
  const std::wstring device_name =
      GetHeadlessDisplayDeviceNameFromDisplayId(display_id);
  CHECK_LT(device_name.length() + 1, std::size(monitor_info.szDevice));

  base::span<WCHAR> device_name_buf = monitor_info.szDevice;
  std::copy(device_name.begin(), device_name.end(), device_name_buf.begin());
  device_name_buf[device_name.length()] = L'\0';
}

}  // namespace

ScreenWinHeadless::ScreenWinHeadless(
    const std::vector<headless::HeadlessScreenInfo>& screen_infos)
    : ScreenWin(/*initialize_from_system=*/false) {
  CHECK(!screen_infos.empty());

  UpdateFromDisplayInfos(DisplayInfosFromScreenInfo(screen_infos));

  HeadlessScreenManager::Get()->SetDelegate(this);
}

ScreenWinHeadless::~ScreenWinHeadless() {
  HeadlessScreenManager::Get()->SetDelegate(nullptr);
}

int64_t ScreenWinHeadless::GetDisplayIdFromWindow(HWND hwnd,
                                                  DWORD default_options) {
  if (auto monitor_info = MonitorInfoFromWindow(hwnd, default_options)) {
    return GetDisplayIdFromMonitorInfo(monitor_info.value());
  }

  return kInvalidDisplayId;
}

int64_t ScreenWinHeadless::GetDisplayIdFromScreenRect(
    const gfx::Rect& screen_rect) {
  if (auto monitor_info = MonitorInfoFromScreenRect(screen_rect)) {
    return GetDisplayIdFromMonitorInfo(monitor_info.value());
  }

  return GetPrimaryDisplay().id();
}

int ScreenWinHeadless::GetSystemMetricsForDisplayId(int64_t id, int metric) {
  const Display display = GetScreenWinDisplayWithDisplayId(id).display();
  return base::ClampRound(::GetSystemMetrics(metric) *
                          display.device_scale_factor());
}

void ScreenWinHeadless::SetCursorScreenPointForTesting(
    const gfx::Point& point) {
  cursor_screen_point_ = point;
}

gfx::Point ScreenWinHeadless::GetCursorScreenPoint() {
  return cursor_screen_point_;
}

bool ScreenWinHeadless::IsWindowUnderCursor(gfx::NativeWindow window) {
  return GetWindowAtScreenPoint(GetCursorScreenPoint()) == window;
}

gfx::NativeWindow ScreenWinHeadless::GetWindowAtScreenPoint(
    const gfx::Point& point) {
  return GetNativeWindowAtScreenPoint(point, std::set<gfx::NativeWindow>());
}

gfx::NativeWindow ScreenWinHeadless::GetLocalProcessWindowAtPoint(
    const gfx::Point& point,
    const std::set<gfx::NativeWindow>& ignore) {
  return GetRootWindow(GetNativeWindowAtScreenPoint(point, ignore));
}

int ScreenWinHeadless::GetNumDisplays() const {
  return GetAllDisplays().size();
}

const std::vector<Display>& ScreenWinHeadless::GetAllDisplays() const {
  return ScreenWin::GetAllDisplays();
}

Display ScreenWinHeadless::GetDisplayNearestWindow(
    gfx::NativeWindow window) const {
  if (window) {
    return GetDisplayFromScreenRect(GetNativeWindowBoundsInScreen(window));
  }

  return GetPrimaryDisplay();
}

Display ScreenWinHeadless::GetDisplayNearestPoint(
    const gfx::Point& point) const {
  return GetDisplayFromScreenPoint(point);
}

Display ScreenWinHeadless::GetDisplayMatching(
    const gfx::Rect& match_rect) const {
  return GetDisplayFromScreenRect(match_rect);
}

Display ScreenWinHeadless::GetPrimaryDisplay() const {
  // In headless the primary display is always the first display.
  return GetNumDisplays() ? GetAllDisplays()[0] : Display::GetDefaultDisplay();
}

HMONITOR ScreenWinHeadless::HMONITORFromScreenPoint(
    const gfx::Point& screen_point) const {
  NOTREACHED();
}

HMONITOR ScreenWinHeadless::HMONITORFromScreenRect(
    const gfx::Rect& screen_rect) const {
  NOTREACHED();
}

HMONITOR ScreenWinHeadless::HMONITORFromWindow(HWND hwnd,
                                               DWORD default_options) const {
  NOTREACHED();
}

std::optional<MONITORINFOEX> ScreenWinHeadless::MonitorInfoFromScreenPoint(
    const gfx::Point& screen_point) const {
  // ScreenWin::MonitorInfoFromScreenPoint() uses Win32 ::MonitorFromPoint()
  // with MONITOR_DEFAULTTONEAREST flag.
  if (const Display* display =
          FindDisplayNearestPoint(GetAllDisplays(), screen_point)) {
    return GetMONITORINFOFromDisplayId(display->id());
  }

  return std::nullopt;
}

std::optional<MONITORINFOEX> ScreenWinHeadless::MonitorInfoFromHMONITOR(
    HMONITOR monitor) const {
  return std::nullopt;
}

gfx::Rect ScreenWinHeadless::ScreenToDIPRectInWindow(
    gfx::NativeWindow window,
    const gfx::Rect& screen_rect) const {
  // The base class implementation does the right thing, but we want this to be
  // exposed publicly as the rest of display::Screen overrides.
  return ScreenWin::ScreenToDIPRectInWindow(window, screen_rect);
}

gfx::Rect ScreenWinHeadless::DIPToScreenRectInWindow(
    gfx::NativeWindow window,
    const gfx::Rect& dip_rect) const {
  // The base class implementation does the right thing, but we want this to be
  // exposed publicly as the rest of display::Screen overrides.
  return ScreenWin::DIPToScreenRectInWindow(window, dip_rect);
}

bool ScreenWinHeadless::IsHeadless() const {
  return true;
}

std::optional<MONITORINFOEX> ScreenWinHeadless::MonitorInfoFromScreenRect(
    const gfx::Rect& screen_rect) const {
  // ScreenWin::MonitorInfoFromScreenRect() uses Win32 ::MonitorFromRect() with
  // MONITOR_DEFAULTTONEAREST flag.
  if (const Display* display =
          FindDisplayWithBiggestIntersection(GetAllDisplays(), screen_rect)) {
    return GetMONITORINFOFromDisplayId(display->id());
  }

  if (const Display* display = FindDisplayNearestPoint(
          GetAllDisplays(), screen_rect.CenterPoint())) {
    return GetMONITORINFOFromDisplayId(display->id());
  }

  return std::nullopt;
}

std::optional<MONITORINFOEX> ScreenWinHeadless::MonitorInfoFromWindow(
    HWND hwnd,
    DWORD default_options) const {
  CHECK(hwnd);
  const gfx::Rect bounds = GetHeadlessWindowBounds(hwnd);

  // ScreenWin::MonitorInfoFromWindow() calls Win32 ::MonitorFromWindow() so
  // replicate its behavior according to
  // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-monitorfromwindow
  if (const Display* display =
          FindDisplayWithBiggestIntersection(GetAllDisplays(), bounds)) {
    return GetMONITORINFOFromDisplayId(display->id());
  }

  if (default_options == MONITOR_DEFAULTTONEAREST) {
    if (const Display* display =
            FindDisplayNearestPoint(GetAllDisplays(), bounds.CenterPoint())) {
      return GetMONITORINFOFromDisplayId(display->id());
    }
  } else if (default_options == MONITOR_DEFAULTTOPRIMARY) {
    return GetMONITORINFOFromDisplayId(GetPrimaryDisplay().id());
  }

  return std::nullopt;
}

HWND ScreenWinHeadless::GetRootWindow(HWND hwnd) const {
  // Headless windows don't have hierarchy, so return self.
  return hwnd;
}

int64_t ScreenWinHeadless::GetDisplayIdFromMonitorInfo(
    const MONITORINFOEX& monitor_info) const {
  // This will crash if called with the real Windows monitor info.
  return GetHeadlessDisplayIdFromMonitorInfo(monitor_info);
}

void ScreenWinHeadless::UpdateAllDisplaysAndNotify() {
  // Ignore all display update requests because the underlying implementation
  // requests display infos from the system and overrides headless screen
  // configuration. Headless screen configuration is defined in ctor and never
  // changes.
}

void ScreenWinHeadless::UpdateAllDisplaysIfPrimaryMonitorChanged() {
  // Headless primary monitor never changes, so intercept and ignore.
}

void ScreenWinHeadless::OnColorProfilesChanged() {
  // Just ignore as we don't expect any on the fly color profile changes in
  // headless mode.
}

gfx::NativeWindow ScreenWinHeadless::GetNativeWindowAtScreenPoint(
    const gfx::Point& point,
    const std::set<gfx::NativeWindow>& ignore) const {
  NOTREACHED();
}

gfx::Rect ScreenWinHeadless::GetNativeWindowBoundsInScreen(
    gfx::NativeWindow window) const {
  NOTREACHED();
}

gfx::Rect ScreenWinHeadless::GetHeadlessWindowBounds(
    gfx::AcceleratedWidget window) const {
  NOTREACHED();
}

gfx::NativeWindow ScreenWinHeadless::GetRootWindow(
    gfx::NativeWindow window) const {
  NOTREACHED();
}

ScreenWinDisplay ScreenWinHeadless::GetScreenWinDisplayNearestHWND(
    HWND hwnd) const {
  return GetScreenWinDisplay(
      MonitorInfoFromWindow(hwnd, MONITOR_DEFAULTTONEAREST));
}

ScreenWinDisplay ScreenWinHeadless::GetScreenWinDisplayNearestScreenRect(
    const gfx::Rect& screen_rect) const {
  return GetScreenWinDisplay(MonitorInfoFromScreenRect(screen_rect));
}

ScreenWinDisplay ScreenWinHeadless::GetScreenWinDisplayNearestScreenPoint(
    const gfx::Point& screen_point) const {
  return GetScreenWinDisplay(MonitorInfoFromScreenPoint(screen_point));
}

ScreenWinDisplay ScreenWinHeadless::GetPrimaryScreenWinDisplay() const {
  // ScreenWin::GetPrimaryScreenWinDisplay() searches the ScreenWinDisplay
  // table for a display with origin at (0,0), however, for headless primary
  // display is always the first registered display.
  const int64_t id = GetPrimaryDisplay().id();
  return GetScreenWinDisplayWithDisplayId(id);
}

ScreenWinDisplay ScreenWinHeadless::GetScreenWinDisplay(
    std::optional<MONITORINFOEX> monitor_info) const {
  // ScreenWin::GetScreenWinDisplay() calls
  // DisplayInfo::DisplayIdFromMonitorInfo() to derive display id from monitor
  // info. Headless display ids are synthesized, so retrieve display id from
  // the headless display device name.
  if (monitor_info) {
    const int64_t id = GetHeadlessDisplayIdFromMonitorInfo(*monitor_info);
    return GetScreenWinDisplayWithDisplayId(id);
  }

  return GetPrimaryScreenWinDisplay();
}

ScreenWinDisplay ScreenWinHeadless::GetScreenWinDisplayForHMONITOR(
    HMONITOR monitor) const {
  // Headless displays don't have a real HMONITOR, so all paths that call this
  // method should be overridden.
  NOTREACHED();
}

int64_t ScreenWinHeadless::AddDisplay(const Display& display) {
  int64_t display_id = HeadlessScreenManager::GetNewDisplayId();

  MONITORINFOEX monitor_info = {};
  monitor_info.cbSize = sizeof(monitor_info);

  // Display's bounds and work area have scale factor already applied, so we
  // have to unscale them to get the correct monitor info geometry.
  if (display.device_scale_factor() == 1.0f) {
    monitor_info.rcMonitor = display.bounds().ToRECT();
    monitor_info.rcWork = display.work_area().ToRECT();
  } else {
    const float scale_factor = display.device_scale_factor();
    monitor_info.rcMonitor =
        gfx::ScaleToEnclosingRect(display.bounds(), scale_factor).ToRECT();
    monitor_info.rcWork =
        gfx::ScaleToEnclosingRect(display.work_area(), scale_factor).ToRECT();
  }

  SetHeadlessDisplayDeviceName(monitor_info, display_id);

  headless_monitor_info_.insert({display_id, monitor_info});

  internal::DisplayInfo display_info(
      display_id, monitor_info, display.device_scale_factor(),
      /*sdr_white_level=*/200.0,
      /*rotation=*/display.rotation(),
      /*display_frequency=*/60.0,
      /*pixels_per_inch=*/
      GetDisplayPhysicalPixelsPerInch(display.device_scale_factor()),
      /*output_technology=*/display.IsInternal()
          ? DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL
          : DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER,
      display.label());

  // Get the existing display infos and append the new one.
  std::vector<internal::DisplayInfo> display_infos = GetExistingDisplayInfos();
  display_infos.push_back(display_info);

  UpdateFromDisplayInfos(display_infos);

  return display_id;
}

void ScreenWinHeadless::RemoveDisplay(int64_t display_id) {
  CHECK_NE(display_id, GetPrimaryDisplay().id());

  if (!headless_monitor_info_.erase(display_id)) {
    return;
  }

  // Get the existing display infos except for the one being removed.
  std::vector<internal::DisplayInfo> display_infos =
      GetExistingDisplayInfos(/*except_display_id=*/display_id);

  UpdateFromDisplayInfos(display_infos);
}

std::vector<internal::DisplayInfo>
ScreenWinHeadless::DisplayInfosFromScreenInfo(
    const std::vector<headless::HeadlessScreenInfo>& screen_infos) {
  CHECK(!screen_infos.empty());

  std::optional<float> forced_device_scale_factor;
  if (Display::HasForceDeviceScaleFactor()) {
    forced_device_scale_factor = Display::GetForcedDeviceScaleFactor();
  }

  bool is_primary = true;
  std::vector<internal::DisplayInfo> display_infos;
  for (const auto& screen_info : screen_infos) {
    int64_t display_id = HeadlessScreenManager::GetNewDisplayId();

    MONITORINFOEX monitor_info;
    monitor_info.cbSize = sizeof(monitor_info);
    monitor_info.rcMonitor = screen_info.bounds.ToRECT();

    if (screen_info.work_area_insets.IsEmpty()) {
      monitor_info.rcWork = monitor_info.rcMonitor;
    } else {
      gfx::Rect work_area = screen_info.bounds;
      work_area.Inset(screen_info.work_area_insets);
      monitor_info.rcWork = work_area.ToRECT();
    }

    monitor_info.dwFlags = is_primary ? MONITORINFOF_PRIMARY : 0;

    SetHeadlessDisplayDeviceName(monitor_info, display_id);

    const float device_scale_factor =
        forced_device_scale_factor.value_or(screen_info.device_pixel_ratio);

    // Maintain display id to monitor info association for all the
    // MonitorInfoFromScreen*() functions below.
    headless_monitor_info_.insert({display_id, monitor_info});

    internal::DisplayInfo display_info(
        display_id, monitor_info, device_scale_factor,
        /*sdr_white_level=*/200.0,
        /*rotation=*/Display::DegreesToRotation(screen_info.rotation),
        /*display_frequency=*/60.0,
        /*pixels_per_inch=*/
        GetDisplayPhysicalPixelsPerInch(screen_info.device_pixel_ratio),
        /*output_technology=*/screen_info.is_internal
            ? DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL
            : DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER,
        screen_info.label);

    display_infos.push_back(std::move(display_info));

    is_primary = false;
  }

  return display_infos;
}

std::vector<internal::DisplayInfo> ScreenWinHeadless::GetExistingDisplayInfos(
    int64_t except_display_id) {
  std::vector<internal::DisplayInfo> display_infos;

  for (const Display& display : GetAllDisplays()) {
    if (display.id() == except_display_id) {
      continue;
    }

    auto monitor_info = GetMONITORINFOFromDisplayId(display.id());
    CHECK(monitor_info);

    internal::DisplayInfo display_info(
        display.id(), *monitor_info, display.device_scale_factor(),
        /*sdr_white_level=*/200.0,
        /*rotation=*/display.rotation(),
        /*display_frequency=*/60.0,
        /*pixels_per_inch=*/
        GetDisplayPhysicalPixelsPerInch(display.device_scale_factor()),
        /*output_technology=*/display.IsInternal()
            ? DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL
            : DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER,
        display.label());

    display_infos.push_back(std::move(display_info));
  }

  return display_infos;
}

Display ScreenWinHeadless::GetDisplayFromScreenPoint(
    const gfx::Point& point) const {
  if (const Display* display =
          FindDisplayNearestPoint(GetAllDisplays(), point)) {
    return *display;
  }

  return GetPrimaryDisplay();
}

Display ScreenWinHeadless::GetDisplayFromScreenRect(
    const gfx::Rect& rect) const {
  if (const Display* display =
          FindDisplayWithBiggestIntersection(GetAllDisplays(), rect)) {
    return *display;
  }

  if (const Display* display =
          FindDisplayNearestPoint(GetAllDisplays(), rect.CenterPoint())) {
    return *display;
  }

  return GetPrimaryDisplay();
}

std::optional<MONITORINFOEX>
ScreenWinHeadless::GetMONITORINFOFromDisplayIdForTest(int64_t id) const {
  return GetMONITORINFOFromDisplayId(id);
}

std::optional<MONITORINFOEX> ScreenWinHeadless::GetMONITORINFOFromDisplayId(
    int64_t id) const {
  auto it = headless_monitor_info_.find(id);
  if (it == headless_monitor_info_.cend()) {
    return std::nullopt;
  }

  return it->second;
}

DISPLAY_EXPORT ScreenWinHeadless* GetScreenWinHeadless() {
  ScreenWin* screen_win = GetScreenWin();
  CHECK(screen_win->IsHeadless());
  return static_cast<ScreenWinHeadless*>(screen_win);
}

namespace internal {
bool VerifyHeadlessDisplayDeviceName(int64_t id,
                                     const MONITORINFOEX& monitor_info) {
  return GetHeadlessDisplayDeviceNameFromDisplayId(id) == monitor_info.szDevice;
}
}  // namespace internal

}  // namespace display::win