// macOS/AppKit F08 demo — DPI / backingScaleFactor (../../../features/f08-dpi-scaling.md).
// Where the scale lives, when it becomes learnable, and what AppKit pre-scales for a
// software renderer. Built on the scaffold/f02 recipe; the run findings are A[ssh]
// (locked console — no monitor drag possible; that path is Tier C).
//
// What it logs:
// - every scale source: NSScreen.screens (per-screen backingScaleFactor + frame),
// NSWindow.backingScaleFactor, NSView convertRectToBacking:, and the CALayer
// contentsScale during a brief wantsLayer=YES probe;
// - the created-at-wrong-scale timeline with µs timestamps: window scale at
// initWithContentRect return, the VIEW's pre-install conversion scale (a windowless
// view converts at 1.0), and viewDidChangeBackingProperties firing 1.0 -> 2.0
// synchronously inside setContentView: — plus a counter proving nothing else
// triggers it headless (resizes, ordering, an off-screen setFrameOrigin:);
// - convertRectToBacking:/convertRectFromBacking: round-trips, incl. odd and
// fractional point sizes;
// - drawRect: ground truth: CGContextGetCTM of the view's context — the transform
// AppKit pre-installs so points-rect drawing lands on device pixels;
// - the deliberately-mismatched probe: one frame draws a 1.0-scale (points-sized)
// CGImage into the 2.0 view — same points rect, half the pixels, so Quartz
// resamples it x2 (the classic blurry-stretch); the CGImage size vs CTM scale is
// the app-observable evidence;
// - per-resize buffer math assert pixels == points x scale (the f02 invariant);
// 1-physical-px hairlines (1/scale-point rects) at the window edges;
// - runtime-rescale observability: registration for
// NSApplicationDidChangeScreenParametersNotification and
// NSWindowDidChangeBackingPropertiesNotification (with the old-scale userInfo key),
// which would fire on a display config change / monitor drag (Tier C).
//
// Modes: WSI_AUTO_EXIT=1 = bounded scripted run (exit 1 on any math mismatch);
// without it the window stays up for a manual monitor-drag 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, (alias) app.malloc = void* core.stdc.stdlib.malloc(ulong size) nothrow @nogcmalloc, (alias) app.free = void core.stdc.stdlib.free(void* ptr) nothrow @nogcfree;
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.CGAffineTransformCGAffineTransform
{
double (field) double app.CGAffineTransform.aa, (field) double app.CGAffineTransform.bb, (field) double app.CGAffineTransform.cc, (field) double app.CGAffineTransform.dd, (field) double app.CGAffineTransform.txtx, (field) double app.CGAffineTransform.tyty;
}
extern (C) nothrow @nogc
{
uint uint app.CGMainDisplayID() nothrow @nogcCGMainDisplayID();
void* void* app.CGColorSpaceCreateDeviceRGB() nothrow @nogcCGColorSpaceCreateDeviceRGB();
void void app.CGColorSpaceRelease(void* space) nothrow @nogcCGColorSpaceRelease(void* (parameter) void* spacespace);
void* void* app.CGDataProviderCreateWithData(void* info, const(void)* data, ulong size, void* releaseCallback) nothrow @nogcCGDataProviderCreateWithData(void* (parameter) void* infoinfo, const(void)* (parameter) const(void)* datadata, (alias) object.size_t = ulongsize_t (parameter) ulong sizesize,
void* (parameter) void* releaseCallbackreleaseCallback);
void void app.CGDataProviderRelease(void* provider) nothrow @nogcCGDataProviderRelease(void* (parameter) void* providerprovider);
void* void* app.CGImageCreate(ulong width, ulong height, ulong bitsPerComponent, ulong bitsPerPixel, ulong bytesPerRow, void* colorSpace, uint bitmapInfo, void* provider, const(double)* decode, bool shouldInterpolate, int intent) nothrow @nogcCGImageCreate((alias) object.size_t = ulongsize_t (parameter) ulong widthwidth, (alias) object.size_t = ulongsize_t (parameter) ulong heightheight, (alias) object.size_t = ulongsize_t (parameter) ulong bitsPerComponentbitsPerComponent,
(alias) object.size_t = ulongsize_t (parameter) ulong bitsPerPixelbitsPerPixel, (alias) object.size_t = ulongsize_t (parameter) ulong bytesPerRowbytesPerRow, void* (parameter) void* colorSpacecolorSpace, uint (parameter) uint bitmapInfobitmapInfo,
void* (parameter) void* providerprovider, const(double)* (parameter) const(double)* decodedecode, bool (parameter) bool shouldInterpolateshouldInterpolate, int (parameter) int intentintent);
void void app.CGImageRelease(void* image) nothrow @nogcCGImageRelease(void* (parameter) void* imageimage);
(alias) object.size_t = ulongsize_t ulong app.CGImageGetWidth(void* image) nothrow @nogcCGImageGetWidth(void* (parameter) void* imageimage);
(alias) object.size_t = ulongsize_t ulong app.CGImageGetHeight(void* image) nothrow @nogcCGImageGetHeight(void* (parameter) void* imageimage);
void void app.CGContextDrawImage(void* ctx, app.NSRect rect, void* image) nothrow @nogcCGContextDrawImage(void* (parameter) void* ctxctx, (struct) app.NSRectNSRect (parameter) app.NSRect rectrect, void* (parameter) void* imageimage);
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);
(struct) app.CGAffineTransformCGAffineTransform app.CGAffineTransform app.CGContextGetCTM(void* ctx) nothrow @nogcCGContextGetCTM(void* (parameter) void* ctxctx);
// AppKit-exported notification-name / userInfo-key constants (NSString*).
extern __gshared void* (__gshared global) void* app.NSApplicationDidChangeScreenParametersNotificationNSApplicationDidChangeScreenParametersNotification;
extern __gshared void* (__gshared global) void* app.NSWindowDidChangeBackingPropertiesNotificationNSWindowDidChangeBackingPropertiesNotification;
extern __gshared void* (__gshared global) void* app.NSBackingPropertyOldScaleFactorKeyNSBackingPropertyOldScaleFactorKey;
}
enum uint (constant) uint app.kCGImageAlphaNoneSkipFirst = 6ukCGImageAlphaNoneSkipFirst = 6;
enum uint (constant) uint app.kCGBitmapByteOrder32Little = 8192ukCGBitmapByteOrder32Little = 2 << 12;
enum int (constant) int app.kCGRenderingIntentDefault = 0kCGRenderingIntentDefault = 0;
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;
}
// --- 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
{
double double app.NSNumber.doubleValue()doubleValue() @selector("doubleValue");
}
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
{
(class) app.NSDictionaryNSDictionary app.NSDictionary app.NSNotification.userInfo()userInfo() @selector("userInfo");
}
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");
double double app.NSScreen.backingScaleFactor()backingScaleFactor() @selector("backingScaleFactor");
(struct) app.NSRectNSRect app.NSRect app.NSScreen.frame()frame() @selector("frame");
(class) app.NSStringNSString app.NSString app.NSScreen.localizedName()localizedName() @selector("localizedName");
}
extern class (class) app.CALayerCALayer : (class) app.NSObjectNSObject
{
double double app.CALayer.contentsScale()contentsScale() @selector("contentsScale");
}
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.activateIgnoringOtherApps(bool flag)activateIgnoringOtherApps(bool (parameter) bool flagflag) @selector("activateIgnoringOtherApps:");
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
{
void void app.NSView.setFrameSize(app.NSSize newSize)setFrameSize((struct) app.NSSizeNSSize (parameter) app.NSSize newSizenewSize) @selector("setFrameSize:");
void void app.NSView.setNeedsDisplay(bool flag)setNeedsDisplay(bool (parameter) bool flagflag) @selector("setNeedsDisplay:");
(struct) app.NSRectNSRect app.NSRect app.NSView.bounds()bounds() @selector("bounds");
(struct) app.NSRectNSRect app.NSRect app.NSView.convertRectToBacking(app.NSRect rect)convertRectToBacking((struct) app.NSRectNSRect (parameter) app.NSRect rectrect) @selector("convertRectToBacking:");
(struct) app.NSRectNSRect app.NSRect app.NSView.convertRectFromBacking(app.NSRect rect)convertRectFromBacking((struct) app.NSRectNSRect (parameter) app.NSRect rectrect) @selector("convertRectFromBacking:");
void void app.NSView.setWantsLayer(bool flag)setWantsLayer(bool (parameter) bool flagflag) @selector("setWantsLayer:");
(class) app.CALayerCALayer app.CALayer app.NSView.layer()layer() @selector("layer");
(class) app.NSWindowNSWindow app.NSWindow app.NSView.window()window() @selector("window");
}
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:");
void void app.NSWindow.setContentSize(app.NSSize size)setContentSize((struct) app.NSSizeNSSize (parameter) app.NSSize sizesize) @selector("setContentSize:");
void void app.NSWindow.setFrameOrigin(app.NSPoint point)setFrameOrigin((struct) app.NSPointNSPoint (parameter) app.NSPoint pointpoint) @selector("setFrameOrigin:");
(struct) app.NSRectNSRect app.NSRect app.NSWindow.frame()frame() @selector("frame");
double double app.NSWindow.backingScaleFactor()backingScaleFactor() @selector("backingScaleFactor");
(class) app.NSScreenNSScreen app.NSScreen app.NSWindow.screen()screen() @selector("screen");
(struct) app.NSRectNSRect app.NSRect app.NSWindow.convertRectToBacking(app.NSRect rect)convertRectToBacking((struct) app.NSRectNSRect (parameter) app.NSRect rectrect) @selector("convertRectToBacking:");
}
// The instrumented view: scale-aware CPU blit + CTM logging + the mismatch probe.
class (class) app.ScaleViewScaleView : (class) app.NSViewNSView
{
static (class) app.ScaleViewScaleView app.ScaleView app.ScaleView.alloc()alloc() @selector("alloc");
(class) app.ScaleViewScaleView app.ScaleView app.ScaleView.initWithFrame(app.NSRect frame)initWithFrame((struct) app.NSRectNSRect (parameter) app.NSRect frameframe) @selector("initWithFrame:");
override void void app.ScaleView.setFrameSize(app.NSSize newSize)setFrameSize((struct) app.NSSizeNSSize (parameter) app.NSSize newSizenewSize) @selector("setFrameSize:")
{
super.super.setFrameSizesetFrameSize(newSize);
onViewResized(this, newSize);
}
// The scale-migration signal. Counted + timestamped: the only firing expected in
// this headless run is the 1.0 -> 2.0 flip inside setContentView:.
void void app.ScaleView.viewDidChangeBackingProperties()viewDidChangeBackingProperties() @selector("viewDidChangeBackingProperties")
{
++g_backingChanges;
(unresolved type) NSWindowNSWindow _error_ ww = this.this.windowwindow();
immutable _error_ scalescale = w !is null ? w.w.backingScaleFactorbackingScaleFactor() : 0.0;
immutable _error_ bb = this.this.convertRectToBackingconvertRectToBacking(NSRect(NSPoint(0, 0), NSSize(100, 100)));
instr("backing_changed", "n=%d window_scale=%.1f view_converts_100pt_to=%.0fpx phase=%s",
g_backingChanges, scale, b.b.sizesize.b.size.widthwidth, g_phase);
}
void void app.ScaleView.drawRect(app.NSRect dirtyRect)drawRect((struct) app.NSRectNSRect (parameter) app.NSRect dirtyRectdirtyRect) @selector("drawRect:")
{
onDrawRect(this, this.this.boundsbounds());
}
void void app.ScaleView.tick(app.NSTimer timer)tick((class) app.NSTimerNSTimer (parameter) app.NSTimer timertimer) @selector("tick:")
{
onTick(this);
}
// Would fire on any display configuration change (resolution/scale/arrangement,
// monitor plug/unplug) — the app-level runtime-rescale signal. Registration is
// the headless deliverable; an actual firing needs Tier C.
void void app.ScaleView.screenParamsChanged(app.NSNotification n)screenParamsChanged((class) app.NSNotificationNSNotification (parameter) app.NSNotification nn) @selector("screenParamsChanged:")
{
instr("screen_params_changed", "phase=%s", g_phase);
logScreens("notification");
}
// Window-level backing change notification; userInfo carries the OLD scale.
void void app.ScaleView.windowBackingChanged(app.NSNotification n)windowBackingChanged((class) app.NSNotificationNSNotification (parameter) app.NSNotification nn) @selector("windowBackingChanged:")
{
double _error_ oldScaleoldScale = 0;
(unresolved type) NSDictionaryNSDictionary _error_ infoinfo = n.n.userInfouserInfo();
if (info !is null)
{
auto _error_ numnum = cast((unresolved type) NSNumberNSNumber) info.info.objectForKeyobjectForKey(
cast((unresolved type) NSObjectNSObject) NSBackingPropertyOldScaleFactorKey);
if (num !is null)
oldScale = num.num.doubleValuedoubleValue();
}
instr("window_backing_changed", "old_scale=%.1f new_scale=%.1f phase=%s",
oldScale, g_win !is null ? g_win.g_win.backingScaleFactorbackingScaleFactor() : 0.0, g_phase);
}
void void app.ScaleView.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;
// --- 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 uint* (__gshared global) uint* app.g_bufg_buf;
__gshared (alias) object.size_t = ulongsize_t (__gshared global) ulong app.g_bufWg_bufW, (__gshared global) ulong app.g_bufHg_bufH;
__gshared int (__gshared global) int app.g_tickg_tick, (__gshared global) int app.g_framesg_frames, (__gshared global) int app.g_backingChangesg_backingChanges, (__gshared global) int app.g_mismatchesg_mismatches;
__gshared bool (__gshared global) bool app.g_autog_auto, (__gshared global) bool app.g_stopRequestedg_stopRequested, (__gshared global) bool app.g_firstConfigureSeeng_firstConfigureSeen;
__gshared bool (__gshared global) bool app.g_probeMismatchg_probeMismatch; // next drawRect uses a 1.0-scale (points-sized) buffer
__gshared (struct) app.CGAffineTransformCGAffineTransform (__gshared global) app.CGAffineTransform app.g_lastCtmg_lastCtm;
__gshared bool (__gshared global) bool app.g_ctmLoggedg_ctmLogged, (__gshared global) bool app.g_ctmVsBackingLoggedg_ctmVsBackingLogged;
__gshared const(char)* (__gshared global) const(char)* app.g_phaseg_phase = "setup";
// Schedule (ticks of a ~16 ms timer).
immutable int[2][3] (immutable global) immutable(int[2][3]) app.resizeSizesresizeSizes = [[640, 400], [333, 217], [480, 320]];
enum (constant) int app.resizeStartTick = 20resizeStartTick = 20;
enum (constant) int app.resizeStrideTicks = 8resizeStrideTicks = 8;
enum (constant) int app.mismatchProbeTick = 50mismatchProbeTick = 50;
enum (constant) int app.offscreenMoveTick = 60offscreenMoveTick = 60;
enum (constant) int app.moveBackTick = 70moveBackTick = 70;
enum (constant) int app.observerSelftestTick = 78observerSelftestTick = 78;
enum (constant) int app.autoExitTick = 85autoExitTick = 85;
void void app.renderGradient(uint* buf, ulong w, ulong h) nothrow @nogcrenderGradient(uint* (parameter) uint* bufbuf, (alias) object.size_t = ulongsize_t (parameter) ulong ww, (alias) object.size_t = ulongsize_t (parameter) ulong hh) nothrow @nogc
{
foreach ((parameter) yy; 0 .. h)
foreach ((parameter) xx; 0 .. w)
{
immutable _error_ rr = cast(uint)(x * 255 / w);
immutable _error_ gg = cast(uint)(y * 255 / h);
immutable _error_ bb = cast(uint)((x + y) * 255 / (w + h));
buf[y * w + x] = 0xFF00_0000 | (r << 16) | (g << 8) | b;
}
}
long long app.pixelsFor(double points, double scale) nothrow @nogcpixelsFor(double (parameter) double pointspoints, double (parameter) double scalescale) nothrow @nogc
{
return cast(long)(points * scale + 0.5);
}
void void app.logScreens(const(char)* when)logScreens(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("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();
(unresolved type) NSStringNSString _error_ namename = s.s.localizedNamelocalizedName();
instr("screen", "idx=%llu scale=%.1f frame=(%.0f,%.0f %.0fx%.0f) name=\"%s\"",
i, s.s.backingScaleFactorbackingScaleFactor(), f.f.originorigin.f.origin.xx, f.f.originorigin.f.origin.yy, f.f.sizesize.f.size.widthwidth,
f.f.sizesize.f.size.heightheight, name !is null ? name.name.UTF8StringUTF8String() : "");
}
(unresolved type) NSScreenNSScreen _error_ mainmain = NSScreen.NSScreen.mainScreenmainScreen();
if (main !is null)
instr("screen", "idx=main scale=%.1f", main.main.backingScaleFactorbackingScaleFactor());
}
// convertRectToBacking:/convertRectFromBacking: round-trips, incl. odd + fractional
// point sizes — the points <-> pixels unit conversion contract.
void void app.roundTrips(app.ScaleView view)roundTrips((class) app.ScaleViewScaleView (parameter) app.ScaleView viewview)
{
immutable double[2][4] _error_ sizessizes = [[480.0, 320.0], [333.0, 217.0], [100.5, 50.25],
[1.0, 1.0]];
foreach ((parameter) szsz; sizes)
{
immutable _error_ ptpt = NSRect(NSPoint(0, 0), NSSize(sz[0], sz[1]));
immutable _error_ pxpx = view.view.convertRectToBackingconvertRectToBacking(pt);
immutable _error_ backback = view.view.convertRectFromBackingconvertRectFromBacking(px);
immutable _error_ okok = back.back.sizesize.back.size.widthwidth == pt.pt.sizesize.pt.size.widthwidth && back.back.sizesize.back.size.heightheight == pt.pt.sizesize.pt.size.heightheight;
if (!ok)
++g_mismatches;
instr("round_trip", "points=%.2fx%.2f -> pixels=%.2fx%.2f -> points=%.2fx%.2f exact=%d",
pt.pt.sizesize.pt.size.widthwidth, pt.pt.sizesize.pt.size.heightheight, px.px.sizesize.px.size.widthwidth, px.px.sizesize.px.size.heightheight,
back.back.sizesize.back.size.widthwidth, back.back.sizesize.back.size.heightheight, ok ? 1 : 0);
}
// The window-level converter answers the same question pre-contentView.
immutable _error_ wpxwpx = g_win.g_win.convertRectToBackingconvertRectToBacking(NSRect(NSPoint(0, 0), NSSize(100, 100)));
instr("round_trip", "window_converts_100pt_to=%.0fpx", wpx.wpx.sizesize.wpx.size.widthwidth);
}
// Brief wantsLayer probe: what contentsScale does AppKit hand the backing layer?
void void app.layerProbe(app.ScaleView view)layerProbe((class) app.ScaleViewScaleView (parameter) app.ScaleView viewview)
{
instr("layer_probe", "stage=before layer=%s",
view.view.layerlayer() !is null ? "non-nil".ptr : "nil".ptr);
view.view.setWantsLayersetWantsLayer(true);
(unresolved type) CALayerCALayer _error_ layerlayer = view.view.layerlayer();
instr("layer_probe", "stage=wants_layer layer=%s contents_scale=%.1f",
layer !is null ? "non-nil".ptr : "nil".ptr,
layer !is null ? layer.layer.contentsScalecontentsScale() : 0.0);
view.view.setWantsLayersetWantsLayer(false);
instr("layer_probe", "stage=after_revert layer=%s",
view.view.layerlayer() !is null ? "non-nil".ptr : "nil".ptr);
}
void void app.onViewResized(app.ScaleView view, app.NSSize size)onViewResized((class) app.ScaleViewScaleView (parameter) app.ScaleView viewview, (struct) app.NSSizeNSSize (parameter) app.NSSize sizesize)
{
(unresolved type) NSWindowNSWindow _error_ ww = view.view.windowwindow();
immutable _error_ backingbacking = view.view.convertRectToBackingconvertRectToBacking(NSRect(NSPoint(0, 0), size));
immutable _error_ pwpw = cast(long) backing.backing.sizesize.backing.size.widthwidth;
immutable _error_ phph = cast(long) backing.backing.sizesize.backing.size.heightheight;
if (w is null)
{
// The first setFrameSize: fires inside setContentView: BEFORE the view's
// window link is established — informational, not a math failure.
instr(g_firstConfigureSeen ? "resize".ptr : "first_configure".ptr,
"points=%dx%d pixels=%lldx%lld window=nil",
cast(int) size.size.widthwidth, cast(int) size.size.heightheight, pw, ph);
}
else
{
immutable _error_ scalescale = w.w.backingScaleFactorbackingScaleFactor();
immutable _error_ matchmatch = pw == pixelsFor(size.size.widthwidth, scale)
&& ph == pixelsFor(size.size.heightheight, scale);
if (!match)
++g_mismatches;
instr(g_firstConfigureSeen ? "resize".ptr : "first_configure".ptr,
"points=%dx%d pixels=%lldx%lld scale=%.1f match=%d",
cast(int) size.size.widthwidth, cast(int) size.size.heightheight, pw, ph, scale, match ? 1 : 0);
}
g_firstConfigureSeen = true;
}
void void app.logCtmIfChanged(void* ctx, const(char)* when)logCtmIfChanged(void* (parameter) void* ctxctx, const(char)* (parameter) const(char)* whenwhen)
{
immutable _error_ ctmctm = CGContextGetCTM(ctx);
if (g_ctmLogged && ctm == g_lastCtm)
return;
g_lastCtm = ctm;
g_ctmLogged = true;
instr("ctm", "when=%s a=%.2f b=%.2f c=%.2f d=%.2f tx=%.2f ty=%.2f", when,
ctm.ctm.aa, ctm.ctm.bb, ctm.ctm.cc, ctm.ctm.dd, ctm.ctm.txtx, ctm.ctm.tyty);
}
void void app.onDrawRect(app.ScaleView view, app.NSRect bounds)onDrawRect((class) app.ScaleViewScaleView (parameter) app.ScaleView viewview, (struct) app.NSRectNSRect (parameter) app.NSRect boundsbounds)
{
void* _error_ ctxctx = NSGraphicsContext.NSGraphicsContext.currentContextcurrentContext().NSGraphicsContext.currentContext().CGContextCGContext();
logCtmIfChanged(ctx, "drawRect"); // THE ground truth of what AppKit pre-scales
(unresolved type) NSWindowNSWindow _error_ ww = view.view.windowwindow();
immutable _error_ scalescale = w !is null ? w.w.backingScaleFactorbackingScaleFactor() : 1.0;
if (!g_ctmVsBackingLogged && g_lastCtm.g_lastCtm.aa != scale)
{
g_ctmVsBackingLogged = true;
instr("ctm_vs_backing", "ctm_a=%.1f backing_scale=%.1f note=context_scale_disagrees_with_geometry_scale",
g_lastCtm.g_lastCtm.aa, scale);
}
immutable _error_ backingbacking = view.view.convertRectToBackingconvertRectToBacking(bounds);
// Buffer size: backing pixels normally; points (1.0-scale) for the mismatch probe.
immutable _error_ pwpw = g_probeMismatch ? cast((unresolved type) size_tsize_t) bounds.bounds.sizesize.bounds.size.widthwidth
: cast((unresolved type) size_tsize_t) backing.backing.sizesize.backing.size.widthwidth;
immutable _error_ phph = g_probeMismatch ? cast((unresolved type) size_tsize_t) bounds.bounds.sizesize.bounds.size.heightheight
: cast((unresolved type) size_tsize_t) backing.backing.sizesize.backing.size.heightheight;
if (pw == 0 || ph == 0)
return;
if (pw != g_bufW || ph != g_bufH)
{
free(g_buf);
g_buf = cast(uint*) malloc(pw * ph * 4);
g_bufW = pw;
g_bufH = ph;
instr("buffer_alloc", "size=%zux%zu bytes=%zu", pw, ph, pw * ph * 4);
}
renderGradient(g_buf, pw, ph);
void* _error_ cscs = CGColorSpaceCreateDeviceRGB();
void* _error_ dpdp = CGDataProviderCreateWithData(null, g_buf, pw * ph * 4, null);
void* _error_ imgimg = CGImageCreate(pw, ph, 8, 32, pw * 4, cs,
kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, dp, null, false,
kCGRenderingIntentDefault);
CGContextDrawImage(ctx, bounds, img);
if (g_probeMismatch)
{
// App-observable mismatch evidence: the CGImage is points-sized while the CTM
// maps the same points rect onto scale x as many device pixels -> Quartz
// resamples (stretches) the image x scale. On screen: blurry.
instr("mismatch_probe",
"img_px=%zux%zu rect_pt=%.0fx%.0f ctm_scale=%.1f device_px=%.0fx%.0f note=resampled_x%.1f_blurry",
CGImageGetWidth(img), CGImageGetHeight(img), bounds.bounds.sizesize.bounds.size.widthwidth,
bounds.bounds.sizesize.bounds.size.heightheight, g_lastCtm.g_lastCtm.aa, bounds.bounds.sizesize.bounds.size.widthwidth * g_lastCtm.g_lastCtm.aa,
bounds.bounds.sizesize.bounds.size.heightheight * g_lastCtm.g_lastCtm.aa, g_lastCtm.g_lastCtm.aa);
g_probeMismatch = false;
g_bufW = g_bufH = 0; // force a realloc back to backing size next frame
}
CGImageRelease(img);
CGDataProviderRelease(dp);
CGColorSpaceRelease(cs);
// Crisp 1-physical-px hairlines at the window edges: thickness 1/scale POINTS.
// With the CTM pre-scale (x scale) that is exactly one device pixel.
immutable _error_ hlhl = 1.0 / scale;
CGContextSetRGBFillColor(ctx, 1, 1, 1, 1);
CGContextFillRect(ctx, NSRect(NSPoint(0, 0), NSSize(bounds.bounds.sizesize.bounds.size.widthwidth, hl)));
CGContextFillRect(ctx, NSRect(NSPoint(0, bounds.bounds.sizesize.bounds.size.heightheight - hl),
NSSize(bounds.bounds.sizesize.bounds.size.widthwidth, hl)));
CGContextFillRect(ctx, NSRect(NSPoint(0, 0), NSSize(hl, bounds.bounds.sizesize.bounds.size.heightheight)));
CGContextFillRect(ctx, NSRect(NSPoint(bounds.bounds.sizesize.bounds.size.widthwidth - hl, 0),
NSSize(hl, bounds.bounds.sizesize.bounds.size.heightheight)));
if (g_frames == 0)
instr("hairline", "thickness_pt=%.2f device_px=%.1f", hl, hl * g_lastCtm.g_lastCtm.aa);
++g_frames;
}
void void app.onTick(app.ScaleView view)onTick((class) app.ScaleViewScaleView (parameter) app.ScaleView viewview)
{
++g_tick;
view.view.setNeedsDisplaysetNeedsDisplay(true);
if (!g_auto)
return; // interactive mode: keep the window up for a manual monitor drag
immutable _error_ rEndrEnd = resizeStartTick + cast(int) resizeSizes.length * resizeStrideTicks;
if (g_tick >= resizeStartTick && g_tick < rEnd
&& (g_tick - resizeStartTick) % resizeStrideTicks == 0)
{
immutable _error_ sizesize = resizeSizes[(g_tick - resizeStartTick) / resizeStrideTicks];
g_phase = "resize_storm";
instr("step", "name=setContentSize size=%dx%d", size[0], size[1]);
g_win.g_win.setContentSizesetContentSize(NSSize(size[0], size[1]));
}
else if (g_tick == mismatchProbeTick)
{
g_phase = "mismatch_probe";
instr("step", "name=mismatch_probe note=next_frame_draws_points_sized_buffer");
g_probeMismatch = true;
}
else if (g_tick == offscreenMoveTick)
{
// A hypothetical second screen: with none attached, what does AppKit do with
// an off-screen frame origin, and does any backing/screen change fire?
g_phase = "offscreen_move";
instr("step", "name=setFrameOrigin origin=(5000,100) note=no_second_screen");
g_win.g_win.setFrameOriginsetFrameOrigin(NSPoint(5000, 100));
immutable _error_ ff = g_win.g_win.frameframe();
(unresolved type) NSScreenNSScreen _error_ scrscr = g_win.g_win.screenscreen();
instr("offscreen_result", "frame=(%.0f,%.0f %.0fx%.0f) screen=%s scale=%.1f backing_changes=%d",
f.f.originorigin.f.origin.xx, f.f.originorigin.f.origin.yy, f.f.sizesize.f.size.widthwidth, f.f.sizesize.f.size.heightheight,
scr !is null ? "non-nil".ptr : "nil".ptr,
g_win.g_win.backingScaleFactorbackingScaleFactor(), g_backingChanges);
}
else if (g_tick == moveBackTick)
{
g_phase = "move_back";
instr("step", "name=setFrameOrigin origin=(120,120)");
g_win.g_win.setFrameOriginsetFrameOrigin(NSPoint(120, 120));
}
else if (g_tick == observerSelftestTick)
{
// Prove the notification observers are wired (nothing fires them headless):
// post the screen-params notification by hand and expect the handler to log.
g_phase = "observer_selftest";
instr("step", "name=post_didChangeScreenParameters note=selftest");
NSNotificationCenter.NSNotificationCenter.defaultCenterdefaultCenter().NSNotificationCenter.defaultCenter().postNotificationNamepostNotificationName(
cast((unresolved type) NSObjectNSObject) NSApplicationDidChangeScreenParametersNotification, null);
}
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_F08");
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);
g_app = NSApplication.NSApplication.sharedApplicationsharedApplication();
g_app.g_app.setActivationPolicysetActivationPolicy(NSApplicationActivationPolicyRegular);
logScreens("startup");
// --- created-at-wrong-scale timeline (timestamps are the instr() µs column) ----
immutable _error_ contentRectcontentRect = NSRect(NSPoint(120, 120), NSSize(480, 320));
instr("timeline", "t=window_init_call size=480x320");
g_win = NSWindow.NSWindow.allocalloc().NSWindow.alloc().initWithContentRectinitWithContentRect(contentRect,
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable,
NSBackingStoreBuffered, false);
(unresolved type) NSScreenNSScreen _error_ wscrwscr = g_win.g_win.screenscreen();
instr("timeline", "t=window_init_return window_scale=%.1f screen_scale=%.1f",
g_win.g_win.backingScaleFactorbackingScaleFactor(), wscr !is null ? wscr.wscr.backingScaleFactorbackingScaleFactor() : 0.0);
g_win.g_win.setTitlesetTitle(NSString.NSString.allocalloc().NSString.alloc().initWithUTF8StringinitWithUTF8String("wsi-f08-dpi"));
(unresolved type) ScaleViewScaleView _error_ viewview = ScaleView.ScaleView.allocalloc().ScaleView.alloc().initWithFrameinitWithFrame(contentRect);
immutable _error_ preInstallpreInstall = view.view.convertRectToBackingconvertRectToBacking(NSRect(NSPoint(0, 0), NSSize(100, 100)));
instr("timeline", "t=view_pre_install view_converts_100pt_to=%.0fpx window=%s",
preInstall.preInstall.sizesize.preInstall.size.widthwidth, view.view.windowwindow() !is null ? "non-nil".ptr : "nil".ptr);
// Notification registrations BEFORE setContentView: so any backing flip is caught.
(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("windowBackingChanged:"),
cast((unresolved type) NSObjectNSObject) NSWindowDidChangeBackingPropertiesNotification, null);
instr("step", "name=notification_observers registered=didChangeScreenParameters,windowDidChangeBackingProperties");
instr("timeline", "t=setContentView_call");
g_win.g_win.setContentViewsetContentView(view);
g_win.g_win.setDelegatesetDelegate(view);
instr("timeline", "t=setContentView_return backing_changes=%d", g_backingChanges);
instr("step", "name=makeKeyAndOrderFront");
g_win.g_win.makeKeyAndOrderFrontmakeKeyAndOrderFront(null);
g_app.g_app.activateIgnoringOtherAppsactivateIgnoringOtherApps(true);
instr("timeline", "t=after_order_front backing_changes=%d", g_backingChanges);
roundTrips(view);
layerProbe(view);
g_phase = "run";
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", "frames=%d ticks=%d backing_changes=%d mismatches=%d",
g_frames, g_tick, g_backingChanges, g_mismatches);
free(g_buf);
return g_mismatches == 0 ? 0 : 1;
}