// macOS/AppKit F10 demo — pointer lock / confine / relative motion
// (../../../features/f10-pointer-capture.md).
// The mouselook problem on macOS is assembled from three independent pieces:
// stop the cursor (CGAssociateMouseAndMouseCursorPosition(false)), hide it
// (NSCursor.hide — app-scoped counter — vs CGDisplayHideCursor — per-display),
// and read relative motion (NSEvent.deltaX/deltaY on mouseMoved:, which keeps
// flowing while the cursor is pinned). There is NO public confine-to-rect API;
// this demo implements the warp-back workaround and logs its artifacts.
//
// What it logs (A[ssh]; locked console — no real pointer, so motion is driven by
// synthetic NSEvents dispatched in-process, the f06/f07 route-1 workhorse):
// - lock enter/exit: CGAssociateMouseAndMouseCursorPosition return value (and
// the absence of any read-back getter), NSCursor.hide/unhide pairing,
// saved-position capture + CGWarpMouseCursorPosition restore on exit;
// - the two ways to build a delta-bearing synthetic mouseMoved:
// a. +[NSEvent mouseEventWithType:...] — the constructor has NO delta
// parameters; the wrapped event's deltaX/deltaY are the probe;
// b. CGEventCreateMouseEvent + CGEventSetIntegerValueField(
// kCGMouseEventDeltaX/Y) wrapped via +[NSEvent eventWithCGEvent:] —
// read back at the CG layer, at the wrapped-NSEvent layer, and at the
// view's mouseMoved: (whatever delta survives each hop is the finding);
// - CGWarpMouseCursorPosition: CGError + CGEventCreate location read-back
// (does the warp move the CG-side pointer under a locked session?), whether
// any mouseMoved is generated by the warp, and the classic suppression
// interval (CGEventSourceGet/SetLocalEventsSuppressionInterval);
// - CGDisplayHideCursor/CGDisplayShowCursor returns, contrasted with the
// app-scoped NSCursor counter;
// - the confine workaround: a center-half confine rect, scripted motion that
// leaves it, warp-back + the artifact (the out-of-bounds event is already
// delivered before the warp can react);
// - a crosshair/angle readout (yaw/pitch integrated from deltas) drawn in
// drawRect: (visible pixels are Tier C; the numbers are in the log).
//
// Modes: WSI_AUTO_EXIT=1 = bounded scripted run; without it the window stays up
// and a mouse click toggles mouselook for a manual (Tier C) 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;
// --- Geometry (NSPoint/NSRect are layout-identical to CGPoint/CGRect) ------------
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 C API -----------------------------------------------------------
extern (C) nothrow @nogc
{
uint uint app.CGMainDisplayID() nothrow @nogcCGMainDisplayID();
(struct) app.NSRectNSRect app.NSRect app.CGDisplayBounds(uint display) nothrow @nogcCGDisplayBounds(uint (parameter) uint displaydisplay);
// The lock trio: dissociate, warp, hide (display-scoped variant).
int int app.CGAssociateMouseAndMouseCursorPosition(uint connected) nothrow @nogcCGAssociateMouseAndMouseCursorPosition(uint (parameter) uint connectedconnected); // CGError
int int app.CGWarpMouseCursorPosition(app.NSPoint newPosition) nothrow @nogcCGWarpMouseCursorPosition((struct) app.NSPointNSPoint (parameter) app.NSPoint newPositionnewPosition); // CGError
int int app.CGDisplayHideCursor(uint display) nothrow @nogcCGDisplayHideCursor(uint (parameter) uint displaydisplay); // CGError
int int app.CGDisplayShowCursor(uint display) nothrow @nogcCGDisplayShowCursor(uint (parameter) uint displaydisplay); // CGError
// Pointer-position read-back + synthetic mouse events with delta fields.
void* void* app.CGEventCreate(void* source) nothrow @nogcCGEventCreate(void* (parameter) void* sourcesource);
(struct) app.NSPointNSPoint app.NSPoint app.CGEventGetLocation(void* event) nothrow @nogcCGEventGetLocation(void* (parameter) void* eventevent);
void* void* app.CGEventCreateMouseEvent(void* source, uint type, app.NSPoint pos, uint button) nothrow @nogcCGEventCreateMouseEvent(void* (parameter) void* sourcesource, uint (parameter) uint typetype, (struct) app.NSPointNSPoint (parameter) app.NSPoint pospos, uint (parameter) uint buttonbutton);
void void app.CGEventSetIntegerValueField(void* event, uint field, long value) nothrow @nogcCGEventSetIntegerValueField(void* (parameter) void* eventevent, uint (parameter) uint fieldfield, long (parameter) long valuevalue);
long long app.CGEventGetIntegerValueField(void* event, uint field) nothrow @nogcCGEventGetIntegerValueField(void* (parameter) void* eventevent, uint (parameter) uint fieldfield);
// The classic warp-suppression-interval knob (per event source).
void* void* app.CGEventSourceCreate(int stateID) nothrow @nogcCGEventSourceCreate(int (parameter) int stateIDstateID);
double double app.CGEventSourceGetLocalEventsSuppressionInterval(void* source) nothrow @nogcCGEventSourceGetLocalEventsSuppressionInterval(void* (parameter) void* sourcesource);
void void app.CGEventSourceSetLocalEventsSuppressionInterval(void* source, double seconds) nothrow @nogcCGEventSourceSetLocalEventsSuppressionInterval(void* (parameter) void* sourcesource, double (parameter) double secondsseconds);
void void app.CFRelease(void* obj) nothrow @nogcCFRelease(void* (parameter) void* objobj);
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);
}
enum uint (constant) uint app.kCGEventMouseMoved = 5ukCGEventMouseMoved = 5; // CGEventType
enum uint (constant) uint app.kCGMouseEventDeltaX = 4ukCGMouseEventDeltaX = 4; // CGEventField
enum uint (constant) uint app.kCGMouseEventDeltaY = 5ukCGMouseEventDeltaY = 5;
enum int (constant) int app.kCGEventSourceStateCombinedSessionState = 0kCGEventSourceStateCombinedSessionState = 0;
// --- 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:");
}
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.eventWithCGEvent(void* cgEvent)eventWithCGEvent(void* (parameter) void* cgEventcgEvent) @selector("eventWithCGEvent:");
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:");
static (struct) app.NSPointNSPoint app.NSPoint app.NSEvent.mouseLocation()mouseLocation() @selector("mouseLocation");
long long app.NSEvent.type()type() @selector("type");
(struct) app.NSPointNSPoint app.NSPoint app.NSEvent.locationInWindow()locationInWindow() @selector("locationInWindow");
double double app.NSEvent.deltaX()deltaX() @selector("deltaX");
double double app.NSEvent.deltaY()deltaY() @selector("deltaY");
long long app.NSEvent.windowNumber()windowNumber() @selector("windowNumber");
(class) app.NSObjectNSObject app.NSObject app.NSEvent.window()window() @selector("window");
}
extern class (class) app.NSCursorNSCursor : (class) app.NSObjectNSObject
{
static void void app.NSCursor.hide()hide() @selector("hide");
static void void app.NSCursor.unhide()unhide() @selector("unhide");
}
extern class (class) app.NSTimerNSTimer : (class) app.NSObjectNSObject
{
static (class) app.NSTimerNSTimer app.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(double interval, (class) app.NSObjectNSObject (parameter) NSObject targettarget,
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.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.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
{
bool bool app.NSView.acceptsFirstResponder()acceptsFirstResponder() @selector("acceptsFirstResponder");
(struct) app.NSRectNSRect app.NSRect app.NSView.bounds()bounds() @selector("bounds");
void void app.NSView.setNeedsDisplay(bool flag)setNeedsDisplay(bool (parameter) bool flagflag) @selector("setNeedsDisplay:");
}
extern class (class) app.NSWindowNSWindow : (class) app.NSObjectNSObject
{
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.NSObject view)setContentView((class) app.NSObjectNSObject (parameter) app.NSObject viewview) @selector("setContentView:");
void void app.NSWindow.makeKeyAndOrderFront(app.NSObject sender)makeKeyAndOrderFront((class) app.NSObjectNSObject (parameter) app.NSObject sendersender) @selector("makeKeyAndOrderFront:");
bool bool app.NSWindow.makeFirstResponder(app.NSObject responder)makeFirstResponder((class) app.NSObjectNSObject (parameter) app.NSObject responderresponder) @selector("makeFirstResponder:");
void void app.NSWindow.sendEvent(app.NSEvent event)sendEvent((class) app.NSEventNSEvent (parameter) app.NSEvent eventevent) @selector("sendEvent:");
long long app.NSWindow.windowNumber()windowNumber() @selector("windowNumber");
(struct) app.NSRectNSRect app.NSRect app.NSWindow.frame()frame() @selector("frame");
(struct) app.NSRectNSRect app.NSRect app.NSWindow.convertRectToScreen(app.NSRect r)convertRectToScreen((struct) app.NSRectNSRect (parameter) app.NSRect rr) @selector("convertRectToScreen:");
bool bool app.NSWindow.acceptsMouseMovedEvents()acceptsMouseMovedEvents() @selector("acceptsMouseMovedEvents");
void void app.NSWindow.setAcceptsMouseMovedEvents(bool flag)setAcceptsMouseMovedEvents(bool (parameter) bool flagflag) @selector("setAcceptsMouseMovedEvents:");
}
// The mouselook view: logs abs+rel motion, integrates a yaw/pitch readout, and
// enforces the confine rect (warp-back workaround) while it is active.
class (class) app.LookViewLookView : (class) app.NSViewNSView
{
static (class) app.LookViewLookView app.LookView app.LookView.alloc()alloc() @selector("alloc");
(class) app.LookViewLookView app.LookView app.LookView.initWithFrame(app.NSRect frame)initWithFrame((struct) app.NSRectNSRect (parameter) app.NSRect frameframe) @selector("initWithFrame:");
override bool bool app.LookView.acceptsFirstResponder()acceptsFirstResponder() @selector("acceptsFirstResponder")
{
return true;
}
void void app.LookView.drawRect(app.NSRect dirtyRect)drawRect((struct) app.NSRectNSRect (parameter) app.NSRect dirtyRectdirtyRect) @selector("drawRect:")
{
// Tier-C visual aid: dark field, confine region, crosshair at yaw/pitch.
void* _error_ ctxctx = NSGraphicsContext.NSGraphicsContext.currentContextcurrentContext().NSGraphicsContext.currentContext().CGContextCGContext();
immutable _error_ bb = this.this.boundsbounds();
CGContextSetRGBFillColor(ctx, 0.12, 0.12, 0.14, 1);
CGContextFillRect(ctx, b);
if (g_confine)
{
CGContextSetRGBFillColor(ctx, 0.18, 0.22, 0.18, 1);
CGContextFillRect(ctx, confineRectView(b));
}
immutable _error_ cxcx = b.b.sizesize.b.size.widthwidth / 2 + clampd(g_yaw, -100, 100);
immutable _error_ cycy = b.b.sizesize.b.size.heightheight / 2 - clampd(g_pitch, -100, 100);
CGContextSetRGBFillColor(ctx, 0.9, 0.3, 0.2, 1);
CGContextFillRect(ctx, NSRect(NSPoint(cx - 12, cy - 1), NSSize(24, 2)));
CGContextFillRect(ctx, NSRect(NSPoint(cx - 1, cy - 12), NSSize(2, 24)));
}
void void app.LookView.mouseMoved(app.NSEvent e)mouseMoved((class) app.NSEventNSEvent (parameter) app.NSEvent ee) @selector("mouseMoved:")
{
onMotion(e, "mouseMoved");
}
void void app.LookView.mouseDragged(app.NSEvent e)mouseDragged((class) app.NSEventNSEvent (parameter) app.NSEvent ee) @selector("mouseDragged:")
{
onMotion(e, "mouseDragged");
}
void void app.LookView.mouseDown(app.NSEvent e)mouseDown((class) app.NSEventNSEvent (parameter) app.NSEvent ee) @selector("mouseDown:")
{
// Interactive (Tier C) toggle: click = mouselook on/off.
instr("mouse_down", "lock_toggle=%d", g_lock ? 0 : 1);
if (!g_auto)
g_lock ? exitLock() : enterLock();
}
void void app.LookView.tick(app.NSTimer t)tick((class) app.NSTimerNSTimer (parameter) app.NSTimer tt) @selector("tick:")
{
onStep();
}
}
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.NSEventTypeMouseMoved = 5LNSEventTypeMouseMoved = 5;
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 (class) app.LookViewLookView (__gshared global) app.LookView app.g_viewg_view;
__gshared long (__gshared global) long app.g_winNumg_winNum;
__gshared bool (__gshared global) bool app.g_autog_auto;
__gshared int (__gshared global) int app.g_stepg_step;
__gshared bool (__gshared global) bool app.g_lockg_lock; // mouselook active
__gshared (struct) app.NSPointNSPoint (__gshared global) app.NSPoint app.g_savedCGg_savedCG; // CG-coord pointer position captured at lock enter
__gshared double (__gshared global) double app.g_yawg_yaw = 0, (__gshared global) double app.g_pitchg_pitch = 0; // crosshair readout, degrees-ish
__gshared bool (__gshared global) bool app.g_confineg_confine; // confine workaround active
__gshared (struct) app.NSRectNSRect (__gshared global) app.NSRect app.g_confineScreeng_confineScreen; // confine rect in AppKit screen coords (y-up)
__gshared int (__gshared global) int app.g_arrivalsg_arrivals; // mouseMoved/mouseDragged deliveries
__gshared int (__gshared global) int app.g_arrivalsAtWarpg_arrivalsAtWarp; // snapshot to detect warp-generated events
__gshared int (__gshared global) int app.g_warpsg_warps; // confine warp-backs issued
__gshared int (__gshared global) int app.g_outOfBoundsg_outOfBounds; // events delivered outside the confine rect
__gshared const(char)* (__gshared global) const(char)* app.g_routeg_route = "none"; // current injection route label
double double app.clampd(double v, double lo, double hi) nothrow @nogcclampd(double (parameter) double vv, double (parameter) double lolo, double (parameter) double hihi) nothrow @nogc
{
return v < lo ? lo : (v > hi ? hi : v);
}
// --- Coordinate helpers -----------------------------------------------------------
// AppKit screen coords are bottom-left-origin y-up; CG global coords are
// top-left-origin y-down. Flip via the main display height (single-display box).
(struct) app.NSPointNSPoint app.NSPoint app.screenToCG(app.NSPoint p)screenToCG((struct) app.NSPointNSPoint (parameter) app.NSPoint pp)
{
immutable _error_ dbdb = CGDisplayBounds(CGMainDisplayID());
return NSPoint(p.p.xx, db.db.sizesize.db.size.heightheight - p.p.yy);
}
(struct) app.NSPointNSPoint app.NSPoint app.cgPointerLocation()cgPointerLocation()
{
void* _error_ evev = CGEventCreate(null);
immutable _error_ pp = CGEventGetLocation(ev);
CFRelease(ev);
return p;
}
// Window-content point -> AppKit screen point (content view origin == window base).
(struct) app.NSPointNSPoint app.NSPoint app.viewToScreen(app.NSPoint p)viewToScreen((struct) app.NSPointNSPoint (parameter) app.NSPoint pp)
{
return g_win.g_win.convertRectToScreenconvertRectToScreen(NSRect(p, NSSize(1, 1))).g_win.convertRectToScreen(NSRect(p, NSSize(1, 1))).originorigin;
}
(struct) app.NSRectNSRect app.NSRect app.confineRectView(app.NSRect b)confineRectView((struct) app.NSRectNSRect (parameter) app.NSRect bb)
{
// Center half of the content view.
return NSRect(NSPoint(b.b.sizesize.b.size.widthwidth / 4, b.b.sizesize.b.size.heightheight / 4),
NSSize(b.b.sizesize.b.size.widthwidth / 2, b.b.sizesize.b.size.heightheight / 2));
}
// --- Lock enter/exit ---------------------------------------------------------------
void void app.enterLock()enterLock()
{
g_savedCG = cgPointerLocation();
instr("lock_save", "cg_pos=(%.1f,%.1f)", g_savedCG.g_savedCG.xx, g_savedCG.g_savedCG.yy);
NSCursor.NSCursor.hidehide();
instr("cursor", "op=NSCursor_hide scope=app note=balanced_counter");
immutable _error_ errerr = CGAssociateMouseAndMouseCursorPosition(0);
g_lock = true;
// There is no CGGetAssociateMouseAndMouseCursorPosition — the association
// state is write-only; the app must track it itself.
instr("lock", "state=on assoc_err=%d readback=no_public_getter", err);
}
void void app.exitLock()exitLock()
{
immutable _error_ errerr = CGAssociateMouseAndMouseCursorPosition(1);
immutable _error_ werrwerr = CGWarpMouseCursorPosition(g_savedCG);
immutable _error_ afterafter = cgPointerLocation();
NSCursor.NSCursor.unhideunhide();
instr("cursor", "op=NSCursor_unhide scope=app");
g_lock = false;
instr("lock", "state=off assoc_err=%d restore_warp_err=%d target=(%.1f,%.1f) readback=(%.1f,%.1f)",
err, werr, g_savedCG.g_savedCG.xx, g_savedCG.g_savedCG.yy, after.after.xx, after.after.yy);
}
// --- Motion handling (the view's mouseMoved/mouseDragged funnel) -------------------
void void app.onMotion(app.NSEvent e, const(char)* kind)onMotion((class) app.NSEventNSEvent (parameter) app.NSEvent ee, const(char)* (parameter) const(char)* kindkind)
{
++g_arrivals;
immutable _error_ dxdx = e.e.deltaXdeltaX();
immutable _error_ dydy = e.e.deltaYdeltaY();
(unresolved type) NSPointNSPoint _error_ pp = e.e.locationInWindowlocationInWindow();
// A window-less event (CGEvent-wrapped) reports locationInWindow in AppKit
// screen coords already; a window-bound one needs conversion.
immutable (unresolved type) NSPointNSPoint _error_ spsp = e.e.windowwindow() !is null ? viewToScreen(p) : p;
instr("pointer", "abs x=%.1f y=%.1f screen=(%.1f,%.1f) kind=%s win=%ld",
p.p.xx, p.p.yy, sp.sp.xx, sp.sp.yy, kind, e.e.windowNumberwindowNumber());
// NSEvent deltas are post-acceleration (WindowServer ballistics) — raw=0.
// Raw counts would need IOHIDManager, below the window system.
instr("pointer", "rel dx=%.2f dy=%.2f raw=0 route=%s", dx, dy, g_route);
if (g_lock)
{
g_yaw += dx * 0.5;
g_pitch += dy * 0.5;
instr("mouselook", "yaw=%.2f pitch=%.2f", g_yaw, g_pitch);
g_view.g_view.setNeedsDisplaysetNeedsDisplay(true);
}
if (g_confine)
enforceConfine(sp);
}
void void app.enforceConfine(app.NSPoint screenPt)enforceConfine((struct) app.NSPointNSPoint (parameter) app.NSPoint screenPtscreenPt)
{
immutable _error_ rr = g_confineScreen;
immutable _error_ inXinX = screenPt.screenPt.xx >= r.r.originorigin.r.origin.xx && screenPt.screenPt.xx <= r.r.originorigin.r.origin.xx + r.r.sizesize.r.size.widthwidth;
immutable _error_ inYinY = screenPt.screenPt.yy >= r.r.originorigin.r.origin.yy && screenPt.screenPt.yy <= r.r.originorigin.r.origin.yy + r.r.sizesize.r.size.heightheight;
if (inX && inY)
return;
// The artifact: this event ALREADY arrived at an out-of-bounds position —
// the warp is reactive, one event too late, every time the pointer leaves.
++g_outOfBounds;
immutable _error_ clampedclamped = NSPoint(
clampd(screenPt.screenPt.xx, r.r.originorigin.r.origin.xx, r.r.originorigin.r.origin.xx + r.r.sizesize.r.size.widthwidth),
clampd(screenPt.screenPt.yy, r.r.originorigin.r.origin.yy, r.r.originorigin.r.origin.yy + r.r.sizesize.r.size.heightheight));
immutable _error_ errerr = CGWarpMouseCursorPosition(screenToCG(clamped));
++g_warps;
instr("confine", "warp from=(%.1f,%.1f) to=(%.1f,%.1f) err=%d note=event_already_delivered_outside",
screenPt.screenPt.xx, screenPt.screenPt.yy, clamped.clamped.xx, clamped.clamped.yy, err);
}
// --- Synthetic motion injection -----------------------------------------------------
// Dispatch in-process: try [window sendEvent:]; if the view never sees it, fall
// back to calling mouseMoved: directly (either outcome is logged).
void void app.dispatchMouse(app.NSEvent e, const(char)* label)dispatchMouse((class) app.NSEventNSEvent (parameter) app.NSEvent ee, const(char)* (parameter) const(char)* labellabel)
{
if (e is null)
{
instr("inject", "label=%s result=nil_event", label);
return;
}
immutable _error_ beforebefore = g_arrivals;
g_route = "sendEvent";
g_win.g_win.sendEventsendEvent(e);
if (g_arrivals == before)
{
instr("route", "label=%s sendEvent=not_delivered fallback=direct", label);
g_route = "direct";
g_view.g_view.mouseMovedmouseMoved(e);
}
else
instr("route", "label=%s sendEvent=delivered", label);
g_route = "none";
}
// Probe (a): the plain NSEvent constructor — it has NO delta parameters.
void void app.injectPlainMove(app.NSPoint viewPt, const(char)* label)injectPlainMove((struct) app.NSPointNSPoint (parameter) app.NSPoint viewPtviewPt, const(char)* (parameter) const(char)* labellabel)
{
(unresolved type) NSEventNSEvent _error_ ee = NSEvent.NSEvent.mouseEventWithTypemouseEventWithType(NSEventTypeMouseMoved, viewPt, 0, 0,
g_winNum, null, 0, 0, 0);
instr("inject", "route=nsevent label=%s loc=(%.0f,%.0f) ctor_deltas=none wrapped_dx=%.2f wrapped_dy=%.2f",
label, viewPt.viewPt.xx, viewPt.viewPt.yy, e.e.deltaXdeltaX(), e.e.deltaYdeltaY());
dispatchMouse(e, label);
}
// Probe (b): CGEvent with kCGMouseEventDeltaX/Y set, wrapped via eventWithCGEvent.
void void app.injectCGMove(app.NSPoint screenPt, long dx, long dy, const(char)* label)injectCGMove((struct) app.NSPointNSPoint (parameter) app.NSPoint screenPtscreenPt, long (parameter) long dxdx, long (parameter) long dydy, const(char)* (parameter) const(char)* labellabel)
{
void* _error_ cgcg = CGEventCreateMouseEvent(null, kCGEventMouseMoved, screenToCG(screenPt), 0);
if (cg is null)
{
instr("inject", "route=cgevent_wrap label=%s result=create_failed", label);
return;
}
CGEventSetIntegerValueField(cg, kCGMouseEventDeltaX, dx);
CGEventSetIntegerValueField(cg, kCGMouseEventDeltaY, dy);
instr("inject", "route=cgevent_wrap label=%s cg_dx=%ld cg_dy=%ld",
label, CGEventGetIntegerValueField(cg, kCGMouseEventDeltaX),
CGEventGetIntegerValueField(cg, kCGMouseEventDeltaY));
(unresolved type) NSEventNSEvent _error_ ee = NSEvent.NSEvent.eventWithCGEventeventWithCGEvent(cg);
if (e !is null)
instr("wrap", "label=%s type=%ld wrapped_dx=%.2f wrapped_dy=%.2f win=%ld",
label, e.e.typetype(), e.e.deltaXdeltaX(), e.e.deltaYdeltaY(), e.e.windowNumberwindowNumber());
dispatchMouse(e, label);
CFRelease(cg);
}
// --- The scripted run ----------------------------------------------------------------
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); // stop: is only checked after an event dispatch
}
void void app.onStep()onStep()
{
if (!g_auto)
return;
immutable _error_ bb = g_view.g_view.boundsbounds();
immutable _error_ centercenter = NSPoint(b.b.sizesize.b.size.widthwidth / 2, b.b.sizesize.b.size.heightheight / 2);
switch (g_step)
{
case 0: // Baseline reads: AppKit vs CG pointer position, mouse-moved opt-in.
{
immutable _error_ nsLocnsLoc = NSEvent.NSEvent.mouseLocationmouseLocation();
immutable _error_ cgLoccgLoc = cgPointerLocation();
instr("baseline", "nsevent_mouseLocation=(%.1f,%.1f) cgevent_location=(%.1f,%.1f) accepts_mouse_moved=%d",
nsLoc.nsLoc.xx, nsLoc.nsLoc.yy, cgLoc.cgLoc.xx, cgLoc.cgLoc.yy,
g_win.g_win.acceptsMouseMovedEventsacceptsMouseMovedEvents() ? 1 : 0);
}
break;
case 1:
enterLock();
break;
case 2: // (a) plain synthetic mouseMoved: no way to carry a delta.
injectPlainMove(center, "plain_move_center");
break;
case 3: // (b) CGEvent-backed with deltas.
injectCGMove(viewToScreen(center), 12, -7, "cg_move_delta_12_-7");
break;
case 4:
injectCGMove(viewToScreen(center), 3, 4, "cg_move_delta_3_4");
break;
case 5: // Warp while locked: CGError + CG-side read-back.
{
immutable _error_ targettarget = screenToCG(viewToScreen(center));
immutable _error_ beforebefore = cgPointerLocation();
immutable _error_ errerr = CGWarpMouseCursorPosition(target);
immutable _error_ afterafter = cgPointerLocation();
g_arrivalsAtWarp = g_arrivals;
instr("warp", "target=(%.1f,%.1f) err=%d before=(%.1f,%.1f) after=(%.1f,%.1f)",
target.target.xx, target.target.yy, err, before.before.xx, before.before.yy, after.after.xx, after.after.yy);
}
break;
case 6: // Did the warp synthesize any motion event? + suppression interval.
{
instr("warp_events", "arrivals_since_warp=%d note=warp_generates_no_events_documented",
g_arrivals - g_arrivalsAtWarp);
void* _error_ srcsrc = CGEventSourceCreate(kCGEventSourceStateCombinedSessionState);
if (src !is null)
{
immutable _error_ defdef = CGEventSourceGetLocalEventsSuppressionInterval(src);
CGEventSourceSetLocalEventsSuppressionInterval(src, 0.0);
instr("suppression_interval", "default_s=%.3f set_s=0.000 readback_s=%.3f scope=per_event_source",
def, CGEventSourceGetLocalEventsSuppressionInterval(src));
CFRelease(src);
}
else
instr("suppression_interval", "result=source_create_failed");
}
break;
case 7:
exitLock();
break;
case 8: // Display-scoped hide/show, for contrast with NSCursor's app counter.
{
immutable _error_ diddid = CGMainDisplayID();
immutable _error_ hh = CGDisplayHideCursor(did);
immutable _error_ ss = CGDisplayShowCursor(did);
instr("cursor", "op=CGDisplayHideCursor err=%d display=%u scope=display", h, did);
instr("cursor", "op=CGDisplayShowCursor err=%d display=%u scope=display", s, did);
}
break;
case 9: // Confine workaround on: center half of the content view.
{
immutable _error_ rvrv = confineRectView(b);
g_confineScreen = NSRect(viewToScreen(rv.rv.originorigin), rv.rv.sizesize);
g_confine = true;
instr("confine", "rect_view=(%.0f,%.0f %.0fx%.0f) rect_screen=(%.0f,%.0f %.0fx%.0f) api=none_public mechanism=warp_back",
rv.rv.originorigin.rv.origin.xx, rv.rv.originorigin.rv.origin.yy, rv.rv.sizesize.rv.size.widthwidth, rv.rv.sizesize.rv.size.heightheight,
g_confineScreen.g_confineScreen.originorigin.g_confineScreen.origin.xx, g_confineScreen.g_confineScreen.originorigin.g_confineScreen.origin.yy,
g_confineScreen.g_confineScreen.sizesize.g_confineScreen.size.widthwidth, g_confineScreen.g_confineScreen.sizesize.g_confineScreen.size.heightheight);
g_view.g_view.setNeedsDisplaysetNeedsDisplay(true);
}
break;
case 10: // Inside: no warp expected.
injectCGMove(viewToScreen(center), 5, 0, "confine_inside");
break;
case 11: // Past the right edge of the confine rect.
injectCGMove(viewToScreen(NSPoint(b.b.sizesize.b.size.widthwidth - 10, center.center.yy)), 40, 0,
"confine_outside_right");
break;
case 12: // Far past the top-left corner.
injectCGMove(viewToScreen(NSPoint(5, b.b.sizesize.b.size.heightheight - 5)), -60, -50,
"confine_outside_topleft");
break;
case 13:
injectCGMove(viewToScreen(center), 10, 10, "confine_inside_again");
break;
case 14:
g_confine = false;
instr("confine", "state=off out_of_bounds_deliveries=%d warps=%d",
g_outOfBounds, g_warps);
break;
case 15:
stopApp();
break;
default:
break;
}
++g_step;
}
int int D main()main()
{
g_auto = getenv("WSI_AUTO_EXIT") !is null;
instrInit("APPKIT_F10");
instr("init_start", "auto_exit=%d", g_auto ? 1 : 0);
instr("step", "name=CGMainDisplayID");
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);
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(NSString.NSString.allocalloc().NSString.alloc().initWithUTF8StringinitWithUTF8String("wsi-f10-pointer-capture"));
g_view = LookView.LookView.allocalloc().LookView.alloc().initWithFrameinitWithFrame(NSRect(NSPoint(0, 0), contentRect.contentRect.sizesize));
g_win.g_win.setContentViewsetContentView(g_view);
g_win.g_win.makeKeyAndOrderFrontmakeKeyAndOrderFront(null);
g_app.g_app.activateIgnoringOtherAppsactivateIgnoringOtherApps(true);
g_win.g_win.makeFirstRespondermakeFirstResponder(g_view);
g_winNum = g_win.g_win.windowNumberwindowNumber();
// mouseMoved: is opt-in on macOS — without this, only drags arrive.
immutable _error_ beforeOptInbeforeOptIn = g_win.g_win.acceptsMouseMovedEventsacceptsMouseMovedEvents();
g_win.g_win.setAcceptsMouseMovedEventssetAcceptsMouseMovedEvents(true);
instr("window_created", "win_num=%ld accepts_mouse_moved_default=%d set=1",
g_winNum, beforeOptIn ? 1 : 0);
instr("step", "name=script_timer_start interval_ms=150");
NSTimer.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(0.15, g_view, SEL.register("tick:"), null, true);
instr("step", "name=NSApp_run");
g_app.g_app.runrun();
instr("loop_exit", "steps=%d arrivals=%d warps=%d", g_step, g_arrivals, g_warps);
return 0;
}