// Copyright 2022 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/mac/test/virtual_display_mac_util.h"

#include <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>

#include <map>

#include "base/check.h"
#include "base/check_op.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsobject.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/size.h"

// These interfaces were generated from CoreGraphics binaries.
API_AVAILABLE(macos(10.14))
@interface CGVirtualDisplay : NSObject {
  unsigned int _vendorID;
  unsigned int _productID;
  unsigned int _serialNum;
  NSString* _name;
  struct CGSize _sizeInMillimeters;
  unsigned int _maxPixelsWide;
  unsigned int _maxPixelsHigh;
  struct CGPoint _redPrimary;
  struct CGPoint _greenPrimary;
  struct CGPoint _bluePrimary;
  struct CGPoint _whitePoint;
  id _queue;
  id _terminationHandler;
  unsigned int _displayID;
  unsigned int _hiDPI;
  NSArray* _modes;
}

@property(readonly, nonatomic)
    unsigned int vendorID;  // @synthesize vendorID=_vendorID;
@property(readonly, nonatomic)
    unsigned int productID;  // @synthesize productID=_productID;
@property(readonly, nonatomic)
    unsigned int serialNum;  // @synthesize serialNum=_serialNum;
@property(readonly, nonatomic) NSString* name;  // @synthesize name=_name;
@property(readonly, nonatomic) struct CGSize
    sizeInMillimeters;  // @synthesize sizeInMillimeters=_sizeInMillimeters;
@property(readonly, nonatomic)
    unsigned int maxPixelsWide;  // @synthesize maxPixelsWide=_maxPixelsWide;
@property(readonly, nonatomic)
    unsigned int maxPixelsHigh;  // @synthesize maxPixelsHigh=_maxPixelsHigh;
@property(readonly, nonatomic)
    struct CGPoint redPrimary;  // @synthesize redPrimary=_redPrimary;
@property(readonly, nonatomic)
    struct CGPoint greenPrimary;  // @synthesize greenPrimary=_greenPrimary;
@property(readonly, nonatomic)
    struct CGPoint bluePrimary;  // @synthesize bluePrimary=_bluePrimary;
@property(readonly, nonatomic)
    struct CGPoint whitePoint;            // @synthesize whitePoint=_whitePoint;
@property(readonly, nonatomic) id queue;  // @synthesize queue=_queue;
@property(readonly, nonatomic) id
    terminationHandler;  // @synthesize terminationHandler=_terminationHandler;
@property(readonly, nonatomic)
    unsigned int displayID;  // @synthesize displayID=_displayID;
@property(readonly, nonatomic) unsigned int hiDPI;  // @synthesize hiDPI=_hiDPI;
@property(readonly, nonatomic) NSArray* modes;      // @synthesize modes=_modes;
- (BOOL)applySettings:(id)arg1;
- (void)dealloc;
- (id)initWithDescriptor:(id)arg1;

@end

// These interfaces were generated from CoreGraphics binaries.
API_AVAILABLE(macos(10.14))
@interface CGVirtualDisplayDescriptor : NSObject {
  unsigned int _vendorID;
  unsigned int _productID;
  unsigned int _serialNum;
  NSString* _name;
  struct CGSize _sizeInMillimeters;
  unsigned int _maxPixelsWide;
  unsigned int _maxPixelsHigh;
  struct CGPoint _redPrimary;
  struct CGPoint _greenPrimary;
  struct CGPoint _bluePrimary;
  struct CGPoint _whitePoint;
  id _queue;
  id _terminationHandler;
}

@property(nonatomic) unsigned int vendorID;  // @synthesize vendorID=_vendorID;
@property(nonatomic)
    unsigned int productID;  // @synthesize productID=_productID;
@property(nonatomic)
    unsigned int serialNum;  // @synthesize serialNum=_serialNum;
@property(strong, nonatomic) NSString* name;  // @synthesize name=_name;
@property(nonatomic) struct CGSize
    sizeInMillimeters;  // @synthesize sizeInMillimeters=_sizeInMillimeters;
@property(nonatomic)
    unsigned int maxPixelsWide;  // @synthesize maxPixelsWide=_maxPixelsWide;
@property(nonatomic)
    unsigned int maxPixelsHigh;  // @synthesize maxPixelsHigh=_maxPixelsHigh;
@property(nonatomic)
    struct CGPoint redPrimary;  // @synthesize redPrimary=_redPrimary;
