// macOS/AppKit F09 demo — output enumeration & hotplug (../../../features/f09-outputs.md).
// The NSScreen-vs-CGDisplay duality: the same physical display surfaces as two object
// models — AppKit's NSScreen (points, y-up global space, per-screen scale) and
// CoreGraphics' CGDirectDisplayID (pixels, top-left-origin global space, modes) —
// bridged by the `NSScreenNumber` key in -[NSScreen deviceDescription]. Built on the
// scaffold/f08 recipe; run findings are A[ssh] (locked console, single built-in
// display — physical hotplug is Tier C).
//
// What it logs:
// - CG-side enumeration BEFORE NSApplication exists (CGGetOnlineDisplayList /
// CGGetActiveDisplayList — enumeration is global, no window or app object needed):
// per display CGDisplayBounds, CGDisplayPixelsWide/High, vendor/model/serial,
// CGDisplayScreenSize (physical mm), builtin/main/active/online/asleep flags, and
// the current CGDisplayMode (points + pixels + CGDisplayModeGetRefreshRate);
// - NSScreen-side enumeration: per screen frame, visibleFrame (and the
// menubar/Dock delta between them), backingScaleFactor, localizedName,
// maximumFramesPerSecond, and deviceDescription (NSScreenNumber + NSDeviceSize +
// NSDeviceResolution);
// - the bridge: NSScreenNumber == CGDirectDisplayID, matched against the CG list;
// - which screen the window is on: -[NSWindow screen] — AppKit answers directly
// (Win32 derives via MonitorFromWindow, X11 makes you intersect geometry yourself);
// - change tracking: NSWindowDidChangeScreenNotification +
// NSApplicationDidChangeScreenParametersNotification observers and a
// CGDisplayRegisterReconfigurationCallback, with a hand-posted self-test proving
// the NSNotification wiring (a real hotplug/reconfig is Tier C);
// - the headless probe: a late re-enumeration diffs the active/online lists against
// startup — does the locked session itself ever change the answer?
//
// Modes: WSI_AUTO_EXIT=1 = bounded scripted run; without it the window stays up for a
// manual (Tier C) plug/unplug session. Headless-safe: prints `SKIP:` and exits 0 when
// there is no window server.
module (module) appapp;
import (package) corecore.(module) core.attributeThis module contains UDA's (User Defined Attributes) either used in
the runtime or special UDA's recognized by compiler.
Attribute Name, Linkage, Description
gnuAbiTag, C++,
Declares an ABI tag on a C++ symbol.
mustuse,,
Ensures that values of a struct or union type are not discarded.
optional, Objective-C,
Makes an Objective-C interface method optional.
selector, Objective-C,
Attaches an Objective-C selector to a method.
standalone,,
Marks a shared module constructor as not depending on any
other module constructor being run first.
weak,,
Specifies that a global symbol should be emitted with weak linkage.
Source
core/attribute.d
attribute : selector;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdioD header file for C99 <stdio.h>
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdio.h.html, stdio.h
Source
core/stdc/stdio.d
stdio : (alias) app.printf = int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stdlibD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/stdlib.h.html, stdlib.h
Source
core/stdc/stdlib.d
stdlib : (alias) app.getenv = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv;
import instrument : instr, instrInit;
import objc.autorelease : autoreleasepool_push, autoreleasepool_pop; // objective-d
import objc.rt : SEL;
// --- C-level declarations ---------------------------------------------------------
extern (C) struct (struct) app.NSPointNSPoint
{
double (field) double app.NSPoint.xx, (field) double app.NSPoint.yy;
}
extern (C) struct (struct) app.NSSizeNSSize
{
double (field) double app.NSSize.widthwidth, (field) double app.NSSize.heightheight;
}
extern (C) struct (struct) app.NSRectNSRect
{
(struct) app.NSPointNSPoint (field) app.NSPoint app.NSRect.originorigin;
(struct) app.NSSizeNSSize (field) app.NSSize app.NSRect.sizesize;
}
extern (C) nothrow @nogc
{
uint uint app.CGMainDisplayID() nothrow @nogcCGMainDisplayID();
int int app.CGGetOnlineDisplayList(uint maxDisplays, uint* displays, uint* count) nothrow @nogcCGGetOnlineDisplayList(uint (parameter) uint maxDisplaysmaxDisplays, uint* (parameter) uint* displaysdisplays, uint* (parameter) uint* countcount);
int int app.CGGetActiveDisplayList(uint maxDisplays, uint* displays, uint* count) nothrow @nogcCGGetActiveDisplayList(uint (parameter) uint maxDisplaysmaxDisplays, uint* (parameter) uint* displaysdisplays, uint* (parameter) uint* countcount);
(struct) app.NSRectNSRect app.NSRect app.CGDisplayBounds(uint display) nothrow @nogcCGDisplayBounds(uint (parameter) uint displaydisplay); // CGRect, top-left-origin global space
(alias) object.size_t = ulongsize_t ulong app.CGDisplayPixelsWide(uint display) nothrow @nogcCGDisplayPixelsWide(uint (parameter) uint displaydisplay);
(alias) object.size_t = ulongsize_t ulong app.CGDisplayPixelsHigh(uint display) nothrow @nogcCGDisplayPixelsHigh(uint (parameter) uint displaydisplay);
uint uint app.CGDisplayVendorNumber(uint display) nothrow @nogcCGDisplayVendorNumber(uint (parameter) uint displaydisplay);
uint uint app.CGDisplayModelNumber(uint display) nothrow @nogcCGDisplayModelNumber(uint (parameter) uint displaydisplay);
uint uint app.CGDisplaySerialNumber(uint display) nothrow @nogcCGDisplaySerialNumber(uint (parameter) uint displaydisplay);
uint uint app.CGDisplayIsBuiltin(uint display) nothrow @nogcCGDisplayIsBuiltin(uint (parameter) uint displaydisplay);
uint uint app.CGDisplayIsMain(uint display) nothrow @nogcCGDisplayIsMain(uint (parameter) uint displaydisplay);
uint uint app.CGDisplayIsActive(uint display) nothrow @nogcCGDisplayIsActive(uint (parameter) uint displaydisplay);
uint uint app.CGDisplayIsOnline(uint display) nothrow @nogcCGDisplayIsOnline(uint (parameter) uint displaydisplay);
uint uint app.CGDisplayIsAsleep(uint display) nothrow @nogcCGDisplayIsAsleep(uint (parameter) uint displaydisplay);
uint uint app.CGDisplayIsInMirrorSet(uint display) nothrow @nogcCGDisplayIsInMirrorSet(uint (parameter) uint displaydisplay);
(struct) app.NSSizeNSSize app.NSSize app.CGDisplayScreenSize(uint display) nothrow @nogcCGDisplayScreenSize(uint (parameter) uint displaydisplay); // physical size in millimetres
void* void* app.CGDisplayCopyDisplayMode(uint display) nothrow @nogcCGDisplayCopyDisplayMode(uint (parameter) uint displaydisplay);
(alias) object.size_t = ulongsize_t ulong app.CGDisplayModeGetWidth(void* mode) nothrow @nogcCGDisplayModeGetWidth(void* (parameter) void* modemode); // points
(alias) object.size_t = ulongsize_t ulong app.CGDisplayModeGetHeight(void* mode) nothrow @nogcCGDisplayModeGetHeight(void* (parameter) void* modemode);
(alias) object.size_t = ulongsize_t ulong app.CGDisplayModeGetPixelWidth(void* mode) nothrow @nogcCGDisplayModeGetPixelWidth(void* (parameter) void* modemode); // pixels
(alias) object.size_t = ulongsize_t ulong app.CGDisplayModeGetPixelHeight(void* mode) nothrow @nogcCGDisplayModeGetPixelHeight(void* (parameter) void* modemode);
double double app.CGDisplayModeGetRefreshRate(void* mode) nothrow @nogcCGDisplayModeGetRefreshRate(void* (parameter) void* modemode);
void void app.CGDisplayModeRelease(void* mode) nothrow @nogcCGDisplayModeRelease(void* (parameter) void* modemode);
void void app.CGContextSetRGBFillColor(void* ctx, double r, double g, double b, double a) nothrow @nogcCGContextSetRGBFillColor(void* (parameter) void* ctxctx, double (parameter) double rr, double (parameter) double gg, double (parameter) double bb, double (parameter) double aa);
void void app.CGContextFillRect(void* ctx, app.NSRect rect) nothrow @nogcCGContextFillRect(void* (parameter) void* ctxctx, (struct) app.NSRectNSRect (parameter) app.NSRect rectrect);
// AppKit-exported notification-name constants (NSString*).
extern __gshared void* (__gshared global) void* app.NSApplicationDidChangeScreenParametersNotificationNSApplicationDidChangeScreenParametersNotification;
extern __gshared void* (__gshared global) void* app.NSWindowDidChangeScreenNotificationNSWindowDidChangeScreenNotification;
}
// Declared outside the @nogc block above: the callback logs (fprintf is not @nogc).
extern (C) nothrow
{
alias (alias) app.CGDisplayReconfigurationCallBack = extern (C) void function(uint display, uint flags, void* userInfo) nothrowCGDisplayReconfigurationCallBack =
void function(uint (parameter) uint displaydisplay, uint (parameter) uint flagsflags, void* (parameter) void* userInfouserInfo) nothrow;
int int app.CGDisplayRegisterReconfigurationCallback(extern (C) void function(uint display, uint flags, void* userInfo) nothrow callback, void* userInfo) nothrowCGDisplayRegisterReconfigurationCallback(
(alias) app.CGDisplayReconfigurationCallBack = extern (C) void function(uint display, uint flags, void* userInfo) nothrowCGDisplayReconfigurationCallBack (parameter) extern (C) void function(uint display, uint flags, void* userInfo) nothrow callbackcallback, void* (parameter) void* userInfouserInfo);
}
// CGDisplayChangeSummaryFlags bits (CGDisplayConfiguration.h).
enum : uint
{
(enum value) app.kCGDisplayBeginConfigurationFlag = 1ukCGDisplayBeginConfigurationFlag = 1 << 0,
(enum value) app.kCGDisplayMovedFlag = 2ukCGDisplayMovedFlag = 1 << 1,
(enum value) app.kCGDisplaySetMainFlag = 4ukCGDisplaySetMainFlag = 1 << 2,
(enum value) app.kCGDisplaySetModeFlag = 8ukCGDisplaySetModeFlag = 1 << 3,
(enum value) app.kCGDisplayAddFlag = 16ukCGDisplayAddFlag = 1 << 4,
(enum value) app.kCGDisplayRemoveFlag = 32ukCGDisplayRemoveFlag = 1 << 5,
(enum value) app.kCGDisplayEnabledFlag = 256ukCGDisplayEnabledFlag = 1 << 8,
(enum value) app.kCGDisplayDisabledFlag = 512ukCGDisplayDisabledFlag = 1 << 9,
(enum value) app.kCGDisplayMirrorFlag = 1024ukCGDisplayMirrorFlag = 1 << 10,
(enum value) app.kCGDisplayUnMirrorFlag = 2048ukCGDisplayUnMirrorFlag = 1 << 11,
(enum value) app.kCGDisplayDesktopShapeChangedFlag = 4096ukCGDisplayDesktopShapeChangedFlag = 1 << 12,
}
// The reconfiguration callback fires twice per change (begin + after) per display.
// Headless expectation: never — the registration + the static answer is the
// deliverable; counting proves it.
extern (C) void void app.displayReconfigCb(uint display, uint flags, void* userInfo) nothrowdisplayReconfigCb(uint (parameter) uint displaydisplay, uint (parameter) uint flagsflags, void* (parameter) void* userInfouserInfo) nothrow
{
++(__gshared global) int app.g_reconfigCallsg_reconfigCalls;
try
instr("cg_reconfig", "id=%u flags=0x%x begin=%d add=%d remove=%d set_mode=%d",
display, flags,
(flags & kCGDisplayBeginConfigurationFlag) != 0,
(flags & kCGDisplayAddFlag) != 0,
(flags & kCGDisplayRemoveFlag) != 0,
(flags & kCGDisplaySetModeFlag) != 0);
catch ((class) object.ExceptionThe base class of all errors that are safe to catch and handle.
In principle, only thrown objects derived from this class are safe to catch
inside a catch block. Thrown objects not derived from Exception
represent runtime errors that should not be caught, as certain runtime
guarantees may not hold, making it unsafe to continue program execution.
Examples
bool gotCaught;
try
{
throw new Exception("msg");
}
catch (Exception e)
{
gotCaught = true;
assert(e.msg == "msg");
}
assert(gotCaught);
Exception)
{
}
}
// --- AppKit class declarations ----------------------------------------------------
extern (Objective-C):
extern class (class) app.NSObjectNSObject
{
}
extern class (class) app.NSStringNSString : (class) app.NSObjectNSObject
{
static (class) app.NSStringNSString app.NSString app.NSString.alloc()alloc() @selector("alloc");
(class) app.NSStringNSString app.NSString app.NSString.initWithUTF8String(const(char)* s)initWithUTF8String(const(char)* (parameter) const(char)* ss) @selector("initWithUTF8String:");
const(char)* const(char)* app.NSString.UTF8String()UTF8String() @selector("UTF8String");
}
extern class (class) app.NSArrayNSArray : (class) app.NSObjectNSObject
{
ulong ulong app.NSArray.count()count() @selector("count");
(class) app.NSObjectNSObject app.NSObject app.NSArray.objectAtIndex(ulong index)objectAtIndex(ulong (parameter) ulong indexindex) @selector("objectAtIndex:");
}
extern class (class) app.NSNumberNSNumber : (class) app.NSObjectNSObject
{
uint uint app.NSNumber.unsignedIntValue()unsignedIntValue() @selector("unsignedIntValue");
}
extern class (class) app.NSValueNSValue : (class) app.NSObjectNSObject
{
(struct) app.NSSizeNSSize app.NSSize app.NSValue.sizeValue()sizeValue() @selector("sizeValue");
}
extern class (class) app.NSDictionaryNSDictionary : (class) app.NSObjectNSObject
{
(class) app.NSObjectNSObject app.NSObject app.NSDictionary.objectForKey(app.NSObject key)objectForKey((class) app.NSObjectNSObject (parameter) app.NSObject keykey) @selector("objectForKey:");
}
extern class (class) app.NSNotificationNSNotification : (class) app.NSObjectNSObject
{
}
extern class (class) app.NSNotificationCenterNSNotificationCenter : (class) app.NSObjectNSObject
{
static (class) app.NSNotificationCenterNSNotificationCenter app.NSNotificationCenter app.NSNotificationCenter.defaultCenter()defaultCenter() @selector("defaultCenter");
void app.NSNotificationCenter.addObserveraddObserver((class) app.NSObjectNSObject observer, SEL sel, (class) app.NSObjectNSObject (parameter) NSObject namename, (class) app.NSObjectNSObject (parameter) NSObject objobj)
@selector("addObserver:selector:name:object:");
void void app.NSNotificationCenter.postNotificationName(app.NSObject name, app.NSObject obj)postNotificationName((class) app.NSObjectNSObject (parameter) app.NSObject namename, (class) app.NSObjectNSObject (parameter) app.NSObject objobj)
@selector("postNotificationName:object:");
}
extern class (class) app.NSScreenNSScreen : (class) app.NSObjectNSObject
{
static (class) app.NSArrayNSArray app.NSArray app.NSScreen.screens()screens() @selector("screens");
static (class) app.NSScreenNSScreen app.NSScreen app.NSScreen.mainScreen()mainScreen() @selector("mainScreen");
(struct) app.NSRectNSRect app.NSRect app.NSScreen.frame()frame() @selector("frame");
(struct) app.NSRectNSRect app.NSRect app.NSScreen.visibleFrame()visibleFrame() @selector("visibleFrame");
double double app.NSScreen.backingScaleFactor()backingScaleFactor() @selector("backingScaleFactor");
(class) app.NSStringNSString app.NSString app.NSScreen.localizedName()localizedName() @selector("localizedName");
long long app.NSScreen.maximumFramesPerSecond()maximumFramesPerSecond() @selector("maximumFramesPerSecond");
(class) app.NSDictionaryNSDictionary app.NSDictionary app.NSScreen.deviceDescription()deviceDescription() @selector("deviceDescription");
}
extern class (class) app.NSResponderNSResponder : (class) app.NSObjectNSObject
{
}
extern class (class) app.NSEventNSEvent : (class) app.NSObjectNSObject
{
static (class) app.NSEventNSEvent app.NSEvent app.NSEvent.otherEventWithType(long type, app.NSPoint location, ulong modifierFlags, double timestamp, long windowNumber, app.NSObject context, short subtype, long data1, long data2)otherEventWithType(long (parameter) long typetype, (struct) app.NSPointNSPoint (parameter) app.NSPoint locationlocation, ulong (parameter) ulong modifierFlagsmodifierFlags,
double (parameter) double timestamptimestamp, long (parameter) long windowNumberwindowNumber, (class) app.NSObjectNSObject (parameter) app.NSObject contextcontext, short (parameter) short subtypesubtype,
long (parameter) long data1data1, long (parameter) long data2data2)
@selector("otherEventWithType:location:modifierFlags:timestamp:windowNumber:context:subtype:data1:data2:");
}
extern class (class) app.NSTimerNSTimer : (class) app.NSObjectNSObject
{
static (class) app.NSTimerNSTimer app.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(double interval, (class) app.NSObjectNSObject target,
SEL sel, (class) app.NSObjectNSObject (parameter) NSObject userInfouserInfo, bool repeats)
@selector("scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:");
}
extern class (class) app.NSGraphicsContextNSGraphicsContext : (class) app.NSObjectNSObject
{
static (class) app.NSGraphicsContextNSGraphicsContext app.NSGraphicsContext app.NSGraphicsContext.currentContext()currentContext() @selector("currentContext");
void* void* app.NSGraphicsContext.CGContext()CGContext() @selector("CGContext");
}
extern class (class) app.NSApplicationNSApplication : (class) app.NSResponderNSResponder
{
static (class) app.NSApplicationNSApplication app.NSApplication app.NSApplication.sharedApplication()sharedApplication() @selector("sharedApplication");
void void app.NSApplication.setActivationPolicy(long policy)setActivationPolicy(long (parameter) long policypolicy) @selector("setActivationPolicy:");
void void app.NSApplication.run()run() @selector("run");
void void app.NSApplication.stop(app.NSObject sender)stop((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("stop:");
void void app.NSApplication.postEvent(app.NSEvent event, bool atStart)postEvent((class) app.NSEventNSEvent (parameter) app.NSEvent eventevent, bool (parameter) bool atStartatStart) @selector("postEvent:atStart:");
}
extern class (class) app.NSViewNSView : (class) app.NSResponderNSResponder
{
(struct) app.NSRectNSRect app.NSRect app.NSView.bounds()bounds() @selector("bounds");
}
extern class (class) app.NSWindowNSWindow : (class) app.NSResponderNSResponder
{
static (class) app.NSWindowNSWindow app.NSWindow app.NSWindow.alloc()alloc() @selector("alloc");
(class) app.NSWindowNSWindow app.NSWindow app.NSWindow.initWithContentRect(app.NSRect contentRect, ulong styleMask, ulong backing, bool defer)initWithContentRect((struct) app.NSRectNSRect (parameter) app.NSRect contentRectcontentRect, ulong (parameter) ulong styleMaskstyleMask,
ulong (parameter) ulong backingbacking, bool (parameter) bool deferdefer) @selector("initWithContentRect:styleMask:backing:defer:");
void void app.NSWindow.setTitle(app.NSString title)setTitle((class) app.NSStringNSString (parameter) app.NSString titletitle) @selector("setTitle:");
void void app.NSWindow.setContentView(app.NSView view)setContentView((class) app.NSViewNSView (parameter) app.NSView viewview) @selector("setContentView:");
void void app.NSWindow.setDelegate(app.NSObject anObject)setDelegate((class) app.NSObjectNSObject (parameter) app.NSObject anObjectanObject) @selector("setDelegate:");
void void app.NSWindow.makeKeyAndOrderFront(app.NSObject sender)makeKeyAndOrderFront((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("makeKeyAndOrderFront:");
(struct) app.NSRectNSRect app.NSRect app.NSWindow.frame()frame() @selector("frame");
(class) app.NSScreenNSScreen app.NSScreen app.NSWindow.screen()screen() @selector("screen");
}
// The instrumented view: flat fill + notification handlers + the tick schedule.
class (class) app.OutputViewOutputView : (class) app.NSViewNSView
{
static (class) app.OutputViewOutputView app.OutputView app.OutputView.alloc()alloc() @selector("alloc");
(class) app.OutputViewOutputView app.OutputView app.OutputView.initWithFrame(app.NSRect frame)initWithFrame((struct) app.NSRectNSRect (parameter) app.NSRect frameframe) @selector("initWithFrame:");
void void app.OutputView.drawRect(app.NSRect dirtyRect)drawRect((struct) app.NSRectNSRect (parameter) app.NSRect dirtyRectdirtyRect) @selector("drawRect:")
{
void* _error_ ctxctx = NSGraphicsContext.NSGraphicsContext.currentContextcurrentContext().NSGraphicsContext.currentContext().CGContextCGContext();
CGContextSetRGBFillColor(ctx, 0.15, 0.3, 0.5, 1);
CGContextFillRect(ctx, this.this.boundsbounds());
}
void void app.OutputView.tick(app.NSTimer timer)tick((class) app.NSTimerNSTimer (parameter) app.NSTimer timertimer) @selector("tick:")
{
onTick();
}
// App-level: display configuration changed — re-enumerate everything.
void void app.OutputView.screenParamsChanged(app.NSNotification n)screenParamsChanged((class) app.NSNotificationNSNotification (parameter) app.NSNotification nn) @selector("screenParamsChanged:")
{
++g_screenParamsEvents;
instr("screen_params_changed", "n=%d", g_screenParamsEvents);
logNSScreens("notification");
}
// Window-level: THIS window moved to a different screen.
void void app.OutputView.windowScreenChanged(app.NSNotification n)windowScreenChanged((class) app.NSNotificationNSNotification (parameter) app.NSNotification nn) @selector("windowScreenChanged:")
{
++g_windowScreenEvents;
instr("window_screen_changed", "n=%d", g_windowScreenEvents);
logWindowScreen("notification");
}
void void app.OutputView.windowWillClose(app.NSObject notification)windowWillClose((class) app.NSObjectNSObject (parameter) app.NSObject notificationnotification) @selector("windowWillClose:")
{
instr("close_requested");
g_app.g_app.stopstop(null);
}
}
extern (D): // back to D linkage
enum (constant) int app.NSApplicationActivationPolicyRegular = 0NSApplicationActivationPolicyRegular = 0;
enum : ulong
{
(enum value) app.NSWindowStyleMaskTitled = 1LUNSWindowStyleMaskTitled = 1,
(enum value) app.NSWindowStyleMaskClosable = 2LUNSWindowStyleMaskClosable = 2,
(enum value) app.NSWindowStyleMaskResizable = 8LUNSWindowStyleMaskResizable = 8,
}
enum ulong (constant) ulong app.NSBackingStoreBuffered = 2LUNSBackingStoreBuffered = 2;
enum long (constant) long app.NSEventTypeApplicationDefined = 15LNSEventTypeApplicationDefined = 15;
enum (constant) int app.maxDisplays = 16maxDisplays = 16;
// --- Demo state --------------------------------------------------------------------
__gshared (class) app.NSApplicationNSApplication (__gshared global) app.NSApplication app.g_appg_app;
__gshared (class) app.NSWindowNSWindow (__gshared global) app.NSWindow app.g_wing_win;
__gshared int (__gshared global) int app.g_tickg_tick, (__gshared global) int app.g_reconfigCallsg_reconfigCalls, (__gshared global) int app.g_screenParamsEventsg_screenParamsEvents, (__gshared global) int app.g_windowScreenEventsg_windowScreenEvents;
__gshared bool (__gshared global) bool app.g_autog_auto, (__gshared global) bool app.g_stopRequestedg_stopRequested;
__gshared uint (__gshared global) uint app.g_startupActiveg_startupActive, (__gshared global) uint app.g_startupOnlineg_startupOnline; // startup counts for the late diff
// Schedule (ticks of a ~16 ms timer).
enum (constant) int app.windowScreenTick = 10windowScreenTick = 10;
enum (constant) int app.reEnumTick = 20reEnumTick = 20;
enum (constant) int app.selftestTick = 30selftestTick = 30;
enum (constant) int app.autoExitTick = 45autoExitTick = 45;
(class) app.NSStringNSString app.NSString app.nsstr(const(char)* s)nsstr(const(char)* (parameter) const(char)* ss)
{
return NSString.NSString.allocalloc().NSString.alloc().initWithUTF8StringinitWithUTF8String(s);
}
// The bridge key: -[NSScreen deviceDescription][@"NSScreenNumber"] IS the
// CGDirectDisplayID, boxed in an NSNumber.
uint uint app.screenNumber(app.NSScreen s)screenNumber((class) app.NSScreenNSScreen (parameter) app.NSScreen ss)
{
(unresolved type) NSDictionaryNSDictionary _error_ dddd = s.s.deviceDescriptiondeviceDescription();
auto _error_ numnum = cast((unresolved type) NSNumberNSNumber) dd.dd.objectForKeyobjectForKey(cast((unresolved type) NSObjectNSObject) nsstr("NSScreenNumber"));
return num !is null ? num.num.unsignedIntValueunsignedIntValue() : 0;
}
// --- CG-side enumeration (pixels, top-left-origin, modes) --------------------------
uint[2] uint[2] app.logCGDisplays(const(char)* when)logCGDisplays(const(char)* (parameter) const(char)* whenwhen)
{
uint[(unresolved type) maxDisplaysmaxDisplays] _error_ onlineonline, _error_ activeactive;
uint _error_ nOnlinenOnline, _error_ nActivenActive;
CGGetOnlineDisplayList(maxDisplays, online.ptr, &nOnline);
CGGetActiveDisplayList(maxDisplays, active.ptr, &nActive);
instr("cg_displays", "when=%s online=%u active=%u", when, nOnline, nActive);
// Iterate the ONLINE list: in a locked session the ACTIVE list can be empty
// (drawable-desktop displays only) while the hardware is still online.
foreach ((parameter) ii; 0 .. nOnline)
{
immutable _error_ idid = online[i];
immutable _error_ bb = CGDisplayBounds(id);
immutable _error_ mmmm = CGDisplayScreenSize(id);
instr("output",
"api=cg id=%u bounds=(%.0f,%.0f %.0fx%.0f) px=%zux%zu mm=%.0fx%.0f "
~ "vendor=0x%x model=0x%x serial=0x%x builtin=%u main=%u "
~ "active=%u online=%u asleep=%u mirror=%u",
id, b.b.originorigin.b.origin.xx, b.b.originorigin.b.origin.yy, b.b.sizesize.b.size.widthwidth, b.b.sizesize.b.size.heightheight,
CGDisplayPixelsWide(id), CGDisplayPixelsHigh(id), mm.mm.widthwidth, mm.mm.heightheight,
CGDisplayVendorNumber(id), CGDisplayModelNumber(id),
CGDisplaySerialNumber(id), CGDisplayIsBuiltin(id), CGDisplayIsMain(id),
CGDisplayIsActive(id), CGDisplayIsOnline(id), CGDisplayIsAsleep(id),
CGDisplayIsInMirrorSet(id));
void* _error_ modemode = CGDisplayCopyDisplayMode(id);
if (mode !is null)
{
instr("output_mode", "api=cg id=%u mode_pt=%zux%zu mode_px=%zux%zu refresh=%.2f",
id, CGDisplayModeGetWidth(mode), CGDisplayModeGetHeight(mode),
CGDisplayModeGetPixelWidth(mode), CGDisplayModeGetPixelHeight(mode),
CGDisplayModeGetRefreshRate(mode));
CGDisplayModeRelease(mode);
}
else
instr("output_mode", "api=cg id=%u mode=nil", id);
}
return [nActive, nOnline];
}
// --- NSScreen-side enumeration (points, y-up, per-screen scale) --------------------
void void app.logNSScreens(const(char)* when)logNSScreens(const(char)* (parameter) const(char)* whenwhen)
{
(unresolved type) NSArrayNSArray _error_ screensscreens = NSScreen.NSScreen.screensscreens();
immutable _error_ nn = screens !is null ? screens.screens.countcount() : 0;
instr("ns_screens", "when=%s count=%llu", when, n);
foreach ((parameter) ii; 0 .. n)
{
auto _error_ ss = cast((unresolved type) NSScreenNSScreen) screens.screens.objectAtIndexobjectAtIndex(i);
immutable _error_ ff = s.s.frameframe();
immutable _error_ vv = s.s.visibleFramevisibleFrame();
(unresolved type) NSStringNSString _error_ namename = s.s.localizedNamelocalizedName();
instr("output",
"api=appkit idx=%llu id=%u frame=(%.0f,%.0f %.0fx%.0f) "
~ "visible=(%.0f,%.0f %.0fx%.0f) scale=%.1f max_fps=%ld name=\"%s\"",
i, screenNumber(s), f.f.originorigin.f.origin.xx, f.f.originorigin.f.origin.yy, f.f.sizesize.f.size.widthwidth, f.f.sizesize.f.size.heightheight,
v.v.originorigin.v.origin.xx, v.v.originorigin.v.origin.yy, v.v.sizesize.v.size.widthwidth, v.v.sizesize.v.size.heightheight,
s.s.backingScaleFactorbackingScaleFactor(), s.s.maximumFramesPerSecondmaximumFramesPerSecond(),
name !is null ? name.name.UTF8StringUTF8String() : "");
// The frame/visibleFrame split: menubar shaves the top, the Dock the bottom.
instr("output_insets", "api=appkit id=%u menubar_pt=%.0f dock_pt=%.0f side_pt=%.0f",
screenNumber(s),
(f.f.originorigin.f.origin.yy + f.f.sizesize.f.size.heightheight) - (v.v.originorigin.v.origin.yy + v.v.sizesize.v.size.heightheight),
v.v.originorigin.v.origin.yy - f.f.originorigin.f.origin.yy, v.v.originorigin.v.origin.xx - f.f.originorigin.f.origin.xx);
// deviceDescription extras: NSDeviceSize (points) + NSDeviceResolution (dpi).
(unresolved type) NSDictionaryNSDictionary _error_ dddd = s.s.deviceDescriptiondeviceDescription();
auto _error_ devSizedevSize = cast((unresolved type) NSValueNSValue) dd.dd.objectForKeyobjectForKey(cast((unresolved type) NSObjectNSObject) nsstr("NSDeviceSize"));
auto _error_ devResdevRes = cast((unresolved type) NSValueNSValue) dd.dd.objectForKeyobjectForKey(cast((unresolved type) NSObjectNSObject) nsstr("NSDeviceResolution"));
if (devSize !is null && devRes !is null)
{
immutable _error_ szsz = devSize.devSize.sizeValuesizeValue();
immutable _error_ resres = devRes.devRes.sizeValuesizeValue();
instr("output_device", "api=appkit id=%u device_size_pt=%.0fx%.0f device_dpi=%.0fx%.0f",
screenNumber(s), sz.sz.widthwidth, sz.sz.heightheight, res.res.widthwidth, res.res.heightheight);
}
}
}
// The NSScreenNumber <-> CGDirectDisplayID bridge, checked both ways.
void void app.logBridge()logBridge()
{
uint[(unresolved type) maxDisplaysmaxDisplays] _error_ onlineonline, _error_ activeactive;
uint _error_ nOnlinenOnline, _error_ nActivenActive;
CGGetOnlineDisplayList(maxDisplays, online.ptr, &nOnline);
CGGetActiveDisplayList(maxDisplays, active.ptr, &nActive);
(unresolved type) NSArrayNSArray _error_ screensscreens = NSScreen.NSScreen.screensscreens();
immutable _error_ nn = screens !is null ? screens.screens.countcount() : 0;
foreach ((parameter) ii; 0 .. n)
{
auto _error_ ss = cast((unresolved type) NSScreenNSScreen) screens.screens.objectAtIndexobjectAtIndex(i);
immutable _error_ idid = screenNumber(s);
bool _error_ foundActivefoundActive = false, _error_ foundOnlinefoundOnline = false;
foreach ((parameter) jj; 0 .. nActive)
foundActive |= active[j] == id;
foreach ((parameter) jj; 0 .. nOnline)
foundOnline |= online[j] == id;
instr("bridge", "nsscreen_idx=%llu nsscreen_number=%u in_cg_online_list=%d "
~ "in_cg_active_list=%d px_check=%zux%zu", i, id, foundOnline ? 1 : 0,
foundActive ? 1 : 0, CGDisplayPixelsWide(id), CGDisplayPixelsHigh(id));
}
}
// Which screen is the window on? AppKit answers directly — no derivation needed.
void void app.logWindowScreen(const(char)* when)logWindowScreen(const(char)* (parameter) const(char)* whenwhen)
{
(unresolved type) NSScreenNSScreen _error_ ss = g_win.g_win.screenscreen();
immutable _error_ ff = g_win.g_win.frameframe();
if (s is null)
{
instr("window_screen", "when=%s screen=nil note=offscreen_window", when);
return;
}
(unresolved type) NSStringNSString _error_ namename = s.s.localizedNamelocalizedName();
instr("window_screen", "when=%s id=%u name=\"%s\" window_frame=(%.0f,%.0f %.0fx%.0f) "
~ "api=window.screen derived=no",
when, screenNumber(s), name !is null ? name.name.UTF8StringUTF8String() : "",
f.f.originorigin.f.origin.xx, f.f.originorigin.f.origin.yy, f.f.sizesize.f.size.widthwidth, f.f.sizesize.f.size.heightheight);
}
void void app.onTick()onTick()
{
++g_tick;
if (!g_auto)
return; // interactive mode: keep the window up for a manual plug/unplug
if (g_tick == windowScreenTick)
{
instr("step", "name=window_screen_steady");
logWindowScreen("steady");
}
else if (g_tick == reEnumTick)
{
// The headless probe: does the locked session itself ever change the lists?
instr("step", "name=re_enumerate note=locked_session_diff");
immutable _error_ countscounts = logCGDisplays("re_enum");
logNSScreens("re_enum");
instr("hotplug_probe", "active_delta=%d online_delta=%d reconfig_cb_calls=%d "
~ "note=locked_session_static",
cast(int) counts[0] - cast(int) g_startupActive,
cast(int) counts[1] - cast(int) g_startupOnline, g_reconfigCalls);
}
else if (g_tick == selftestTick)
{
// Prove the NSNotification wiring (nothing fires it headless): post both by hand.
instr("step", "name=post_notifications note=selftest");
(unresolved type) NSNotificationCenterNSNotificationCenter _error_ ncnc = NSNotificationCenter.NSNotificationCenter.defaultCenterdefaultCenter();
nc.nc.postNotificationNamepostNotificationName(
cast((unresolved type) NSObjectNSObject) NSApplicationDidChangeScreenParametersNotification, null);
nc.nc.postNotificationNamepostNotificationName(
cast((unresolved type) NSObjectNSObject) NSWindowDidChangeScreenNotification, cast((unresolved type) NSObjectNSObject) g_win);
}
else if (g_tick >= autoExitTick && !g_stopRequested)
{
g_stopRequested = true;
instr("step", "name=NSApp_stop tick=%d", g_tick);
g_app.g_app.stopstop(null);
(unresolved type) NSEventNSEvent _error_ evev = NSEvent.NSEvent.otherEventWithTypeotherEventWithType(NSEventTypeApplicationDefined,
NSPoint(0, 0), 0, 0, 0, null, 0, 0, 0);
g_app.g_app.postEventpostEvent(ev, true); // stop: needs an event dispatch to take effect
}
}
int int D main()main()
{
g_auto = getenv("WSI_AUTO_EXIT") !is null;
instrInit("APPKIT_F09");
instr("init_start", "auto_exit=%d", g_auto ? 1 : 0);
if (CGMainDisplayID() == 0)
{
printf("SKIP: no macOS window server (CGMainDisplayID == 0)\n");
return 0;
}
auto _error_ poolpool = autoreleasepool_push();
scope (exit)
autoreleasepool_pop(pool);
// CG enumeration FIRST — before NSApplication.sharedApplication() even exists.
// CoreGraphics enumeration needs only the WindowServer connection, no app object,
// no window: it is global. (NSScreen is AppKit, so it waits for the app below.)
instr("step", "name=cg_enumeration note=pre_NSApplication main_id=%u", CGMainDisplayID());
immutable _error_ countscounts = logCGDisplays("startup");
g_startupActive = counts[0];
g_startupOnline = counts[1];
// Hotplug/reconfiguration callback — also app-object-free, registered pre-AppKit.
immutable _error_ cbErrcbErr = CGDisplayRegisterReconfigurationCallback(&displayReconfigCb, null);
instr("step", "name=CGDisplayRegisterReconfigurationCallback err=%d", cbErr);
g_app = NSApplication.NSApplication.sharedApplicationsharedApplication();
g_app.g_app.setActivationPolicysetActivationPolicy(NSApplicationActivationPolicyRegular);
logNSScreens("startup");
logBridge();
immutable _error_ contentRectcontentRect = NSRect(NSPoint(120, 120), NSSize(480, 320));
g_win = NSWindow.NSWindow.allocalloc().NSWindow.alloc().initWithContentRectinitWithContentRect(contentRect,
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable,
NSBackingStoreBuffered, false);
g_win.g_win.setTitlesetTitle(nsstr("wsi-f09-outputs"));
// -[NSWindow screen] answers before the window is even ordered in.
logWindowScreen("post_init");
(unresolved type) OutputViewOutputView _error_ viewview = OutputView.OutputView.allocalloc().OutputView.alloc().initWithFrameinitWithFrame(contentRect);
(unresolved type) NSNotificationCenterNSNotificationCenter _error_ ncnc = NSNotificationCenter.NSNotificationCenter.defaultCenterdefaultCenter();
nc.nc.addObserveraddObserver(view, SEL.register("screenParamsChanged:"),
cast((unresolved type) NSObjectNSObject) NSApplicationDidChangeScreenParametersNotification, null);
nc.nc.addObserveraddObserver(view, SEL.register("windowScreenChanged:"),
cast((unresolved type) NSObjectNSObject) NSWindowDidChangeScreenNotification, cast((unresolved type) NSObjectNSObject) g_win);
instr("step", "name=notification_observers "
~ "registered=didChangeScreenParameters,windowDidChangeScreen");
g_win.g_win.setContentViewsetContentView(view);
g_win.g_win.setDelegatesetDelegate(view);
g_win.g_win.makeKeyAndOrderFrontmakeKeyAndOrderFront(null);
logWindowScreen("post_order_front");
instr("step", "name=NSTimer_scheduledTimerWithTimeInterval interval_ms=16");
NSTimer.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(0.016, view, SEL.register("tick:"), null, true);
instr("step", "name=NSApp_run");
g_app.g_app.runrun();
instr("loop_exit", "ticks=%d reconfig_cb=%d screen_params=%d window_screen=%d",
g_tick, g_reconfigCalls, g_screenParamsEvents, g_windowScreenEvents);
return 0;
}