// macOS/AppKit F16 demo — clipboard + drag-and-drop (../../../features/f16-clipboard-dnd.md).
// The pasteboard server (com.apple.pboard) is the negotiation peer; this demo logs
// every step of the format negotiation:
// COPY (eager): clearContents (changeCount bump = the ownership signal) +
// setString:"é漢🎈" forType:public.utf8-plain-text; verified cross-process by
// spawning `pbpaste` and diffing its bytes.
// COPY (lazy): declareTypes:owner: + pasteboard:provideDataForType: — the promise
// variant. The demo measures WHEN the promise is demanded (only when `pbpaste`
// actually reads, N ticks after the declare) and on which thread.
// Source-exits-first: run modes WSI_MODE=promise-exit-return / promise-exit-hard
// declare a promise and quit WITHOUT rendering it — the driver's pbpaste then
// shows who pays (run.sh / the findings doc record the outcome).
// PASTE: an external `pbcopy` write is detected by polling changeCount in the
// tick timer (there is NO change notification on macOS — polling is the only
// change signal); the demo then logs the offered types array, the chosen type,
// and the byte count. pasteboardChangedOwner: on the old owner logs the
// ownership-loss callback (or its absence).
// DnD: the content view is registerForDraggedTypes: (file URL + string) and
// implements the full NSDraggingDestination protocol (draggingEntered/Updated/
// Exited/performDragOperation, NSDragOperation masks logged); the source side
// starts beginDraggingSessionWithItems:event:source: (NSDraggingSource) with a
// synthetic mouse-down over its own view. Under A[ssh]+locked console the
// session may never deliver destination events — logged honestly (Tier C).
//
// Modes: WSI_AUTO_EXIT=1 = bounded scripted run (default for CI);
// WSI_MODE=promise-exit-return | promise-exit-hard = the unrendered-promise
// probes (no run loop; exits immediately after the declare).
// Headless-safe: prints `SKIP:` and exits 0 when no window server is reachable.
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, (alias) app.fopen = shared(core.stdc.stdio._IO_FILE)* core.stdc.stdio.fopen(scope const(char*) filename, scope const(char*) mode) nothrow @nogcfopen, (alias) app.fread = ulong core.stdc.stdio.fread(scope void* ptr, ulong size, ulong nmemb, shared(core.stdc.stdio._IO_FILE)* stream) nothrow @nogcfread, (alias) app.fclose = int core.stdc.stdio.fclose(shared(core.stdc.stdio._IO_FILE)* stream) nothrow @nogcfclose, (struct) core.stdc.stdio._IO_FILEFILE, (alias) app.snprintf = int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogcsnprintf;
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.system = int core.stdc.stdlib.system(scope const(char*) string) nothrow @nogcsystem;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.stringD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html, string.h
Source
core/stdc/string.d
string : (alias) app.strlen = ulong core.stdc.string.strlen(scope const(char*) s) pure nothrow @nogcstrlen, (alias) app.strcmp = int core.stdc.string.strcmp(scope const(char*) s1, scope const(char*) s2) pure nothrow @nogcstrcmp;
import instrument : instr, instrInit, instrNowUs;
import objc.autorelease : autoreleasepool_push, autoreleasepool_pop; // objective-d
import objc.rt : SEL;
// --- Geometry ----------------------------------------------------------------------
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;
}
// --- CoreGraphics / CoreFoundation / libc ------------------------------------------
extern (C) nothrow @nogc
{
uint uint app.CGMainDisplayID() nothrow @nogcCGMainDisplayID();
void void app.CFRelease(void* obj) nothrow @nogcCFRelease(void* (parameter) void* objobj);
void* void* app.CGSessionCopyCurrentDictionary() nothrow @nogcCGSessionCopyCurrentDictionary();
void* void* app.CFStringCreateWithCString(void* alloc, const(char)* s, uint encoding) nothrow @nogcCFStringCreateWithCString(void* (parameter) void* allocalloc, const(char)* (parameter) const(char)* ss, uint (parameter) uint encodingencoding);
void* void* app.CFDictionaryGetValue(void* dict, void* key) nothrow @nogcCFDictionaryGetValue(void* (parameter) void* dictdict, void* (parameter) void* keykey);
ubyte ubyte app.CFBooleanGetValue(void* b) nothrow @nogcCFBooleanGetValue(void* (parameter) void* bb);
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);
int int app.pthread_main_np() nothrow @nogcpthread_main_np();
void void app._exit(int code) nothrow @nogc_exit(int (parameter) int codecode);
}
enum uint (constant) uint app.kCFStringEncodingUTF8 = 134217984ukCFStringEncodingUTF8 = 0x08000100;
void void app.logSessionLockState()logSessionLockState()
{
void* (local variable) void* dictdict = void* app.CGSessionCopyCurrentDictionary() nothrow @nogcCGSessionCopyCurrentDictionary();
if ((local variable) void* dictdict is null)
{
instr("session", "lock_state=no_session_dictionary");
return;
}
void* (local variable) void* kLockedkLocked = void* app.CFStringCreateWithCString(void* alloc, const(char)* s, uint encoding) nothrow @nogcCFStringCreateWithCString(null, "CGSSessionScreenIsLocked",
(constant) uint app.kCFStringEncodingUTF8 = 134217984ukCFStringEncodingUTF8);
void* (local variable) void* lockedlocked = void* app.CFDictionaryGetValue(void* dict, void* key) nothrow @nogcCFDictionaryGetValue((local variable) void* dictdict, (local variable) void* kLockedkLocked);
instr("session", "screen_locked=%d",
locked !is null && CFBooleanGetValue(locked) ? 1 : 0);
void app.CFRelease(void* obj) nothrow @nogcCFRelease((local variable) void* kLockedkLocked);
void app.CFRelease(void* obj) nothrow @nogcCFRelease((local variable) void* dictdict);
}
// --- AppKit class declarations -----------------------------------------------------
extern (Objective-C):
extern class (class) app.NSObjectNSObject
{
// id<NSDraggingInfo> accessors — NSDraggingInfo is a protocol (no class object
// to link against), and Objective-C dispatch is by selector, so declaring the
// selectors on NSObject lets the destination methods message the info object.
(class) app.NSPasteboardNSPasteboard app.NSPasteboard app.NSObject.draggingPasteboard()draggingPasteboard() @selector("draggingPasteboard");
ulong ulong app.NSObject.draggingSourceOperationMask()draggingSourceOperationMask() @selector("draggingSourceOperationMask");
(struct) app.NSPointNSPoint app.NSPoint app.NSObject.draggingLocation()draggingLocation() @selector("draggingLocation");
}
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");
ulong ulong app.NSString.lengthOfBytesUsingEncoding(ulong enc)lengthOfBytesUsingEncoding(ulong (parameter) ulong encenc) @selector("lengthOfBytesUsingEncoding:");
}
extern class (class) app.NSArrayNSArray : (class) app.NSObjectNSObject
{
static (class) app.NSArrayNSArray app.NSArray app.NSArray.arrayWithObject(app.NSObject o)arrayWithObject((class) app.NSObjectNSObject (parameter) app.NSObject oo) @selector("arrayWithObject:");
ulong ulong app.NSArray.count()count() @selector("count");
(class) app.NSObjectNSObject app.NSObject app.NSArray.objectAtIndex(ulong i)objectAtIndex(ulong (parameter) ulong ii) @selector("objectAtIndex:");
}
extern class (class) app.NSMutableArrayNSMutableArray : (class) app.NSArrayNSArray
{
static (class) app.NSMutableArrayNSMutableArray app.NSMutableArray app.NSMutableArray.array()array() @selector("array");
void void app.NSMutableArray.addObject(app.NSObject o)addObject((class) app.NSObjectNSObject (parameter) app.NSObject oo) @selector("addObject:");
}
extern class (class) app.NSPasteboardNSPasteboard : (class) app.NSObjectNSObject
{
static (class) app.NSPasteboardNSPasteboard app.NSPasteboard app.NSPasteboard.generalPasteboard()generalPasteboard() @selector("generalPasteboard");
static (class) app.NSPasteboardNSPasteboard app.NSPasteboard app.NSPasteboard.pasteboardWithName(app.NSString name)pasteboardWithName((class) app.NSStringNSString (parameter) app.NSString namename) @selector("pasteboardWithName:");
(class) app.NSStringNSString app.NSString app.NSPasteboard.name()name() @selector("name");
long long app.NSPasteboard.changeCount()changeCount() @selector("changeCount");
long long app.NSPasteboard.clearContents()clearContents() @selector("clearContents");
bool bool app.NSPasteboard.setString(app.NSString s, app.NSString type)setString((class) app.NSStringNSString (parameter) app.NSString ss, (class) app.NSStringNSString (parameter) app.NSString typetype) @selector("setString:forType:");
(class) app.NSStringNSString app.NSString app.NSPasteboard.stringForType(app.NSString type)stringForType((class) app.NSStringNSString (parameter) app.NSString typetype) @selector("stringForType:");
(class) app.NSArrayNSArray app.NSArray app.NSPasteboard.types()types() @selector("types");
long long app.NSPasteboard.declareTypes(app.NSArray types, app.NSObject owner)declareTypes((class) app.NSArrayNSArray (parameter) app.NSArray typestypes, (class) app.NSObjectNSObject (parameter) app.NSObject ownerowner) @selector("declareTypes:owner:");
}
extern class (class) app.NSPasteboardItemNSPasteboardItem : (class) app.NSObjectNSObject
{
static (class) app.NSPasteboardItemNSPasteboardItem app.NSPasteboardItem app.NSPasteboardItem.alloc()alloc() @selector("alloc");
(class) app.NSPasteboardItemNSPasteboardItem app.NSPasteboardItem app.NSPasteboardItem.init()init() @selector("init");
bool bool app.NSPasteboardItem.setString(app.NSString s, app.NSString type)setString((class) app.NSStringNSString (parameter) app.NSString ss, (class) app.NSStringNSString (parameter) app.NSString typetype) @selector("setString:forType:");
}
extern class (class) app.NSDraggingItemNSDraggingItem : (class) app.NSObjectNSObject
{
static (class) app.NSDraggingItemNSDraggingItem app.NSDraggingItem app.NSDraggingItem.alloc()alloc() @selector("alloc");
(class) app.NSDraggingItemNSDraggingItem app.NSDraggingItem app.NSDraggingItem.initWithPasteboardWriter(app.NSObject writer)initWithPasteboardWriter((class) app.NSObjectNSObject (parameter) app.NSObject writerwriter)
@selector("initWithPasteboardWriter:");
void void app.NSDraggingItem.setDraggingFrame(app.NSRect frame, app.NSObject contents)setDraggingFrame((struct) app.NSRectNSRect (parameter) app.NSRect frameframe, (class) app.NSObjectNSObject (parameter) app.NSObject contentscontents)
@selector("setDraggingFrame:contents:");
}
extern class (class) app.NSDraggingSessionNSDraggingSession : (class) app.NSObjectNSObject
{
long long app.NSDraggingSession.draggingSequenceNumber()draggingSequenceNumber() @selector("draggingSequenceNumber");
}
extern class (class) app.NSEventNSEvent : (class) app.NSObjectNSObject
{
static (class) app.NSEventNSEvent app.NSEvent app.NSEvent.mouseEventWithType(long type, app.NSPoint location, ulong modifierFlags, double timestamp, long windowNumber, app.NSObject context, long eventNumber, long clickCount, double pressure)mouseEventWithType(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, long (parameter) long eventNumbereventNumber,
long (parameter) long clickCountclickCount, double (parameter) double pressurepressure)
@selector("mouseEventWithType:location:modifierFlags:timestamp:windowNumber:context:eventNumber:clickCount:pressure:");
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:");
long long app.NSEvent.type()type() @selector("type");
}
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 userInfo, bool repeats)
@selector("scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:");
}
extern class (class) app.NSApplicationNSApplication : (class) app.NSObjectNSObject
{
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.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.NSViewNSView : (class) app.NSObjectNSObject
{
(struct) app.NSRectNSRect app.NSRect app.NSView.bounds()bounds() @selector("bounds");
void void app.NSView.registerForDraggedTypes(app.NSArray types)registerForDraggedTypes((class) app.NSArrayNSArray (parameter) app.NSArray typestypes) @selector("registerForDraggedTypes:");
(class) app.NSWindowNSWindow app.NSWindow app.NSView.window()window() @selector("window");
(class) app.NSDraggingSessionNSDraggingSession app.NSDraggingSession app.NSView.beginDraggingSession(app.NSArray items, app.NSEvent event, app.NSObject source)beginDraggingSession((class) app.NSArrayNSArray (parameter) app.NSArray itemsitems, (class) app.NSEventNSEvent (parameter) app.NSEvent eventevent, (class) app.NSObjectNSObject (parameter) app.NSObject sourcesource)
@selector("beginDraggingSessionWithItems:event:source:");
}
extern class (class) app.NSWindowNSWindow : (class) app.NSObjectNSObject
{
(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.NSObject view)setContentView((class) app.NSObjectNSObject (parameter) app.NSObject viewview) @selector("setContentView:");
void void app.NSWindow.setReleasedWhenClosed(bool b)setReleasedWhenClosed(bool (parameter) bool bb) @selector("setReleasedWhenClosed:");
void void app.NSWindow.makeKeyAndOrderFront(app.NSObject sender)makeKeyAndOrderFront((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("makeKeyAndOrderFront:");
long long app.NSWindow.windowNumber()windowNumber() @selector("windowNumber");
}
class (class) app.PlainWindowPlainWindow : (class) app.NSWindowNSWindow
{
static (class) app.PlainWindowPlainWindow app.PlainWindow app.PlainWindow.alloc()alloc() @selector("alloc");
}
// Pasteboard owner: the lazy-promise provider + the ownership-loss callback;
// also the NSDraggingSource for the DnD attempt.
class (class) app.PBOwnerPBOwner : (class) app.NSObjectNSObject
{
static (class) app.PBOwnerPBOwner app.PBOwner app.PBOwner.alloc()alloc() @selector("alloc");
(class) app.PBOwnerPBOwner app.PBOwner app.PBOwner.init()init() @selector("init");
// The promise is demanded HERE — only when a reader actually asks for the type.
void void app.PBOwner.pasteboardProvideDataForType(app.NSPasteboard pb, app.NSString type)pasteboardProvideDataForType((class) app.NSPasteboardNSPasteboard (parameter) app.NSPasteboard pbpb, (class) app.NSStringNSString (parameter) app.NSString typetype)
@selector("pasteboard:provideDataForType:")
{
++g_provideCalls;
instr("clip_request", "fmt=%s lazy=1 main_thread=%d declare_to_demand_us=%llu",
type.type.UTF8StringUTF8String(), pthread_main_np(), instrNowUs() - g_declareUs);
pb.pb.setStringsetString(nsstr(kPayload), type); // render the promise
instr("clip_send", "bytes=%d fmt=%s lazy=1", cast(int) strlen(kPayload),
type.type.UTF8StringUTF8String());
}
// Old owner's loss signal (clipboard taken by another app). HAZARD, measured:
// AppKit delivers this from INSIDE -[NSPasteboard changeCount] (the poll), and
// calling changeCount here re-enters handleOwnershipChange → unbounded
// recursion → stack-overflow SIGSEGV. Do not touch the pasteboard here.
void void app.PBOwner.pasteboardChangedOwner(app.NSPasteboard pb)pasteboardChangedOwner((class) app.NSPasteboardNSPasteboard (parameter) app.NSPasteboard pbpb) @selector("pasteboardChangedOwner:")
{
++g_ownerLost;
instr("ownership_lost", "callback=pasteboardChangedOwner main_thread=%d n=%d",
pthread_main_np(), g_ownerLost);
}
// NSDraggingSource (required method).
ulong ulong app.PBOwner.sourceOperationMask(app.NSDraggingSession session, long context)sourceOperationMask((class) app.NSDraggingSessionNSDraggingSession (parameter) app.NSDraggingSession sessionsession, long (parameter) long contextcontext)
@selector("draggingSession:sourceOperationMaskForDraggingContext:")
{
instr("dnd_source", "event=sourceOperationMask context=%ld mask=copy", context);
return 1; // NSDragOperationCopy
}
void void app.PBOwner.sessionWillBegin(app.NSDraggingSession session, app.NSPoint p)sessionWillBegin((class) app.NSDraggingSessionNSDraggingSession (parameter) app.NSDraggingSession sessionsession, (struct) app.NSPointNSPoint (parameter) app.NSPoint pp)
@selector("draggingSession:willBeginAtPoint:")
{
instr("dnd_source", "event=willBeginAtPoint loc=(%.0f,%.0f)", p.p.xx, p.p.yy);
}
void void app.PBOwner.sessionEnded(app.NSDraggingSession session, app.NSPoint p, ulong op)sessionEnded((class) app.NSDraggingSessionNSDraggingSession (parameter) app.NSDraggingSession sessionsession, (struct) app.NSPointNSPoint (parameter) app.NSPoint pp, ulong (parameter) ulong opop)
@selector("draggingSession:endedAtPoint:operation:")
{
instr("dnd_source", "event=endedAtPoint loc=(%.0f,%.0f) operation=%lu", p.p.xx, p.p.yy, op);
}
void void app.PBOwner.tick(app.NSTimer t)tick((class) app.NSTimerNSTimer (parameter) app.NSTimer tt) @selector("tick:")
{
onStep();
}
}
// The drop target: full NSDraggingDestination protocol, every callback logged.
class (class) app.DropViewDropView : (class) app.NSViewNSView
{
static (class) app.DropViewDropView app.DropView app.DropView.alloc()alloc() @selector("alloc");
(class) app.DropViewDropView app.DropView app.DropView.initWithFrame(app.NSRect frame)initWithFrame((struct) app.NSRectNSRect (parameter) app.NSRect frameframe) @selector("initWithFrame:");
void void app.DropView.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.2, 0.25, 1);
CGContextFillRect(ctx, this.this.boundsbounds());
}
ulong ulong app.DropView.draggingEntered(app.NSObject info)draggingEntered((class) app.NSObjectNSObject (parameter) app.NSObject infoinfo) @selector("draggingEntered:")
{
++g_dndEntered;
logOffer("dnd_enter", info.info.draggingPasteboarddraggingPasteboard());
instr("dnd_enter", "source_mask=0x%lx loc=(%.0f,%.0f) accept=copy",
info.info.draggingSourceOperationMaskdraggingSourceOperationMask(), info.info.draggingLocationdraggingLocation().info.draggingLocation().xx,
info.info.draggingLocationdraggingLocation().info.draggingLocation().yy);
return 1; // NSDragOperationCopy
}
ulong ulong app.DropView.draggingUpdated(app.NSObject info)draggingUpdated((class) app.NSObjectNSObject (parameter) app.NSObject infoinfo) @selector("draggingUpdated:")
{
++g_dndUpdated;
if (g_dndUpdated <= 3)
instr("dnd_update", "loc=(%.0f,%.0f) accept=copy",
info.info.draggingLocationdraggingLocation().info.draggingLocation().xx, info.info.draggingLocationdraggingLocation().info.draggingLocation().yy);
return 1;
}
void void app.DropView.draggingExited(app.NSObject info)draggingExited((class) app.NSObjectNSObject (parameter) app.NSObject infoinfo) @selector("draggingExited:")
{
instr("dnd_exit", "");
}
bool bool app.DropView.prepareForDragOperation(app.NSObject info)prepareForDragOperation((class) app.NSObjectNSObject (parameter) app.NSObject infoinfo)
@selector("prepareForDragOperation:")
{
instr("dnd_prepare", "accept=1");
return true;
}
bool bool app.DropView.performDragOperation(app.NSObject info)performDragOperation((class) app.NSObjectNSObject (parameter) app.NSObject infoinfo) @selector("performDragOperation:")
{
++g_dndDropped;
auto _error_ pbpb = info.info.draggingPasteboarddraggingPasteboard();
logOffer("dnd_drop_offer", pb);
(unresolved type) NSStringNSString _error_ urlurl = pb.pb.stringForTypestringForType(nsstr("public.file-url"));
if (url !is null)
instr("dnd_drop", "fmt=public.file-url bytes=%lu data=%s",
url.url.lengthOfBytesUsingEncodinglengthOfBytesUsingEncoding(4), url.url.UTF8StringUTF8String());
(unresolved type) NSStringNSString _error_ ss = pb.pb.stringForTypestringForType(nsstr("public.utf8-plain-text"));
if (s !is null)
instr("dnd_drop", "fmt=public.utf8-plain-text bytes=%lu data=%s",
s.s.lengthOfBytesUsingEncodinglengthOfBytesUsingEncoding(4), s.s.UTF8StringUTF8String());
return true;
}
void void app.DropView.concludeDragOperation(app.NSObject info)concludeDragOperation((class) app.NSObjectNSObject (parameter) app.NSObject infoinfo)
@selector("concludeDragOperation:")
{
instr("dnd_conclude", "");
}
}
extern (D): // back to D linkage
enum (constant) int app.NSApplicationActivationPolicyRegular = 0NSApplicationActivationPolicyRegular = 0;
enum ulong (constant) ulong app.NSWindowStyleMaskTitled = 1LUNSWindowStyleMaskTitled = 1;
enum ulong (constant) ulong app.NSBackingStoreBuffered = 2LUNSBackingStoreBuffered = 2;
enum long (constant) long app.NSEventTypeLeftMouseDown = 1LNSEventTypeLeftMouseDown = 1;
enum long (constant) long app.NSEventTypeApplicationDefined = 15LNSEventTypeApplicationDefined = 15;
enum ulong (constant) ulong app.NSUTF8StringEncoding = 4LUNSUTF8StringEncoding = 4;
// The non-ASCII payload required by the spec: 2 + 3 + 4 UTF-8 bytes.
__gshared const(char)* (__gshared global) const(char)* app.kPayloadkPayload = "é漢🎈";
__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 (class) app.DropViewDropView (__gshared global) app.DropView app.g_viewg_view;
__gshared (class) app.PBOwnerPBOwner (__gshared global) app.PBOwner app.g_ownerg_owner;
__gshared (class) app.NSPasteboardNSPasteboard (__gshared global) app.NSPasteboard app.g_pbg_pb;
__gshared bool (__gshared global) bool app.g_autog_auto;
__gshared int (__gshared global) int app.g_stepg_step;
__gshared long (__gshared global) long app.g_lastSeenCCg_lastSeenCC; // the changeCount poll (the only change signal)
__gshared bool (__gshared global) bool app.g_pollArmedg_pollArmed;
__gshared int (__gshared global) int app.g_provideCallsg_provideCalls;
__gshared int (__gshared global) int app.g_ownerLostg_ownerLost;
__gshared ulong (__gshared global) ulong app.g_declareUsg_declareUs;
__gshared int (__gshared global) int app.g_dndEnteredg_dndEntered, (__gshared global) int app.g_dndUpdatedg_dndUpdated, (__gshared global) int app.g_dndDroppedg_dndDropped;
__gshared (class) app.NSDraggingSessionNSDraggingSession (__gshared global) app.NSDraggingSession app.g_sessiong_session;
(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);
}
void void app.logOffer(const(char)* kind, app.NSPasteboard pb)logOffer(const(char)* (parameter) const(char)* kindkind, (class) app.NSPasteboardNSPasteboard (parameter) app.NSPasteboard pbpb)
{
auto _error_ typestypes = pb.pb.typestypes();
immutable _error_ nn = types !is null ? types.types.countcount() : 0;
char[1024] _error_ bufbuf = 0;
size_t _error_ offoff = 0;
foreach ((parameter) ii; 0 .. n)
{
auto _error_ tt = (cast((unresolved type) NSStringNSString) types.types.objectAtIndexobjectAtIndex(i)).(cast(NSString)types.objectAtIndex(i)).UTF8StringUTF8String();
off += snprintf(buf.ptr + off, buf.length - off, "%s%s",
i ? ",".ptr : "".ptr, t);
if (off >= buf.length - 1)
break;
}
instr(kind, "change_count=%ld formats=[%s] n=%lu", pb.pb.changeCountchangeCount(), buf.ptr, n);
}
// Read a small file written by a background pbpaste; returns bytes read.
int int app.readBack(const(char)* path, char[] sink)readBack(const(char)* (parameter) const(char)* pathpath, char[] (parameter) char[] sinksink)
{
(unresolved type) FILEFILE* _error_ ff = fopen(path, "rb");
if (f is null)
return -1;
immutable _error_ nn = cast(int) fread(sink.ptr, 1, sink.length - 1, f);
fclose(f);
sink[n < 0 ? 0 : n] = 0;
return n;
}
void void app.stopApp()stopApp()
{
instr("step", "name=NSApp_stop");
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);
}
// --- The scripted run (one step per 300 ms tick) ------------------------------------
void void app.onStep()onStep()
{
// The changeCount poll runs on every tick: this IS the macOS clipboard event
// model — NSPasteboard posts no notification on change; readers poll.
if (g_pollArmed)
{
immutable _error_ cccc = g_pb.g_pb.changeCountchangeCount();
if (cc != g_lastSeenCC)
{
instr("clip_external_change", "detected_by=changeCount_poll old=%ld new=%ld",
g_lastSeenCC, cc);
g_lastSeenCC = cc;
}
}
if (!g_auto)
return;
switch (g_step)
{
case 0: // Baseline.
g_lastSeenCC = g_pb.g_pb.changeCountchangeCount();
instr("step", "name=baseline pb=%s change_count=%ld",
g_pb.g_pb.namename().g_pb.name().UTF8StringUTF8String(), g_lastSeenCC);
break;
case 1: // COPY, eager: clearContents bump + setString.
{
immutable _error_ cc0cc0 = g_pb.g_pb.changeCountchangeCount();
immutable _error_ cc1cc1 = g_pb.g_pb.clearContentsclearContents();
immutable _error_ okok = g_pb.g_pb.setStringsetString(nsstr(kPayload), nsstr("public.utf8-plain-text"));
immutable _error_ cc2cc2 = g_pb.g_pb.changeCountchangeCount();
instr("clip_copy", "mode=eager fmt=public.utf8-plain-text bytes=%d ok=%d change_count=%ld->%ld(clear)->%ld",
cast(int) strlen(kPayload), ok ? 1 : 0, cc0, cc1, cc2);
g_lastSeenCC = cc2;
// Cross-process verification: pbpaste in another process, async so the
// run loop keeps servicing the pasteboard server.
system("/bin/sh -c 'LANG=en_US.UTF-8 pbpaste > /tmp/wsi-m8/f16-eager.txt 2>&1 &'");
}
break;
case 2: // Read pbpaste's harvest back.
{
char[256] _error_ sinksink;
immutable _error_ nn = readBack("/tmp/wsi-m8/f16-eager.txt", sink);
instr("clip_verify", "via=pbpaste mode=eager bytes=%d data=%s match=%d",
n, sink.ptr, n == cast(int) strlen(kPayload)
&& strcmp(sink.ptr, kPayload) == 0 ? 1 : 0);
}
break;
case 3: // COPY, lazy: declareTypes:owner: — the promise.
{
immutable _error_ cc0cc0 = g_pb.g_pb.changeCountchangeCount();
auto _error_ typestypes = NSMutableArray.NSMutableArray.arrayarray();
types.types.addObjectaddObject(nsstr("public.utf8-plain-text"));
immutable _error_ cc1cc1 = g_pb.g_pb.declareTypesdeclareTypes(types, g_owner);
g_declareUs = instrNowUs();
instr("clip_offer", "mode=lazy formats=[public.utf8-plain-text] change_count=%ld->%ld provider_called=%d",
cc0, cc1, g_provideCalls);
g_lastSeenCC = g_pb.g_pb.changeCountchangeCount();
}
break;
case 4: // One full tick later: still not demanded? Then unleash the reader.
instr("clip_lazy", "provider_called=%d note=no_reader_yet", g_provideCalls);
system("/bin/sh -c 'LANG=en_US.UTF-8 pbpaste > /tmp/wsi-m8/f16-lazy.txt 2>&1 &'");
break;
case 5: // The promise should have been demanded by pbpaste by now.
{
char[256] _error_ sinksink;
immutable _error_ nn = readBack("/tmp/wsi-m8/f16-lazy.txt", sink);
instr("clip_verify", "via=pbpaste mode=lazy bytes=%d data=%s provider_called=%d match=%d",
n, sink.ptr, g_provideCalls,
n == cast(int) strlen(kPayload) && strcmp(sink.ptr, kPayload) == 0 ? 1 : 0);
}
break;
case 6: // Ownership loss: an external pbcopy takes the board from us.
instr("step", "name=external_pbcopy_takeover promise_state=fulfilled");
system("/bin/sh -c \"printf 'external-Ω' | LANG=en_US.UTF-8 pbcopy &\"");
g_pollArmed = true; // detection = changeCount poll (top of tick)
break;
case 7: // (poll fires above) — nothing; give the takeover a full tick.
break;
case 8: // PASTE: offered types first, then the chosen one + byte count.
{
logOffer("clip_offer_read", g_pb);
(unresolved type) NSStringNSString _error_ ss = g_pb.g_pb.stringForTypestringForType(nsstr("public.utf8-plain-text"));
if (s !is null)
instr("clip_paste", "fmt=public.utf8-plain-text bytes=%lu data=%s ownership_lost_callbacks=%d",
s.s.lengthOfBytesUsingEncodinglengthOfBytesUsingEncoding(NSUTF8StringEncoding),
s.s.UTF8StringUTF8String(), g_ownerLost);
else
instr("clip_paste", "fmt=public.utf8-plain-text result=nil");
}
break;
case 9: // Ownership loss with an UNFULFILLED promise: declare lazy again …
{
auto _error_ typestypes = NSMutableArray.NSMutableArray.arrayarray();
types.types.addObjectaddObject(nsstr("public.utf8-plain-text"));
immutable _error_ cccc = g_pb.g_pb.declareTypesdeclareTypes(types, g_owner);
instr("clip_offer", "mode=lazy_unfulfilled change_count=%ld ownership_lost_callbacks=%d",
cc, g_ownerLost);
g_lastSeenCC = g_pb.g_pb.changeCountchangeCount();
}
break;
case 10: // … and let pbcopy take the board while the promise is pending.
instr("step", "name=external_pbcopy_takeover promise_state=unfulfilled");
system("/bin/sh -c \"printf 'takeover2' | LANG=en_US.UTF-8 pbcopy &\"");
break;
case 11: // Did pasteboardChangedOwner: fire this time?
instr("ownership_probe", "callbacks=%d provider_calls=%d note=takeover_with_pending_promise",
g_ownerLost, g_provideCalls);
break;
case 12: // DnD: in-process drag attempt over our own registered view.
{
system("/bin/sh -c 'echo wsi-f16-drop-payload > /tmp/wsi-m8/f16-drop.txt'");
auto _error_ itemitem = NSPasteboardItem.NSPasteboardItem.allocalloc().NSPasteboardItem.alloc().initinit();
item.item.setStringsetString(nsstr("file:///tmp/wsi-m8/f16-drop.txt"), nsstr("public.file-url"));
item.item.setStringsetString(nsstr(kPayload), nsstr("public.utf8-plain-text"));
auto _error_ dragdrag = NSDraggingItem.NSDraggingItem.allocalloc().NSDraggingItem.alloc().initWithPasteboardWriterinitWithPasteboardWriter(item);
drag.drag.setDraggingFramesetDraggingFrame(NSRect(NSPoint(100, 100), NSSize(32, 32)), null);
(unresolved type) NSEventNSEvent _error_ downdown = NSEvent.NSEvent.mouseEventWithTypemouseEventWithType(NSEventTypeLeftMouseDown,
NSPoint(120, 120), 0, 0, g_win.g_win.windowNumberwindowNumber(), null, 0, 1, 0);
instr("dnd_begin", "api=beginDraggingSessionWithItems types=[public.file-url,public.utf8-plain-text]");
g_session = g_view.g_view.beginDraggingSessionbeginDraggingSession(NSArray.NSArray.arrayWithObjectarrayWithObject(drag),
down, g_owner);
instr("dnd_session", "started=%d seq=%ld", g_session !is null ? 1 : 0,
g_session !is null ? g_session.g_session.draggingSequenceNumberdraggingSequenceNumber() : -1);
}
break;
case 13: // Inspect the DRAG pasteboard the session populated.
case 14:
break;
case 15:
{
auto _error_ dragPbdragPb = NSPasteboard.NSPasteboard.pasteboardWithNamepasteboardWithName(nsstr("Apple CFPasteboard drag"));
logOffer("dnd_drag_pasteboard", dragPb);
(unresolved type) NSStringNSString _error_ urlurl = dragPb.dragPb.stringForTypestringForType(nsstr("public.file-url"));
instr("dnd_drag_pasteboard", "file_url=%s",
url !is null ? url.url.UTF8StringUTF8String() : "nil");
instr("dnd_summary", "entered=%d updated=%d dropped=%d note=%s",
g_dndEntered, g_dndUpdated, g_dndDropped,
g_dndEntered == 0
? "no_destination_events_headless_locked_session_tierC".ptr
: "destination_protocol_exercised".ptr);
}
break;
case 16:
instr("summary", "provider_calls=%d ownership_lost=%d dnd_entered=%d dnd_dropped=%d",
g_provideCalls, g_ownerLost, g_dndEntered, g_dndDropped);
stopApp();
break;
default:
stopApp();
break;
}
++g_step;
}
// --- Promise-exit modes: declare, then die with the promise unrendered ---------------
int int app.runPromiseExit(bool hard)runPromiseExit(bool (parameter) bool hardhard)
{
g_pb = NSPasteboard.NSPasteboard.generalPasteboardgeneralPasteboard();
auto _error_ typestypes = NSMutableArray.NSMutableArray.arrayarray();
types.types.addObjectaddObject(nsstr("public.utf8-plain-text"));
immutable _error_ cccc = g_pb.g_pb.declareTypesdeclareTypes(types, g_owner);
instr("clip_offer", "mode=promise_exit formats=[public.utf8-plain-text] change_count=%ld provider_called=%d",
cc, g_provideCalls);
if (hard)
{
instr("exit", "kind=_exit_2 provider_called=%d note=promise_unrendered", g_provideCalls);
_exit(2); // no atexit, no ObjC teardown — the source vanishes
}
instr("exit", "kind=return_from_main provider_called=%d", g_provideCalls);
return 0; // normal exit: does anything render the promise on the way out?
}
int int D main()main()
{
g_auto = getenv("WSI_AUTO_EXIT") !is null;
const(char)* _error_ modemode = getenv("WSI_MODE");
instrInit("APPKIT_F16");
instr("init_start", "auto_exit=%d mode=%s", g_auto ? 1 : 0,
mode !is null ? mode : "scripted");
if (CGMainDisplayID() == 0)
{
printf("SKIP: no macOS window server (CGMainDisplayID == 0)\n");
return 0;
}
logSessionLockState();
auto _error_ poolpool = autoreleasepool_push();
scope (exit)
autoreleasepool_pop(pool);
g_app = NSApplication.NSApplication.sharedApplicationsharedApplication();
g_app.g_app.setActivationPolicysetActivationPolicy(NSApplicationActivationPolicyRegular);
g_owner = PBOwner.PBOwner.allocalloc().PBOwner.alloc().initinit();
if (mode !is null && strcmp(mode, "promise-exit-return") == 0)
return runPromiseExit(false);
if (mode !is null && strcmp(mode, "promise-exit-hard") == 0)
return runPromiseExit(true);
immutable _error_ contentRectcontentRect = NSRect(NSPoint(120, 120), NSSize(480, 320));
g_win = PlainWindow.PlainWindow.allocalloc().PlainWindow.alloc().initWithContentRectinitWithContentRect(contentRect,
NSWindowStyleMaskTitled, NSBackingStoreBuffered, false);
g_win.g_win.setTitlesetTitle(nsstr("wsi-f16-clipboard-dnd"));
g_win.g_win.setReleasedWhenClosedsetReleasedWhenClosed(false);
g_view = DropView.DropView.allocalloc().DropView.alloc().initWithFrameinitWithFrame(NSRect(NSPoint(0, 0), contentRect.contentRect.sizesize));
auto _error_ dndTypesdndTypes = NSMutableArray.NSMutableArray.arrayarray();
dndTypes.dndTypes.addObjectaddObject(nsstr("public.file-url"));
dndTypes.dndTypes.addObjectaddObject(nsstr("public.utf8-plain-text"));
g_view.g_view.registerForDraggedTypesregisterForDraggedTypes(dndTypes);
instr("dnd_register", "types=[public.file-url,public.utf8-plain-text]");
g_win.g_win.setContentViewsetContentView(g_view);
g_win.g_win.makeKeyAndOrderFrontmakeKeyAndOrderFront(null);
instr("window_created", "win_num=%ld", g_win.g_win.windowNumberwindowNumber());
g_pb = NSPasteboard.NSPasteboard.generalPasteboardgeneralPasteboard();
NSTimer.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(0.3, g_owner, SEL.register("tick:"), null, true);
instr("step", "name=NSApp_run");
g_app.g_app.runrun();
instr("loop_exit", "steps=%d", g_step);
return 0;
}