@property(nonatomic)
    struct CGPoint greenPrimary;  // @synthesize greenPrimary=_greenPrimary;
@property(nonatomic)
    struct CGPoint bluePrimary;  // @synthesize bluePrimary=_bluePrimary;
@property(nonatomic)
    struct CGPoint whitePoint;          // @synthesize whitePoint=_whitePoint;
@property(retain, nonatomic) id queue;  // @synthesize queue=_queue;
@property(copy, nonatomic) id
    terminationHandler;  // @synthesize terminationHandler=_terminationHandler;
- (void)dealloc;
- (id)init;
- (id)dispatchQueue;
- (void)setDispatchQueue:(id)arg1;

@end

// These interfaces were generated from CoreGraphics binaries.
API_AVAILABLE(macos(10.14))
@interface CGVirtualDisplayMode : NSObject {
  unsigned int _width;
  unsigned int _height;
  double _refreshRate;
}

@property(readonly, nonatomic) unsigned int width;  // @synthesize width=_width;
@property(readonly, nonatomic)
    unsigned int height;  // @synthesize height=_height;
@property(readonly, nonatomic)
    double refreshRate;  // @synthesize refreshRate=_refreshRate;
- (id)initWithWidth:(unsigned int)arg1
             height:(unsigned int)arg2
        refreshRate:(double)arg3;

@end

// These interfaces were generated from CoreGraphics binaries.
API_AVAILABLE(macos(10.14))
@interface CGVirtualDisplaySettings : NSObject {
  NSArray* _modes;
  unsigned int _hiDPI;
}

@property(strong, nonatomic) NSArray* modes;  // @synthesize modes=_modes;
@property(nonatomic) unsigned int hiDPI;      // @synthesize hiDPI=_hiDPI;
- (void)dealloc;
- (id)init;

@end

namespace {

static bool g_need_display_removal_workaround = true;

// A global singleton that tracks the current set of mocked displays.
std::map<int64_t, base::scoped_nsobject<CGVirtualDisplay>> g_display_map
    API_AVAILABLE(macos(10.14));

// A helper function for creating virtual display and return CGVirtualDisplay
// object.
base::scoped_nsobject<CGVirtualDisplay> CreateVirtualDisplay(int width,
                                                             int height,
                                                             int ppi,
                                                             BOOL hiDPI,
                                                             NSString* name)
    API_AVAILABLE(macos(10.14)) {
  base::scoped_nsobject<CGVirtualDisplaySettings> settings(
      [[CGVirtualDisplaySettings alloc] init]);
  [settings setHiDPI:hiDPI];

  base::scoped_nsobject<CGVirtualDisplayDescriptor> descriptor(
      [[CGVirtualDisplayDescriptor alloc] init]);
  [descriptor
      setQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)];
  [descriptor setName:name];

  // See System Preferences > Displays > Color > Open Profile > Apple display
  // native information
  [descriptor setWhitePoint:CGPointMake(0.3125, 0.3291)];
  [descriptor setBluePrimary:CGPointMake(0.1494, 0.0557)];
  [descriptor setGreenPrimary:CGPointMake(0.2559, 0.6983)];
  [descriptor setRedPrimary:CGPointMake(0.6797, 0.3203)];
  [descriptor setMaxPixelsHigh:height];
  [descriptor setMaxPixelsWide:width];
  [descriptor
      setSizeInMillimeters:CGSizeMake(25.4 * width / ppi, 25.4 * height / ppi)];
  [descriptor setSerialNum:0];
  [descriptor setProductID:0];
  [descriptor setVendorID:0];

  base::scoped_nsobject<CGVirtualDisplay> display(
      [[CGVirtualDisplay alloc] initWithDescriptor:descriptor]);

  if ([settings hiDPI]) {
    width /= 2;
    height /= 2;
  }
  base::scoped_nsobject<CGVirtualDisplayMode> mode([[CGVirtualDisplayMode alloc]
      initWithWidth:width
             height:height
        refreshRate:60]);
  [settings setModes:@[ mode ]];

  if (![display applySettings:settings])
    return base::scoped_nsobject<CGVirtualDisplay>();

  return display;
}

