Add virtual display for headless Depot runners (#721)

Depot macOS runners have no physical display, causing XCUITests to fail
with "Failed to activate application (current state: Running Background)".
This adds a small ObjC tool that creates a virtual display using the
private CGVirtualDisplay API before tests run.
This commit is contained in:
Lawrence Chen 2026-03-01 04:25:36 -08:00 committed by GitHub
parent 1b160dc5ca
commit 2b66a2f5c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 105 additions and 9 deletions

View file

@ -0,0 +1,93 @@
// Creates a virtual display on headless macOS (CI runners without a physical monitor).
// Uses the private CGVirtualDisplay API from CoreGraphics.
// The display stays alive as long as this process runs.
//
// Build: clang -framework Foundation -framework CoreGraphics -o create-virtual-display create-virtual-display.m
// Usage: ./create-virtual-display &
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
// Private CoreGraphics classes (declared here since they're not in public headers)
@interface CGVirtualDisplayMode : NSObject
- (instancetype)initWithWidth:(unsigned int)width height:(unsigned int)height refreshRate:(double)refreshRate;
@end
@interface CGVirtualDisplayDescriptor : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic) unsigned int maxPixelsWide;
@property (nonatomic) unsigned int maxPixelsHigh;
@property (nonatomic) CGSize sizeInMillimeters;
@property (nonatomic) unsigned int vendorID;
@property (nonatomic) unsigned int productID;
@property (nonatomic) unsigned int serialNum;
@property (nonatomic, strong) dispatch_queue_t queue;
@end
@interface CGVirtualDisplaySettings : NSObject
@property (nonatomic) unsigned int hiDPI;
@property (nonatomic, strong) NSArray *modes;
@end
@interface CGVirtualDisplay : NSObject
- (instancetype)initWithDescriptor:(CGVirtualDisplayDescriptor *)descriptor;
- (BOOL)applySettings:(CGVirtualDisplaySettings *)settings;
@property (nonatomic, readonly) unsigned int displayID;
@end
int main(int argc, const char *argv[]) {
@autoreleasepool {
unsigned int width = 1920;
unsigned int height = 1080;
// Verify the private classes exist
if (!NSClassFromString(@"CGVirtualDisplay")) {
fprintf(stderr, "ERROR: CGVirtualDisplay API not available on this system\n");
return 1;
}
// Create display mode
CGVirtualDisplayMode *mode = [[CGVirtualDisplayMode alloc] initWithWidth:width height:height refreshRate:60.0];
if (!mode) {
fprintf(stderr, "ERROR: Failed to create CGVirtualDisplayMode\n");
return 1;
}
// Configure descriptor
CGVirtualDisplayDescriptor *descriptor = [[CGVirtualDisplayDescriptor alloc] init];
descriptor.name = @"CI Virtual Display";
descriptor.maxPixelsWide = width;
descriptor.maxPixelsHigh = height;
descriptor.sizeInMillimeters = CGSizeMake(530, 300);
descriptor.vendorID = 0x1234;
descriptor.productID = 0x5678;
descriptor.serialNum = 0x0001;
descriptor.queue = dispatch_get_main_queue();
// Create virtual display
CGVirtualDisplay *display = [[CGVirtualDisplay alloc] initWithDescriptor:descriptor];
if (!display) {
fprintf(stderr, "ERROR: Failed to create CGVirtualDisplay\n");
return 1;
}
// Apply settings with display mode
CGVirtualDisplaySettings *settings = [[CGVirtualDisplaySettings alloc] init];
settings.hiDPI = 0;
settings.modes = @[mode];
BOOL ok = [display applySettings:settings];
if (!ok) {
fprintf(stderr, "ERROR: Failed to apply display settings\n");
return 1;
}
printf("Virtual display created: %ux%u@60Hz (displayID: %u)\n", width, height, display.displayID);
printf("PID: %d\n", getpid());
fflush(stdout);
// Keep alive so the display persists
dispatch_main();
}
return 0;
}