// macOS/AppKit F06 demo — keyboard & keymap (../../../features/f06-keyboard.md).
// Scancode -> keysym -> text is three different questions, and AppKit splits the work
// between the hardware keyCode, the layout engine (UCKeyTranslate / the `uchr` data),
// and the text-input layer (interpretKeyEvents: -> insertText:/doCommandBySelector:).
// This demo logs all three levels and probes the boundaries.
//
// A custom NSView (KeyView, acceptsFirstResponder = YES) logs keyDown:/keyUp:/
// flagsChanged: as `key code=<scancode> sym=<charsIgnoringMods> text=<characters>
// state=down|up repeat=0|1`, then routes each keyDown through interpretKeyEvents:,
// implementing insertText:replacementRange:, the legacy insertText:, and
// doCommandBySelector: — logging which one the text system actually calls (the
// NSTextInputClient marked-text methods are F07; here we observe what arrives without
// them, which is the boundary finding F07 builds on).
//
// Three demonstrations, choreographed under WSI_AUTO_EXIT=1:
// 1. Layout engine (synchronous, no run loop / injection — robust over SSH):
// UCKeyTranslate on the current `uchr` proves the three-level split (keyCode 0 ->
// "a"; +shift -> "A"; option-e sets a deadKeyState + no text, then keyCode 14
// composes "é"); an installed German `uchr` is pulled WITHOUT switching to show
// the same scancode (keyCode 6) yield 'z' US vs 'y' QWERTZ; a TISSelectInputSource
// switch is attempted and its OSStatus captured.
// 2. App-side chain (route 1 — synthetic NSEvent + [window sendEvent:], always works
// in-process): a letter, shifted digit ('!'), arrow (-> doCommandBySelector:), the
// option-e + e dead-key pair, and a repeat (isARepeat:YES) — logging what
// interpretKeyEvents: forwards to insertText:/doCommandBySelector:.
// 3. Real HID injection (route 2 — CGEventPost): does it reach keyDown: under a
// locked screen / over SSH? Either outcome is a finding.
// Also logs NSEvent.keyRepeatDelay / .keyRepeatInterval (the system repeat contract).
//
// 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.configD compatible types that correspond to various basic types in associated
C and C++ compilers.
Source
core/stdc/config.d
config : c_ulong;
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 (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.strstr = inout(char)* core.stdc.string.strstr(return scope inout(char)* s1, scope const(char*) s2) pure nothrow @nogcstrstr;
import instrument : instr, instrInit;
import objc.autorelease : autoreleasepool_push, autoreleasepool_pop; // objective-d
import objc.rt : SEL;
// --- Carbon / CoreFoundation C API (layout engine + input sources) --------------
// UCKeyTranslate, the TIS* functions, LMGetKbdType, and the kTISProperty* globals
// live in Carbon (HIToolbox); the build links `-framework Carbon` in addition to
// Cocoa. CFData/CFArray/CFString helpers come with CoreFoundation (via Cocoa).
alias (alias) app.OSStatus = intOSStatus = int;
alias (alias) app.TISInputSourceRef = void*TISInputSourceRef = void*;
alias (alias) app.CFStringRef = void*CFStringRef = void*;
alias (alias) app.CFDataRef = void*CFDataRef = void*;
alias (alias) app.CFArrayRef = void*CFArrayRef = void*;
extern (C) nothrow @nogc
{
uint uint app.CGMainDisplayID() nothrow @nogcCGMainDisplayID();
(alias) app.TISInputSourceRef = void*TISInputSourceRef void* app.TISCopyCurrentKeyboardLayoutInputSource() nothrow @nogcTISCopyCurrentKeyboardLayoutInputSource();
void* void* app.TISGetInputSourceProperty(void* source, void* key) nothrow @nogcTISGetInputSourceProperty((alias) app.TISInputSourceRef = void*TISInputSourceRef (parameter) void* sourcesource, (alias) app.CFStringRef = void*CFStringRef (parameter) void* keykey);
(alias) app.CFArrayRef = void*CFArrayRef void* app.TISCreateInputSourceList(void* properties, bool includeAllInstalled) nothrow @nogcTISCreateInputSourceList(void* (parameter) void* propertiesproperties, bool (parameter) bool includeAllInstalledincludeAllInstalled);
(alias) app.OSStatus = intOSStatus int app.TISSelectInputSource(void* source) nothrow @nogcTISSelectInputSource((alias) app.TISInputSourceRef = void*TISInputSourceRef (parameter) void* sourcesource);
extern __gshared (alias) app.CFStringRef = void*CFStringRef (__gshared global) void* app.kTISPropertyUnicodeKeyLayoutDatakTISPropertyUnicodeKeyLayoutData;
extern __gshared (alias) app.CFStringRef = void*CFStringRef (__gshared global) void* app.kTISPropertyInputSourceIDkTISPropertyInputSourceID;
const(ubyte)* const(ubyte)* app.CFDataGetBytePtr(void* d) nothrow @nogcCFDataGetBytePtr((alias) app.CFDataRef = void*CFDataRef (parameter) void* dd);
long long app.CFArrayGetCount(void* a) nothrow @nogcCFArrayGetCount((alias) app.CFArrayRef = void*CFArrayRef (parameter) void* aa);
const(void)* const(void)* app.CFArrayGetValueAtIndex(void* a, long idx) nothrow @nogcCFArrayGetValueAtIndex((alias) app.CFArrayRef = void*CFArrayRef (parameter) void* aa, long (parameter) long idxidx);
bool bool app.CFStringGetCString(void* s, char* buf, long bufSize, uint encoding) nothrow @nogcCFStringGetCString((alias) app.CFStringRef = void*CFStringRef (parameter) void* ss, char* (parameter) char* bufbuf, long (parameter) long bufSizebufSize, uint (parameter) uint encodingencoding);
void void app.CFRelease(void* o) nothrow @nogcCFRelease(void* (parameter) void* oo);
uint uint app.LMGetKbdType() nothrow @nogcLMGetKbdType();
(alias) app.OSStatus = intOSStatus int app.UCKeyTranslate(const(void)* keyLayoutPtr, ushort virtualKeyCode, ushort keyAction, uint modifierKeyState, uint keyboardType, uint keyTranslateOptions, uint* deadKeyState, ulong maxStringLength, ulong* actualStringLength, ushort* unicodeString) nothrow @nogcUCKeyTranslate(const(void)* (parameter) const(void)* keyLayoutPtrkeyLayoutPtr, ushort (parameter) ushort virtualKeyCodevirtualKeyCode,
ushort (parameter) ushort keyActionkeyAction, uint (parameter) uint modifierKeyStatemodifierKeyState, uint (parameter) uint keyboardTypekeyboardType,
uint (parameter) uint keyTranslateOptionskeyTranslateOptions, uint* (parameter) uint* deadKeyStatedeadKeyState, c_ulong (parameter) ulong maxStringLengthmaxStringLength,
c_ulong* (parameter) ulong* actualStringLengthactualStringLength, ushort* (parameter) ushort* unicodeStringunicodeString);
// CoreGraphics event injection (route 2)
void* void* app.CGEventCreateKeyboardEvent(void* source, ushort vk, bool keyDown) nothrow @nogcCGEventCreateKeyboardEvent(void* (parameter) void* sourcesource, ushort (parameter) ushort vkvk, bool (parameter) bool keyDownkeyDown);
void void app.CGEventPost(uint tapLocation, void* event) nothrow @nogcCGEventPost(uint (parameter) uint tapLocationtapLocation, void* (parameter) void* eventevent);
void void app.CGEventSetFlags(void* event, ulong flags) nothrow @nogcCGEventSetFlags(void* (parameter) void* eventevent, ulong (parameter) ulong flagsflags);
}
enum ushort (constant) ushort app.kUCKeyActionDown = cast(ushort)0ukUCKeyActionDown = 0;
enum uint (constant) uint app.kCFStringEncodingUTF8 = 134217984ukCFStringEncodingUTF8 = 0x0800_0100;
enum uint (constant) uint app.kCGHIDEventTap = 0ukCGHIDEventTap = 0;
enum uint (constant) uint app.kCGSessionEventTap = 1ukCGSessionEventTap = 1;
// UCKeyTranslate modifier-key state is the classic high-byte modifiers >> 8.
enum uint (constant) uint app.uckShift = 2uuckShift = 2; // shiftKey (0x0200) >> 8
enum uint (constant) uint app.uckOption = 8uuckOption = 8; // optionKey (0x0800) >> 8
// --- Cocoa geometry / NSRange ---------------------------------------------------
extern (C) struct (struct) app.NSPointNSPoint
{
double (field) double app.NSPoint.xx, (field) double app.NSPoint.yy;
}
extern (C) struct (struct) app.NSSizeNSSize
{
double (field) double app.NSSize.widthwidth, (field) double app.NSSize.heightheight;
}
extern (C) struct (struct) app.NSRectNSRect
{
(struct) app.NSPointNSPoint (field) app.NSPoint app.NSRect.originorigin;
(struct) app.NSSizeNSSize (field) app.NSSize app.NSRect.sizesize;
}
extern (C) struct (struct) app.NSRangeNSRange
{
ulong (field) ulong app.NSRange.locationlocation, (field) ulong app.NSRange.lengthlength; // NSUInteger
}
// --- 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
{
static (class) app.NSArrayNSArray app.NSArray app.NSArray.arrayWithObject(app.NSObject o)arrayWithObject((class) app.NSObjectNSObject (parameter) app.NSObject oo) @selector("arrayWithObject:");
}
extern class (class) app.NSResponderNSResponder : (class) app.NSObjectNSObject
{
void void app.NSResponder.interpretKeyEvents(app.NSArray events)interpretKeyEvents((class) app.NSArrayNSArray (parameter) app.NSArray eventsevents) @selector("interpretKeyEvents:");
}
extern class (class) app.NSEventNSEvent : (class) app.NSObjectNSObject
{
static (class) app.NSEventNSEvent app.NSEvent app.NSEvent.keyEventWithType(long type, app.NSPoint location, ulong modifierFlags, double timestamp, long windowNumber, app.NSObject context, app.NSString characters, app.NSString charactersIgnoringModifiers, bool isARepeat, ushort keyCode)keyEventWithType(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, (class) app.NSStringNSString (parameter) app.NSString characterscharacters,
(class) app.NSStringNSString (parameter) app.NSString charactersIgnoringModifierscharactersIgnoringModifiers, bool (parameter) bool isARepeatisARepeat, ushort (parameter) ushort keyCodekeyCode)
@selector("keyEventWithType:location:modifierFlags:timestamp:windowNumber:context:characters:charactersIgnoringModifiers:isARepeat:keyCode:");
ushort ushort app.NSEvent.keyCode()keyCode() @selector("keyCode");
(class) app.NSStringNSString app.NSString app.NSEvent.characters()characters() @selector("characters");
(class) app.NSStringNSString app.NSString app.NSEvent.charactersIgnoringModifiers()charactersIgnoringModifiers() @selector("charactersIgnoringModifiers");
bool bool app.NSEvent.isARepeat()isARepeat() @selector("isARepeat");
ulong ulong app.NSEvent.modifierFlags()modifierFlags() @selector("modifierFlags");
static double double app.NSEvent.keyRepeatDelay()keyRepeatDelay() @selector("keyRepeatDelay");
static double double app.NSEvent.keyRepeatInterval()keyRepeatInterval() @selector("keyRepeatInterval");
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 (parameter) NSObject targettarget,
SEL sel, (class) app.NSObjectNSObject userInfo, bool repeats)
@selector("scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:");
}
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
{
bool bool app.NSView.acceptsFirstResponder()acceptsFirstResponder() @selector("acceptsFirstResponder");
}
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.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");
}
// The custom view: logs the three key levels and routes through the text system.
class (class) app.KeyViewKeyView : (class) app.NSViewNSView
{
static (class) app.KeyViewKeyView app.KeyView app.KeyView.alloc()alloc() @selector("alloc");
(class) app.KeyViewKeyView app.KeyView app.KeyView.initWithFrame(app.NSRect frame)initWithFrame((struct) app.NSRectNSRect (parameter) app.NSRect frameframe) @selector("initWithFrame:");
override bool bool app.KeyView.acceptsFirstResponder()acceptsFirstResponder() @selector("acceptsFirstResponder")
{
return true;
}
void void app.KeyView.keyDown(app.NSEvent e)keyDown((class) app.NSEventNSEvent (parameter) app.NSEvent ee) @selector("keyDown:")
{
logKey("down", e);
if (e.e.keyCodekeyCode() == g_cgWatchCode)
g_cgArrived = true;
// Route through the text-input system: this is what turns keysym-level events
// into insertText:/doCommandBySelector: callbacks.
instr("step", "name=interpretKeyEvents code=%u", e.e.keyCodekeyCode());
this.this.interpretKeyEventsinterpretKeyEvents(NSArray.NSArray.arrayWithObjectarrayWithObject(e));
}
void void app.KeyView.keyUp(app.NSEvent e)keyUp((class) app.NSEventNSEvent (parameter) app.NSEvent ee) @selector("keyUp:")
{
logKey("up", e);
}
void void app.KeyView.flagsChanged(app.NSEvent e)flagsChanged((class) app.NSEventNSEvent (parameter) app.NSEvent ee) @selector("flagsChanged:")
{
instr("flags_changed", "code=%u flags=0x%llx", e.e.keyCodekeyCode(), e.e.modifierFlagsmodifierFlags());
}
// Modern NSTextInputClient text insertion (the one F07 will round out).
void void app.KeyView.insertText_(app.NSString s, app.NSRange r)insertText_((class) app.NSStringNSString (parameter) app.NSString ss, (struct) app.NSRangeNSRange (parameter) app.NSRange rr) @selector("insertText:replacementRange:")
{
instr("insert_text", "variant=replacementRange text=%s loc=%llu len=%llu",
s !is null ? s.s.UTF8StringUTF8String() : "", r.r.locationlocation, r.r.lengthlength);
}
// Legacy single-argument insertText: (pre-NSTextInputClient NSResponder path).
void void app.KeyView.insertTextLegacy(app.NSObject s)insertTextLegacy((class) app.NSObjectNSObject (parameter) app.NSObject ss) @selector("insertText:")
{
auto _error_ strstr = cast((unresolved type) NSStringNSString) s;
instr("insert_text", "variant=legacy text=%s",
str !is null ? str.str.UTF8StringUTF8String() : "");
}
void app.KeyView.doCommandBySelectordoCommandBySelector(SEL sel) @selector("doCommandBySelector:")
{
instr("do_command", "selector=%s", sel.name);
}
}
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.NSEventTypeKeyDown = 10LNSEventTypeKeyDown = 10;
enum long (constant) long app.NSEventTypeKeyUp = 11LNSEventTypeKeyUp = 11;
enum long (constant) long app.NSEventTypeApplicationDefined = 15LNSEventTypeApplicationDefined = 15;
enum : ulong
{
(enum value) app.NSEventModifierFlagShift = 131072LUNSEventModifierFlagShift = 1UL << 17,
(enum value) app.NSEventModifierFlagControl = 262144LUNSEventModifierFlagControl = 1UL << 18,
(enum value) app.NSEventModifierFlagOption = 524288LUNSEventModifierFlagOption = 1UL << 19,
(enum value) app.NSEventModifierFlagCommand = 1048576LUNSEventModifierFlagCommand = 1UL << 20,
(enum value) app.NSEventModifierFlagNumericPad = 2097152LUNSEventModifierFlagNumericPad = 1UL << 21,
(enum value) app.NSEventModifierFlagFunction = 8388608LUNSEventModifierFlagFunction = 1UL << 23,
}
enum ulong (constant) ulong app.kCGEventFlagMaskAlternate = 524288LUkCGEventFlagMaskAlternate = 1UL << 19; // option, for CGEvent route
// --- 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.KeyViewKeyView (__gshared global) app.KeyView app.g_viewg_view;
__gshared long (__gshared global) long app.g_winNumg_winNum;
__gshared int (__gshared global) int app.g_stepg_step;
__gshared ushort (__gshared global) ushort app.g_cgWatchCodeg_cgWatchCode = 2; // 'd' on US ANSI — used only by route 2
__gshared bool (__gshared global) bool app.g_cgArrivedg_cgArrived;
// Route-1 synthetic key script. `chars`/`charsNoMod` are D string literals (always
// NUL-terminated), so `.ptr` is a valid C string for initWithUTF8String.
struct (struct) app.KeySpecKeySpec
{
ushort (field) ushort app.KeySpec.codecode;
ulong (field) ulong app.KeySpec.flagsflags;
(alias) object.string = stringstring (field) string app.KeySpec.charschars; // -[NSEvent characters]
(alias) object.string = stringstring (field) string app.KeySpec.charsNoModcharsNoMod; // -[NSEvent charactersIgnoringModifiers]
bool (field) bool app.KeySpec.repeatrepeat;
(alias) object.string = stringstring (field) string app.KeySpec.labellabel;
}
// keyCode 18 = '1', 123 = LeftArrow, 14 = 'e'. The option-e event carries the spacing
// acute U+00B4 in `characters` (what a US dead-acute keyDown reports); the boundary
// question is whether interpretKeyEvents re-composes "é" or just forwards it.
immutable (struct) app.KeySpecKeySpec[] (immutable global) immutable(app.KeySpec[]) app.g_scriptg_script = [
(struct) app.KeySpecKeySpec(0, 0, "a", "a", false, "letter_a"),
(struct) app.KeySpecKeySpec(18, (enum value) app.NSEventModifierFlagShift = 131072LUNSEventModifierFlagShift, "!", "1", false, "shift_1_bang"),
(struct) app.KeySpecKeySpec(123, (enum value) app.NSEventModifierFlagFunction = 8388608LUNSEventModifierFlagFunction | (enum value) app.NSEventModifierFlagNumericPad = 2097152LUNSEventModifierFlagNumericPad,
"", "", false, "arrow_left"),
(struct) app.KeySpecKeySpec(14, (enum value) app.NSEventModifierFlagOption = 524288LUNSEventModifierFlagOption, "´", "e", false, "opt_e_deadacute"),
(struct) app.KeySpecKeySpec(14, 0, "e", "e", false, "e_after_dead"),
(struct) app.KeySpecKeySpec(0, 0, "a", "a", true, "repeat_a"),
];
(class) app.NSEventNSEvent app.NSEvent app.makeKey(long type, in app.KeySpec k)makeKey(long (parameter) long typetype, in (struct) app.KeySpecKeySpec (parameter) const(app.KeySpec) kk)
{
(unresolved type) NSStringNSString _error_ chch = NSString.NSString.allocalloc().NSString.alloc().initWithUTF8StringinitWithUTF8String(k.k.charschars.ptr);
(unresolved type) NSStringNSString _error_ cmcm = NSString.NSString.allocalloc().NSString.alloc().initWithUTF8StringinitWithUTF8String(k.k.charsNoModcharsNoMod.ptr);
return NSEvent.NSEvent.keyEventWithTypekeyEventWithType(type, NSPoint(0, 0), k.k.flagsflags, 0, g_winNum, null,
ch, cm, k.k.repeatrepeat, k.k.codecode);
}
void void app.logKey(const(char)* state, app.NSEvent e)logKey(const(char)* (parameter) const(char)* statestate, (class) app.NSEventNSEvent (parameter) app.NSEvent ee)
{
(unresolved type) NSStringNSString _error_ symsym = e.e.charactersIgnoringModifierscharactersIgnoringModifiers();
(unresolved type) NSStringNSString _error_ txttxt = e.e.characterscharacters();
instr("key", "code=%u sym=%s text=%s state=%s repeat=%d flags=0x%llx",
e.e.keyCodekeyCode(), sym !is null ? sym.sym.UTF8StringUTF8String() : "",
txt !is null ? txt.txt.UTF8StringUTF8String() : "", state, e.e.isARepeatisARepeat() ? 1 : 0,
e.e.modifierFlagsmodifierFlags());
}
// --- UCKeyTranslate layout-engine demonstration (synchronous) -------------------
// BMP-only UTF-16 -> UTF-8 (enough for a/A/é/z/y); writes a NUL-terminated C string.
void void app.utf16ToUtf8(const(ushort)* s, ulong n, char* o) nothrow @nogcutf16ToUtf8(const(ushort)* (parameter) const(ushort)* ss, (alias) object.size_t = ulongsize_t (parameter) ulong nn, char* (parameter) char* oo) nothrow @nogc
{
(unresolved type) size_tsize_t _error_ jj = 0;
foreach ((parameter) ii; 0 .. n)
{
immutable uint _error_ cc = s[i];
if (c < 0x80)
o[j++] = cast(char) c;
else if (c < 0x800)
{
o[j++] = cast(char)(0xC0 | (c >> 6));
o[j++] = cast(char)(0x80 | (c & 0x3F));
}
else
{
o[j++] = cast(char)(0xE0 | (c >> 12));
o[j++] = cast(char)(0x80 | ((c >> 6) & 0x3F));
o[j++] = cast(char)(0x80 | (c & 0x3F));
}
}
o[j] = 0;
}
// One UCKeyTranslate call; returns the number of UTF-16 units produced and updates
// *deadState (kept across calls so a dead key can compose with the next press).
(alias) object.size_t = ulongsize_t ulong app.uck(const(void)* layout, ushort vk, uint modState, uint* deadState, ushort* outUtf16, ulong cap) nothrow @nogcuck(const(void)* (parameter) const(void)* layoutlayout, ushort (parameter) ushort vkvk, uint (parameter) uint modStatemodState, uint* (parameter) uint* deadStatedeadState,
ushort* (parameter) ushort* outUtf16outUtf16, (alias) object.size_t = ulongsize_t (parameter) ulong capcap) nothrow @nogc
{
c_ulong _error_ actualactual = 0;
UCKeyTranslate(layout, vk, kUCKeyActionDown, modState, LMGetKbdType(), 0,
deadState, cap, &actual, outUtf16);
return cast((unresolved type) size_tsize_t) actual;
}
void void app.layoutDemo(const(void)* layout, const(char)* label)layoutDemo(const(void)* (parameter) const(void)* layoutlayout, const(char)* (parameter) const(char)* labellabel)
{
ushort[8] _error_ uu;
char[32] _error_ ss;
uint _error_ deaddead;
dead = 0;
auto _error_ nn = uck(layout, 0, 0, &dead, u.ptr, u.u.lengthlength);
utf16ToUtf8(u.ptr, n, s.ptr);
instr("uckey", "layout=%s code=0 mods=none text=%s", label, s.ptr);
dead = 0;
n = uck(layout, 0, uckShift, &dead, u.ptr, u.u.lengthlength);
utf16ToUtf8(u.ptr, n, s.ptr);
instr("uckey", "layout=%s code=0 mods=shift text=%s", label, s.ptr);
// keyCode 6: 'z' on US ANSI, 'y' on German QWERTZ — same scancode, different text.
dead = 0;
n = uck(layout, 6, 0, &dead, u.ptr, u.u.lengthlength);
utf16ToUtf8(u.ptr, n, s.ptr);
instr("uckey", "layout=%s code=6 mods=none text=%s", label, s.ptr);
// Dead key: option-e sets a dead state and emits nothing...
dead = 0;
n = uck(layout, 14, uckOption, &dead, u.ptr, u.u.lengthlength);
utf16ToUtf8(u.ptr, n, s.ptr);
instr("compose", "layout=%s state=dead_set code=14 mods=option dead_state=%u text_len=%zu text=%s",
label, dead, n, s.ptr);
// ...then the next 'e' composes "é" using the retained dead state.
n = uck(layout, 14, 0, &dead, u.ptr, u.u.lengthlength);
utf16ToUtf8(u.ptr, n, s.ptr);
instr("compose", "layout=%s state=composed code=14 mods=none dead_state=%u text=%s",
label, dead, s.ptr);
}
const(void)* const(void)* app.layoutData(void* src)layoutData((alias) app.TISInputSourceRef = void*TISInputSourceRef (parameter) void* srcsrc)
{
if (src is null)
return null;
(unresolved type) CFDataRefCFDataRef _error_ dd = cast((unresolved type) CFDataRefCFDataRef) TISGetInputSourceProperty(src, kTISPropertyUnicodeKeyLayoutData);
return d is null ? null : cast(const(void)*) CFDataGetBytePtr(d);
}
void void app.sourceId(void* src, char* buf, long bufSize)sourceId((alias) app.TISInputSourceRef = void*TISInputSourceRef (parameter) void* srcsrc, char* (parameter) char* bufbuf, long (parameter) long bufSizebufSize)
{
buf[0] = 0;
if (src is null)
return;
(unresolved type) CFStringRefCFStringRef _error_ idid = cast((unresolved type) CFStringRefCFStringRef) TISGetInputSourceProperty(src, kTISPropertyInputSourceID);
if (id !is null)
CFStringGetCString(id, buf, bufSize, kCFStringEncodingUTF8);
}
void void app.runLayoutDemos()runLayoutDemos()
{
instr("repeat_info", "delay_s=%.3f interval_s=%.3f",
NSEvent.NSEvent.keyRepeatDelaykeyRepeatDelay(), NSEvent.NSEvent.keyRepeatIntervalkeyRepeatInterval());
(unresolved type) TISInputSourceRefTISInputSourceRef _error_ curcur = TISCopyCurrentKeyboardLayoutInputSource();
char[256] _error_ idbufidbuf;
sourceId(cur, idbuf.ptr, idbuf.idbuf.lengthlength);
instr("input_source", "role=current id=%s", idbuf.ptr);
const(void)* _error_ curLayoutcurLayout = layoutData(cur);
if (curLayout !is null)
layoutDemo(curLayout, "current");
else
instr("uckey", "layout=current text=<no_uchr> note=current_source_has_no_unicode_layout");
// Enumerate installed sources; pull a German keylayout's uchr WITHOUT switching.
(unresolved type) CFArrayRefCFArrayRef _error_ listlist = TISCreateInputSourceList(null, true);
(unresolved type) TISInputSourceRefTISInputSourceRef _error_ germangerman = null;
if (list !is null)
{
immutable _error_ countcount = CFArrayGetCount(list);
foreach ((parameter) ii; 0 .. count)
{
auto _error_ srcsrc = cast((unresolved type) TISInputSourceRefTISInputSourceRef) CFArrayGetValueAtIndex(list, i);
sourceId(src, idbuf.ptr, idbuf.idbuf.lengthlength);
if (strstr(idbuf.ptr, "German") !is null && layoutData(src) !is null)
{
german = src;
break;
}
}
}
if (german !is null)
{
sourceId(german, idbuf.ptr, idbuf.idbuf.lengthlength);
instr("input_source", "role=german_found id=%s", idbuf.ptr);
layoutDemo(layoutData(german), "german");
// Attempt a runtime system layout switch and capture the status.
immutable _error_ stst = TISSelectInputSource(german);
instr("layout_switch", "target=german status=%d note=%s", st,
st == 0 ? "selected".ptr : "rejected".ptr);
if (st == 0 && cur !is null)
{
immutable _error_ backback = TISSelectInputSource(cur);
instr("layout_switch", "target=restore status=%d", back);
}
}
else
{
instr("input_source", "role=german_found id=<none> note=german_layout_not_installed");
}
if (cur !is null)
CFRelease(cur);
if (list !is null)
CFRelease(list);
}
// --- Route 2: real HID injection via CGEventPost --------------------------------
void void app.postCGKey(ushort code)postCGKey(ushort (parameter) ushort codecode)
{
void* _error_ downdown = CGEventCreateKeyboardEvent(null, code, true);
void* _error_ upup = CGEventCreateKeyboardEvent(null, code, false);
if (down is null || up is null)
{
instr("cgevent_post", "result=create_failed");
return;
}
CGEventPost(kCGSessionEventTap, down);
CGEventPost(kCGSessionEventTap, up);
CFRelease(down);
CFRelease(up);
}
// --- Run-loop script (route 1 injection + the CGEventPost probe) ------------------
void void app.stopApp()stopApp()
{
instr("step", "name=NSApp_stop");
g_app.g_app.stopstop(null);
// stop: only takes effect after the next event dispatch; post a synthetic
// application-defined event so [NSApp run] wakes up and returns (scaffold finding).
(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);
}
void void app.onStep()onStep()
{
if (g_step < cast(int) g_script.g_script.lengthlength)
{
immutable _error_ kk = g_script[g_step];
instr("inject", "route=1 label=%.*s code=%u repeat=%d",
cast(int) k.k.labellabel.k.label.lengthlength, k.k.labellabel.ptr, k.k.codecode, k.k.repeatrepeat ? 1 : 0);
g_win.g_win.sendEventsendEvent(makeKey(NSEventTypeKeyDown, k));
g_win.g_win.sendEventsendEvent(makeKey(NSEventTypeKeyUp, k));
}
else if (g_step == cast(int) g_script.g_script.lengthlength)
{
instr("step", "name=cgevent_post tap=session code=%u", g_cgWatchCode);
g_cgArrived = false;
postCGKey(g_cgWatchCode);
}
else if (g_step == cast(int) g_script.g_script.lengthlength + 3)
{
// Grace period elapsed: did the HID event reach our keyDown:?
instr("cgevent_result", "tap=session code=%u reached=%d note=%s",
g_cgWatchCode, g_cgArrived ? 1 : 0,
g_cgArrived ? "delivered".ptr : "blocked_or_not_routed".ptr);
}
else if (g_step >= cast(int) g_script.g_script.lengthlength + 4)
{
stopApp();
}
++g_step;
}
extern (Objective-C) class (class) app.StepTargetStepTarget : (class) app.NSObjectNSObject
{
static (class) app.StepTargetStepTarget app.StepTarget app.StepTarget.alloc()alloc() @selector("alloc");
(class) app.StepTargetStepTarget app.StepTarget app.StepTarget.init()init() @selector("init");
void void app.StepTarget.step(app.NSTimer t)step((class) app.NSTimerNSTimer (parameter) app.NSTimer tt) @selector("step:")
{
onStep();
}
}
int int D main()main()
{
immutable _error_ autoExitautoExit = getenv("WSI_AUTO_EXIT") !is null;
instrInit("APPKIT_F06");
instr("init_start", "auto_exit=%d", autoExit ? 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(360, 240));
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-f06-keyboard"));
g_view = KeyView.KeyView.allocalloc().KeyView.alloc().initWithFrameinitWithFrame(contentRect);
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();
instr("window_created", "win_num=%ld first_responder=KeyView", g_winNum);
// Synchronous layout-engine + input-source demonstrations (no run loop needed).
runLayoutDemos();
// Drive the route-1 script and the route-2 probe from a repeating timer inside
// [NSApp run] (interpretKeyEvents: needs the running loop / live input context).
(unresolved type) StepTargetStepTarget _error_ targettarget = StepTarget.StepTarget.allocalloc().StepTarget.alloc().initinit();
instr("step", "name=script_timer_start interval_ms=200");
NSTimer.NSTimer.scheduledTimerWithTimeIntervalscheduledTimerWithTimeInterval(0.2, target, SEL.register("step:"), null, true);
instr("step", "name=NSApp_run");
g_app.g_app.runrun();
instr("loop_exit", "steps=%d", g_step);
return 0;
}