// This method detects whether the local machine is running headless.
// Typically returns true when the session is curtained or if there are no
// physical monitors attached.  In those two scenarios, the online display will
// be marked as virtual.
bool IsRunningHeadless() {
  // Most machines will have < 4 displays but a larger upper bound won't hurt.
  constexpr UInt32 kMaxDisplaysToQuery = 32;
  // 0x76697274 is a 4CC value for 'virt' which indicates the display is
  // virtual.
  constexpr CGDirectDisplayID kVirtualDisplayID = 0x76697274;

  CGDirectDisplayID online_displays[kMaxDisplaysToQuery];
  UInt32 online_display_count = 0;
  CGError return_code = CGGetOnlineDisplayList(
      kMaxDisplaysToQuery, online_displays, &online_display_count);
  if (return_code != kCGErrorSuccess) {
    LOG(ERROR) << __func__
               << " - CGGetOnlineDisplayList() failed: " << return_code << ".";
    // If this fails, assume machine is headless to err on the side of caution.
    return true;
  }

  bool is_running_headless = true;
  for (UInt32 i = 0; i < online_display_count; i++) {
    if (CGDisplayModelNumber(online_displays[i]) != kVirtualDisplayID) {
      // At least one monitor is attached so the machine is not headless.
      is_running_headless = false;
      break;
    }
  }

  // TODO(crbug.com/1126278): Please remove this log or replace it with
  // [D]CHECK() ASAP when the TEST is stable.
  LOG(INFO) << __func__ << " - Is running headless: " << is_running_headless
            << ". Online display count: " << online_display_count << ".";

  return is_running_headless;
}

// Observer for display metrics change notifications.
class DisplayMetricsChangeObserver : public display::DisplayObserver {
 public:
  DisplayMetricsChangeObserver(int64_t display_id,
                               const gfx::Size& size,
                               uint32_t expected_changed_metrics)
      : display_id_(display_id),
        size_(size),
        expected_changed_metrics_(expected_changed_metrics) {
    display::Screen::GetScreen()->AddObserver(this);
  }
  ~DisplayMetricsChangeObserver() override {
    display::Screen::GetScreen()->RemoveObserver(this);
  }

  DisplayMetricsChangeObserver(const DisplayMetricsChangeObserver&) = delete;
  DisplayMetricsChangeObserver& operator=(const DisplayMetricsChangeObserver&) =
      delete;

  // Runs a loop until the display metrics change is seen (unless one has
  // already been observed, in which case it returns immediately).
  void Wait() {
    if (observed_change_)
      return;

    run_loop_.Run();
  }

 private:
  // display::DisplayObserver:
  void OnDisplayMetricsChanged(const display::Display& display,
                               uint32_t changed_metrics) override {
    if (!(display.id() == display_id_ && display.size() == size_ &&
          (changed_metrics & expected_changed_metrics_))) {
      return;
    }

    observed_change_ = true;
    if (run_loop_.running())
      run_loop_.Quit();
  }
  void OnDisplayAdded(const display::Display& new_display) override {}
  void OnDisplayRemoved(const display::Display& old_display) override {}

  const int64_t display_id_;
  const gfx::Size size_;
  const uint32_t expected_changed_metrics_;

  bool observed_change_ = false;
  base::RunLoop run_loop_;
};

void EnsureDisplayWithResolution(CGDirectDisplayID display_id,
                                 const gfx::Size& size) {
  base::ScopedCFTypeRef<CGDisplayModeRef> current_display_mode(
      CGDisplayCopyDisplayMode(display_id));
  if (gfx::Size(CGDisplayModeGetWidth(current_display_mode),
                CGDisplayModeGetHeight(current_display_mode)) == size) {
    return;
  }

  base::ScopedCFTypeRef<CFArrayRef> display_modes(
      CGDisplayCopyAllDisplayModes(display_id, nullptr));
  DCHECK(display_modes);

  CGDisplayModeRef prefered_display_mode = nullptr;
  for (CFIndex i = 0; i < CFArrayGetCount(display_modes); ++i) {
    CGDisplayModeRef display_mode =
        (CGDisplayModeRef)CFArrayGetValueAtIndex(display_modes, i);
    if (gfx::Size(CGDisplayModeGetWidth(display_mode),
                  CGDisplayModeGetHeight(display_mode)) == size) {
      prefered_display_mode = display_mode;
      break;
    }
  }
  DCHECK(prefered_display_mode);

  uint32_t expected_changed_metrics =
      display::DisplayObserver::DISPLAY_METRIC_BOUNDS |
      display::DisplayObserver::DISPLAY_METRIC_WORK_AREA |
      display::DisplayObserver::DISPLAY_METRIC_DEVICE_SCALE_FACTOR;
  DisplayMetricsChangeObserver display_metrics_change_observer(
      display_id, size, expected_changed_metrics);

  // This operation is always synchronous. The function doesn’t return until the
  // mode switch is complete.
  CGError result =
      CGDisplaySetDisplayMode(display_id, prefered_display_mode, nullptr);
  DCHECK_EQ(result, kCGErrorSuccess);

  // Wait for `display::Screen` and `display::Display` structures to be updated.
  display_metrics_change_observer.Wait();
}

}  // namespace

