diff --git a/.github/workflows/test-depot.yml b/.github/workflows/test-depot.yml index b1a04d53..ca636bf6 100644 --- a/.github/workflows/test-depot.yml +++ b/.github/workflows/test-depot.yml @@ -82,17 +82,20 @@ jobs: rm GhosttyKit.xcframework.tar.gz test -d GhosttyKit.xcframework - - name: Display diagnostics + - name: Create virtual display run: | - echo "=== Screen resolution ===" - system_profiler SPDisplaysDataType 2>/dev/null || echo "No display info available" + set -euo pipefail + echo "=== Display before ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" echo "" - echo "=== Window server ===" - /usr/sbin/system_profiler SPSoftwareDataType | grep -i "boot mode" || true - echo "" - echo "=== Accessibility permissions ===" - tccutil reset Accessibility 2>/dev/null || true - echo "Accessibility reset attempted" + clang -framework Foundation -framework CoreGraphics \ + -o /tmp/create-virtual-display scripts/create-virtual-display.m + /tmp/create-virtual-display & + VDISPLAY_PID=$! + echo "VDISPLAY_PID=$VDISPLAY_PID" >> "$GITHUB_ENV" + sleep 3 + echo "=== Display after ===" + system_profiler SPDisplaysDataType 2>/dev/null || echo "(none)" - name: Clean DerivedData run: | diff --git a/scripts/create-virtual-display.m b/scripts/create-virtual-display.m new file mode 100644 index 00000000..d3df1bae --- /dev/null +++ b/scripts/create-virtual-display.m @@ -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 +#import + +// 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; +}