namespace display::test {

struct DisplayParams {
  DisplayParams(int width,
                int height,
                int ppi,
                bool hiDPI,
                std::string description)
      : width(width),
        height(height),
        ppi(ppi),
        hiDPI(hiDPI),
        description([NSString
            stringWithCString:description.c_str()
                     encoding:[NSString defaultCStringEncoding]]) {}

  bool IsValid() const {
    return width > 0 && height > 0 && ppi > 0 && [description length] > 0;
  }

  int width;
  int height;
  int ppi;
  BOOL hiDPI;
  base::scoped_nsobject<NSString> description;
};

VirtualDisplayMacUtil::VirtualDisplayMacUtil() {
  display::Screen::GetScreen()->AddObserver(this);
}

VirtualDisplayMacUtil::~VirtualDisplayMacUtil() {
  RemoveAllDisplays();
  display::Screen::GetScreen()->RemoveObserver(this);
}

int64_t VirtualDisplayMacUtil::AddDisplay(int64_t display_id,
                                          const DisplayParams& display_params) {
  if (@available(macos 10.14, *)) {
    DCHECK(display_params.IsValid());

    NSString* display_name =
        [NSString stringWithFormat:@"Virtual Display #%lld", display_id];
    base::scoped_nsobject<CGVirtualDisplay> display = CreateVirtualDisplay(
        display_params.width, display_params.height, display_params.ppi,
        display_params.hiDPI, display_name);
    DCHECK(display.get());

    // TODO(crbug.com/1126278): Please remove this log or replace it with
    // [D]CHECK() ASAP when the TEST is stable.
    LOG(INFO) << "VirtualDisplayMacUtil::" << __func__
              << " - display id: " << display_id
              << ". CreateVirtualDisplay success.";

    int64_t id = [display displayID];
    DCHECK_NE(id, 0u);

    WaitForDisplay(id, /*added=*/true);

    EnsureDisplayWithResolution(
        id, gfx::Size(display_params.width, display_params.height));

    // TODO(crbug.com/1126278): Please remove this log or replace it with
    // [D]CHECK() ASAP when the TEST is stable.
    LOG(INFO) << "VirtualDisplayMacUtil::" << __func__
              << " - display id: " << display_id << "(" << id
              << "). WaitForDisplay success.";

    DCHECK(!g_display_map[id]);
    g_display_map[id] = display;

    return id;
  }
  return display::kInvalidDisplayId;
}

void VirtualDisplayMacUtil::RemoveDisplay(int64_t display_id) {
  if (@available(macos 10.14, *)) {
    auto it = g_display_map.find(display_id);
    DCHECK(it != g_display_map.end());

    // The first display removal has known flaky timeouts if removed
    // individually. Remove another display simultaneously during the first
    // display removal.
    // TODO(crbug.com/1126278): Resolve this defect in a more hermetic manner.
    if (g_need_display_removal_workaround) {
      const int64_t tmp_display_id = AddDisplay(0, k1920x1080);
      auto tmp_it = g_display_map.find(tmp_display_id);
      DCHECK(tmp_it != g_display_map.end());

      waiting_for_ids_.insert(display_id);
      waiting_for_ids_.insert(tmp_display_id);

      g_display_map.erase(it);
      g_display_map.erase(tmp_it);

      g_need_display_removal_workaround = false;

      StartWaiting();

      return;
    }

    g_display_map.erase(it);

    // TODO(crbug.com/1126278): Please remove this log or replace it with
    // [D]CHECK() ASAP when the TEST is stable.
    LOG(INFO) << "VirtualDisplayMacUtil::" << __func__
              << " - display id: " << display_id << ". Erase success.";

    WaitForDisplay(display_id, /*added=*/false);

    // TODO(crbug.com/1126278): Please remove this log or replace it with
    // [D]CHECK() ASAP when the TEST is stable.
    LOG(INFO) << "VirtualDisplayMacUtil::" << __func__
              << " - display id: " << display_id << ". WaitForDisplay success.";
  }
}

// static
bool VirtualDisplayMacUtil::IsAPIAvailable() {
  bool is_api_available = false;
  // The underlying API is only available on macos 10.14 or higher.
  // TODO(crbug.com/1126278): enable support on 10.15 and 10.14.
  if (@available(macos 11.0, *)) {
    is_api_available = true;
  }
  if (!is_api_available) {
    return false;
  }

  // Only support non-headless bots now. Some odd behavior happened when
  // enable on headless bots. See
  // https://ci.chromium.org/ui/p/chromium/builders/try/mac-rel/1058024/test-results
  // for details.
  // TODO(crbug.com/1126278): Remove this restriction when support headless
  // environment.
  if (IsRunningHeadless()) {
    return false;
  }

  return true;
}

// Predefined display configurations from
// https://en.wikipedia.org/wiki/Graphics_display_resolution and
// https://www.theverge.com/tldr/2016/3/21/11278192/apple-iphone-ipad-screen-sizes-pixels-density-so-many-choices.
const DisplayParams VirtualDisplayMacUtil::k6016x3384 =
    DisplayParams(6016, 3384, 218, true, "Apple Pro Display XDR");
const DisplayParams VirtualDisplayMacUtil::k5120x2880 =
    DisplayParams(5120, 2880, 218, true, "27-inch iMac with Retina 5K display");
const DisplayParams VirtualDisplayMacUtil::k4096x2304 =
    DisplayParams(4096,
                  2304,
                  219,
                  true,
                  "21.5-inch iMac with Retina 4K display");
const DisplayParams VirtualDisplayMacUtil::k3840x2400 =
    DisplayParams(3840, 2400, 200, true, "WQUXGA");
const DisplayParams VirtualDisplayMacUtil::k3840x2160 =
    DisplayParams(3840, 2160, 200, true, "UHD");
const DisplayParams VirtualDisplayMacUtil::k3840x1600 =
    DisplayParams(3840, 1600, 200, true, "WQHD+, UW-QHD+");
const DisplayParams VirtualDisplayMacUtil::k3840x1080 =
    DisplayParams(3840, 1080, 200, true, "DFHD");
const DisplayParams VirtualDisplayMacUtil::k3072x1920 =
    DisplayParams(3072,
                  1920,
                  226,
                  true,
                  "16-inch MacBook Pro with Retina display");
const DisplayParams VirtualDisplayMacUtil::k2880x1800 =
    DisplayParams(2880,
                  1800,
                  220,
                  true,
                  "15.4-inch MacBook Pro with Retina display");
const DisplayParams VirtualDisplayMacUtil::k2560x1600 =
    DisplayParams(2560,
                  1600,
                  227,
                  true,
                  "WQXGA, 13.3-inch MacBook Pro with Retina display");
const DisplayParams VirtualDisplayMacUtil::k2560x1440 =
    DisplayParams(2560, 1440, 109, false, "27-inch Apple Thunderbolt display");
const DisplayParams VirtualDisplayMacUtil::k2304x1440 =
    DisplayParams(2304, 1440, 226, true, "12-inch MacBook with Retina display");
const DisplayParams VirtualDisplayMacUtil::k2048x1536 =
    DisplayParams(2048, 1536, 150, false, "QXGA");
const DisplayParams VirtualDisplayMacUtil::k2048x1152 =
    DisplayParams(2048, 1152, 150, false, "QWXGA");
const DisplayParams VirtualDisplayMacUtil::k1920x1200 =
    DisplayParams(1920, 1200, 150, false, "WUXGA");
const DisplayParams VirtualDisplayMacUtil::k1600x1200 =
    DisplayParams(1600, 1200, 125, false, "UXGA");
const DisplayParams VirtualDisplayMacUtil::k1920x1080 =
    DisplayParams(1920, 1080, 102, false, "HD, 21.5-inch iMac");
const DisplayParams VirtualDisplayMacUtil::k1680x1050 =
    DisplayParams(1680,
                  1050,
                  99,
                  false,
                  "WSXGA+, Apple Cinema Display (20-inch), 20-inch iMac");
const DisplayParams VirtualDisplayMacUtil::k1440x900 =
    DisplayParams(1440, 900, 127, false, "WXGA+, 13.3-inch MacBook Air");
const DisplayParams VirtualDisplayMacUtil::k1400x1050 =
    DisplayParams(1400, 1050, 125, false, "SXGA+");
const DisplayParams VirtualDisplayMacUtil::k1366x768 =
    DisplayParams(1366, 768, 135, false, "11.6-inch MacBook Air");
const DisplayParams VirtualDisplayMacUtil::k1280x1024 =
    DisplayParams(1280, 1024, 100, false, "SXGA");
const DisplayParams VirtualDisplayMacUtil::k1280x1800 =
    DisplayParams(1280, 800, 113, false, "13.3-inch MacBook Pro");

VirtualDisplayMacUtil::DisplaySleepBlocker::DisplaySleepBlocker() {
  IOReturn result = IOPMAssertionCreateWithName(
      kIOPMAssertionTypeNoDisplaySleep, kIOPMAssertionLevelOn,
      CFSTR("DisplaySleepBlocker"), &assertion_id_);
  DCHECK_EQ(result, kIOReturnSuccess);
}

VirtualDisplayMacUtil::DisplaySleepBlocker::~DisplaySleepBlocker() {
  IOReturn result = IOPMAssertionRelease(assertion_id_);
  DCHECK_EQ(result, kIOReturnSuccess);
}

void VirtualDisplayMacUtil::OnDisplayMetricsChanged(
    const display::Display& display,
    uint32_t changed_metrics) {
  LOG(INFO) << "VirtualDisplayMacUtil::" << __func__
            << " - display id: " << display.id()
            << ", changed_metrics: " << changed_metrics << ".";
}

void VirtualDisplayMacUtil::OnDisplayAdded(
    const display::Display& new_display) {
  // TODO(crbug.com/1126278): Please remove this log or replace it with
  // [D]CHECK() ASAP when the TEST is stable.
  LOG(INFO) << "VirtualDisplayMacUtil::" << __func__
            << " - display id: " << new_display.id() << ".";

  OnDisplayAddedOrRemoved(new_display.id());
}

void VirtualDisplayMacUtil::OnDisplayRemoved(
    const display::Display& old_display) {
  // TODO(crbug.com/1126278): Please remove this log or replace it with
  // [D]CHECK() ASAP when the TEST is stable.
  LOG(INFO) << "VirtualDisplayMacUtil::" << __func__
            << " - display id: " << old_display.id() << ".";

  OnDisplayAddedOrRemoved(old_display.id());
}

void VirtualDisplayMacUtil::OnDisplayAddedOrRemoved(int64_t id) {
  if (!waiting_for_ids_.count(id)) {
    // TODO(crbug.com/1126278): Please remove this log or replace it with
    // [D]CHECK() ASAP when the TEST is stable.
    LOG(INFO) << "VirtualDisplayMacUtil::" << __func__
              << " - unexpected display id: " << id << ".";

    return;
  }

  waiting_for_ids_.erase(id);
  if (!waiting_for_ids_.empty()) {
    return;
  }

  StopWaiting();
}

void VirtualDisplayMacUtil::RemoveAllDisplays() {
  if (@available(macos 10.14, *)) {
    int display_count = g_display_map.size();

    // TODO(crbug.com/1126278): Please remove this log or replace it with
    // [D]CHECK() ASAP when the TEST is stable.
    LOG(INFO) << "VirtualDisplayMacUtil::" << __func__
              << " - display count: " << display_count << ".";

    if (!display_count) {
      return;
    }

    if (display_count == 1 && g_need_display_removal_workaround) {
      RemoveDisplay(g_display_map.begin()->first);
      return;
    }

    for (const auto& [id, display] : g_display_map) {
      waiting_for_ids_.insert(id);
    }

    g_display_map.clear();

    StartWaiting();
  }
}

void VirtualDisplayMacUtil::WaitForDisplay(int64_t id, bool added) {
  display::Display d;
  if (display::Screen::GetScreen()->GetDisplayWithDisplayId(id, &d) == added) {
    return;
  }

  waiting_for_ids_.insert(id);

  // TODO(crbug.com/1126278): Please remove this log or replace it with
  // [D]CHECK() ASAP when the TEST is stable.
  LOG(INFO) << "VirtualDisplayMacUtil::" << __func__ << " - display id: " << id
            << "(added: " << added << "). Start waiting.";

  StartWaiting();
}

void VirtualDisplayMacUtil::StartWaiting() {
  DCHECK(!run_loop_);
  run_loop_ = std::make_unique<base::RunLoop>();
  run_loop_->Run();
  run_loop_.reset();
}

void VirtualDisplayMacUtil::StopWaiting() {
  DCHECK(run_loop_);
  run_loop_->Quit();
}

}  // namespace display::test