// X11 F07 demo — IME / text input via XIM (../../../features/f07-text-input.md).
// Built on the scaffold (../scaffold/app.d): same ImportC binding style, same
// poll(2)-driven readiness loop, same instrument.d event log.
//
// An editable line with a visible caret, rendered as colored block cells
// (committed = colored blocks, pre-edit = gray blocks + underline, caret =
// black bar; the *real* strings go to the log). On top of that, the full XIM
// bring-up with every pathology instrumented:
//
// * Locale coupling: setlocale(LC_ALL, "") -> XSupportsLocale ->
// XSetLocaleModifiers("") are all logged — run with LC_ALL=C vs C.UTF-8
// (the run.sh driver does) to watch the IM stack degrade.
// * XOpenIM timed (for the XMODIFIERS=@im=nonexistent measurement), the
// offered style list dumped, and the negotiated style logged after a
// preference walk: PreeditPosition -> PreeditCallbacks -> PreeditNothing
// -> PreeditNone (WSI_XIM_STYLE=callbacks|nothing reorders it).
// * The XFilterEvent gate: EVERY event passes through it first; swallowed
// events are logged `filtered type=… keycode=…` so the IM's appetite is
// visible.
// * Xutf8LookupString in the key path; XNSpotLocation re-reported on every
// caret move (over-the-spot candidate anchoring); preedit callbacks
// (start/draw/caret/done) implemented for the on-the-spot style.
// * XNDestroyCallback on the IM + XRegisterIMInstantiateCallback on the
// display: an IM server starting *after* the demo (or dying under it) is
// observed live — flags set in the callbacks, reopened from the loop.
//
// Without an IM (XOpenIM failed and nothing instantiated) the demo falls
// back to XLookupString and keeps running. WSI_AUTO_EXIT=1 bounds the run
// (WSI_DURATION_MS, default 10 s). Headless-safe: no X server -> prints
// `SKIP:` and exits 0. Findings: ../../f07-text-input.md.
module (module) appapp;
import (module) cc; // ImportC: Xlib + Xutil (XIM lives in libX11 proper) + poll
import (module) instrumentinstrument;
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_long, c_ulong;
import (package) corecore.(package) core.stdcstdc.(module) core.stdc.localeD header file for C99.
pubs.opengroup.org/onlinepubs/009695399/basedefs/locale.h.html, locale.h
Source
core/stdc/locale.d
locale : (alias constant) app.LC_ALL = int core.stdc.locale.LC_ALL = 6LC_ALL, (alias) app.setlocale = char* core.stdc.locale.setlocale(int category, scope const(char*) locale) nothrow @nogc @systemsetlocale;
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.atoi = int core.stdc.stdlib.atoi(scope const(char*) nptr) nothrow @nogcatoi, (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.memmove = void* core.stdc.string.memmove(return scope void* s1, scope const(void*) s2, ulong n) pure nothrow @nogcmemmove, (alias) app.strlen = ulong core.stdc.string.strlen(scope const(char*) s) pure nothrow @nogcstrlen;
// ---------------------------------------------------------------------------
// Constants Xlib exposes as macros that ImportC cannot import; re-declared
// per the scaffold gotcha. The XN* "names" are string macros in Xlib.h.
enum : c_long
{
(enum value) app.KeyPressMask = 1LKeyPressMask = 1L << 0,
(enum value) app.KeyReleaseMask = 2LKeyReleaseMask = 1L << 1,
(enum value) app.ExposureMask = 32768LExposureMask = 1L << 15,
(enum value) app.StructureNotifyMask = 131072LStructureNotifyMask = 1L << 17,
(enum value) app.FocusChangeMask = 2097152LFocusChangeMask = 1L << 21,
}
enum // XEvent.type discriminators
{
(enum value) app.KeyPress = 2KeyPress = 2,
(enum value) app.KeyRelease = 3KeyRelease = 3,
(enum value) app.FocusIn = 9FocusIn = 9,
(enum value) app.FocusOut = 10FocusOut = 10,
(enum value) app.Expose = 12Expose = 12,
(enum value) app.MapNotify = 19MapNotify = 19,
(enum value) app.ConfigureNotify = 22ConfigureNotify = 22,
(enum value) app.ClientMessage = 33ClientMessage = 33,
}
enum (constant) int app.False = 0False = 0;
enum (constant) int app.True = 1True = 1;
enum (constant) int app.POLLIN = 1POLLIN = 0x001;
enum (constant) int app.ZPixmap = 2ZPixmap = 2;
enum (constant) int app.RevertToParent = 2RevertToParent = 2;
enum (constant) int app.CurrentTime = 0CurrentTime = 0;
enum : c_ulong // XIMStyle bits (Xlib.h macros)
{
(enum value) app.XIMPreeditArea = 1LUXIMPreeditArea = 0x0001,
(enum value) app.XIMPreeditCallbacks = 2LUXIMPreeditCallbacks = 0x0002,
(enum value) app.XIMPreeditPosition = 4LUXIMPreeditPosition = 0x0004,
(enum value) app.XIMPreeditNothing = 8LUXIMPreeditNothing = 0x0008,
(enum value) app.XIMPreeditNone = 16LUXIMPreeditNone = 0x0010,
(enum value) app.XIMStatusArea = 256LUXIMStatusArea = 0x0100,
(enum value) app.XIMStatusCallbacks = 512LUXIMStatusCallbacks = 0x0200,
(enum value) app.XIMStatusNothing = 1024LUXIMStatusNothing = 0x0400,
(enum value) app.XIMStatusNone = 2048LUXIMStatusNone = 0x0800,
}
enum // Xutf8LookupString status (Xlib.h macros)
{
(enum value) app.XBufferOverflow = -1XBufferOverflow = -1,
(enum value) app.XLookupNone = 1XLookupNone = 1,
(enum value) app.XLookupChars = 2XLookupChars = 2,
(enum value) app.XLookupKeySym = 3XLookupKeySym = 3,
(enum value) app.XLookupBoth = 4XLookupBoth = 4,
}
enum // keysyms the editor reacts to (keysymdef.h macros)
{
(enum value) app.XK_BackSpace = 65288XK_BackSpace = 0xff08,
(enum value) app.XK_Return = 65293XK_Return = 0xff0d,
(enum value) app.XK_Escape = 65307XK_Escape = 0xff1b,
(enum value) app.XK_Left = 65361XK_Left = 0xff51,
(enum value) app.XK_Right = 65363XK_Right = 0xff53,
}
// XN* argument names (string macros in Xlib.h).
immutable (immutable global) immutable(string) app.XNQueryInputStyleXNQueryInputStyle = "queryInputStyle";
immutable (immutable global) immutable(string) app.XNClientWindowXNClientWindow = "clientWindow";
immutable (immutable global) immutable(string) app.XNInputStyleXNInputStyle = "inputStyle";
immutable (immutable global) immutable(string) app.XNFocusWindowXNFocusWindow = "focusWindow";
immutable (immutable global) immutable(string) app.XNPreeditAttributesXNPreeditAttributes = "preeditAttributes";
immutable (immutable global) immutable(string) app.XNSpotLocationXNSpotLocation = "spotLocation";
immutable (immutable global) immutable(string) app.XNFontSetXNFontSet = "fontSet";
immutable (immutable global) immutable(string) app.XNFilterEventsXNFilterEvents = "filterEvents";
immutable (immutable global) immutable(string) app.XNDestroyCallbackXNDestroyCallback = "destroyCallback";
immutable (immutable global) immutable(string) app.XNPreeditStartCallbackXNPreeditStartCallback = "preeditStartCallback";
immutable (immutable global) immutable(string) app.XNPreeditDoneCallbackXNPreeditDoneCallback = "preeditDoneCallback";
immutable (immutable global) immutable(string) app.XNPreeditDrawCallbackXNPreeditDrawCallback = "preeditDrawCallback";
immutable (immutable global) immutable(string) app.XNPreeditCaretCallbackXNPreeditCaretCallback = "preeditCaretCallback";
// ---------------------------------------------------------------------------
// The editable line: committed text + pre-edit, both as arrays of UTF-8
// codepoint cells (block-cell rendering; the log carries the real bytes).
enum (constant) int app.MaxCells = 120MaxCells = 120;
struct (struct) app.CellLineCellLine
{
char[8][(constant) int app.MaxCells = 120MaxCells] (field) char[8][120] app.CellLine.cellscells;
ubyte[(constant) int app.MaxCells = 120MaxCells] (field) ubyte[120] app.CellLine.lenlen;
int (field) int app.CellLine.countcount, (field) int app.CellLine.caretcaret;
void void app.CellLine.insert(const(char)* bytes, int n) nothrow @nogcinsert(const(char)* (parameter) const(char)* bytesbytes, int (parameter) int nn) @nogc nothrow
{
int (local variable) int ii = 0;
while ((local variable) int ii < (parameter) int nn && (field) int app.CellLine.countcount < (constant) int app.MaxCells = 120MaxCells)
{
int (local variable) int clcl = 1; // codepoint length from the UTF-8 lead byte
while ((local variable) int ii + (local variable) int clcl < (parameter) int nn && ((parameter) const(char)* bytesbytes[(local variable) int ii + (local variable) int clcl] & 0xc0) == 0x80)
++(local variable) int clcl;
if ((local variable) int clcl > 8)
(local variable) int clcl = 8;
void* core.stdc.string.memmove(return scope void* s1, scope const(void*) s2, ulong n) pure nothrow @nogcmemmove(&(field) char[8][120] app.CellLine.cellscells[(field) int app.CellLine.caretcaret + 1], &(field) char[8][120] app.CellLine.cellscells[(field) int app.CellLine.caretcaret], ((field) int app.CellLine.countcount - (field) int app.CellLine.caretcaret) * 8);
void* core.stdc.string.memmove(return scope void* s1, scope const(void*) s2, ulong n) pure nothrow @nogcmemmove(&(field) ubyte[120] app.CellLine.lenlen[(field) int app.CellLine.caretcaret + 1], &(field) ubyte[120] app.CellLine.lenlen[(field) int app.CellLine.caretcaret], (field) int app.CellLine.countcount - (field) int app.CellLine.caretcaret);
(field) char[8][120] app.CellLine.cellscells[(field) int app.CellLine.caretcaret][0 .. (local variable) int clcl] = (parameter) const(char)* bytesbytes[(local variable) int ii .. (local variable) int ii + (local variable) int clcl];
(field) ubyte[120] app.CellLine.lenlen[(field) int app.CellLine.caretcaret] = cast(ubyte) (local variable) int clcl;
++(field) int app.CellLine.countcount;
++(field) int app.CellLine.caretcaret;
(local variable) int ii += (local variable) int clcl;
}
}
void void app.CellLine.remove(int first, int n) nothrow @nogcremove(int (parameter) int firstfirst, int (parameter) int nn) @nogc nothrow
{
if ((parameter) int firstfirst < 0 || (parameter) int firstfirst >= (field) int app.CellLine.countcount)
return;
if ((parameter) int firstfirst + (parameter) int nn > (field) int app.CellLine.countcount)
(parameter) int nn = (field) int app.CellLine.countcount - (parameter) int firstfirst;
void* core.stdc.string.memmove(return scope void* s1, scope const(void*) s2, ulong n) pure nothrow @nogcmemmove(&(field) char[8][120] app.CellLine.cellscells[(parameter) int firstfirst], &(field) char[8][120] app.CellLine.cellscells[(parameter) int firstfirst + (parameter) int nn], ((field) int app.CellLine.countcount - (parameter) int firstfirst - (parameter) int nn) * 8);
void* core.stdc.string.memmove(return scope void* s1, scope const(void*) s2, ulong n) pure nothrow @nogcmemmove(&(field) ubyte[120] app.CellLine.lenlen[(parameter) int firstfirst], &(field) ubyte[120] app.CellLine.lenlen[(parameter) int firstfirst + (parameter) int nn], (field) int app.CellLine.countcount - (parameter) int firstfirst - (parameter) int nn);
(field) int app.CellLine.countcount -= (parameter) int nn;
if ((field) int app.CellLine.caretcaret > (field) int app.CellLine.countcount)
(field) int app.CellLine.caretcaret = (field) int app.CellLine.countcount;
}
/// NUL-terminated concatenation, for the log.
const(char)* const(char)* app.CellLine.str(return ref char[1024] buf) nothrow @nogcNUL-terminated concatenation, for the log.
str(return ref char[1024] (parameter) char[1024] bufbuf) @nogc nothrow
{
(alias) object.size_t = ulongsize_t (local variable) ulong oo;
foreach ((local variable) int ii; 0 .. (field) int app.CellLine.countcount)
{
(parameter) char[1024] bufbuf[(local variable) ulong oo .. (local variable) ulong oo + (field) ubyte[120] app.CellLine.lenlen[(local variable) int ii]] = (field) char[8][120] app.CellLine.cellscells[(local variable) int ii][0 .. (field) ubyte[120] app.CellLine.lenlen[(local variable) int ii]];
(local variable) ulong oo += (field) ubyte[120] app.CellLine.lenlen[(local variable) int ii];
}
(parameter) char[1024] bufbuf[(local variable) ulong oo] = '\0';
return (parameter) char[1024] bufbuf.(constant) char* char[1024].ptr = &bufptr;
}
}
// ---------------------------------------------------------------------------
// Shared state the XIM callbacks mutate (single-threaded demo; the callbacks
// run inside XFilterEvent/XPending on this thread).
__gshared (struct) app.CellLineCellLine (__gshared global) app.CellLine app.g_textg_text; // committed
__gshared (struct) app.CellLineCellLine (__gshared global) app.CellLine app.g_preg_pre; // pre-edit (PreeditCallbacks style only)
__gshared bool (__gshared global) bool app.g_imDestroyedg_imDestroyed, (__gshared global) bool app.g_imInstantiatedg_imInstantiated, (__gshared global) bool app.g_dirtyg_dirty;
__gshared int (__gshared global) int app.g_preeditDrawsg_preeditDraws;
extern (C) int app.preeditStartpreeditStart(XIC (parameter) XIC icic, XPointer client, XPointer call) @nogc nothrow
{
emit("preedit_start (returning -1: no length limit)");
return -1;
}
extern (C) void app.preeditDonepreeditDone(XIC (parameter) XIC icic, XPointer client, XPointer call) @nogc nothrow
{
emit("preedit_done");
g_pre.g_pre.countcount = 0;
g_pre.g_pre.caretcaret = 0;
g_dirty = true;
}
extern (C) void app.preeditDrawpreeditDraw(XIC (parameter) XIC icic, XPointer client,
XIMPreeditDrawCallbackStruct* call) @nogc nothrow
{
++g_preeditDraws;
const _error_ mbmb = xim_text_mb(call.text);
emitf("preedit_draw", "caret=%d chg_first=%d chg_length=%d text=%s",
call.call.caretcaret, call.chg_first, call.chg_length,
mb !is null ? mb : "(null)"."(null)".ptrptr);
// Replace [chg_first, chg_first+chg_length) with the new text (the XIM
// delta protocol; text==null means pure deletion).
g_pre.g_pre.removeremove(call.chg_first, call.chg_length);
if (mb !is null)
{
g_pre.g_pre.caretcaret = call.chg_first;
g_pre.g_pre.insertinsert(mb, cast(int) strlen(mb));
}
g_pre.g_pre.caretcaret = call.call.caretcaret;
char[1024] _error_ bufbuf;
emitf("preedit_state", "text=%s caret=%d", g_pre.g_pre.strstr(buf), g_pre.g_pre.caretcaret);
g_dirty = true;
}
extern (C) void app.preeditCaretpreeditCaret(XIC (parameter) XIC icic, XPointer client,
XIMPreeditCaretCallbackStruct* call) @nogc nothrow
{
emitf("preedit_caret", "position=%d direction=%d style=%d",
call.position, call.direction, call.call.stylestyle);
g_pre.g_pre.caretcaret = call.position;
g_dirty = true;
}
extern (C) void app.imDestroyedimDestroyed(XIM (parameter) XIM imim, XPointer client, XPointer call) @nogc nothrow
{
// The IM server died. The XIM and all its XICs are already invalid —
// using (or closing) them is a use-after-free; just forget them.
emit("im_destroyed (XIM and XICs now invalid; must NOT XCloseIM)");
g_imDestroyed = true;
}
extern (C) void app.imInstantiatedimInstantiated(Display* (parameter) Display* dpydpy, XPointer client, XPointer call) @nogc nothrow
{
// An IM matching the locale modifiers just became available (e.g. an IM
// server started after us, or the built-in IM at registration time).
emit("im_instantiated");
g_imInstantiated = true;
}
// ---------------------------------------------------------------------------
// XIM bring-up: style negotiation + IC creation, every step logged.
const(char)* const(char)* app.styleName(ulong s, return ref char[64] buf) nothrow @nogcstyleName(c_ulong (parameter) ulong ss, return ref char[64] (parameter) char[64] bufbuf) @nogc nothrow
{
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) snprintf = int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogcsnprintf;
const(char)* (local variable) const(char)* prepre = ((parameter) ulong ss & (enum value) app.XIMPreeditPosition = 4LUXIMPreeditPosition) ? "Position"
: ((parameter) ulong ss & (enum value) app.XIMPreeditCallbacks = 2LUXIMPreeditCallbacks) ? "Callbacks" : ((parameter) ulong ss & (enum value) app.XIMPreeditArea = 1LUXIMPreeditArea) ? "Area"
: ((parameter) ulong ss & (enum value) app.XIMPreeditNothing = 8LUXIMPreeditNothing) ? "Nothing" : ((parameter) ulong ss & (enum value) app.XIMPreeditNone = 16LUXIMPreeditNone) ? "None" : "?";
const(char)* (local variable) const(char)* stst = ((parameter) ulong ss & (enum value) app.XIMStatusCallbacks = 512LUXIMStatusCallbacks) ? "Callbacks"
: ((parameter) ulong ss & (enum value) app.XIMStatusArea = 256LUXIMStatusArea) ? "Area"
: ((parameter) ulong ss & (enum value) app.XIMStatusNothing = 1024LUXIMStatusNothing) ? "Nothing" : ((parameter) ulong ss & (enum value) app.XIMStatusNone = 2048LUXIMStatusNone) ? "None" : "?";
int core.stdc.stdio.snprintf(scope char* s, ulong n, scope const(char*) format, scope const ...) nothrow @nogcsnprintf((parameter) char[64] bufbuf.(constant) char* char[64].ptr = &bufptr, (parameter) char[64] bufbuf.(constant) ulong char[64].length = 64LUlength, "Preedit%s|Status%s", (local variable) const(char)* prepre, (local variable) const(char)* stst);
return (parameter) char[64] bufbuf.(constant) char* char[64].ptr = &bufptr;
}
struct (struct) app.ImIm
{
XIM (field) _error_ app.Im.imim;
XIC (field) _error_ app.Im.icic;
c_ulong (field) ulong app.Im.stylestyle;
c_long (field) long app.Im.filterMaskfilterMask;
}
/// XOpenIM (timed) + destroy callback + style query/negotiation + XCreateIC.
/// Returns im=null when no IM is reachable for the current locale modifiers.
(struct) app.ImIm app.openInputMethodXOpenIM (timed) + destroy callback + style query/negotiation + XCreateIC.
Returns im=null when no IM is reachable for the current locale modifiers.
openInputMethod(Display* (parameter) Display* dpydpy, Window (parameter) Window winwin, XFontSet (parameter) XFontSet fsfs, ref XPoint (parameter) XPoint spotspot) nothrow
{
(unresolved type) ImIm _error_ rr;
const _error_ t0t0 = nowUs();
r.r.imim = XOpenIM(dpy, null, null, null);
emitf("step", "name=XOpenIM ok=%d took_us=%lld", cast(int)(r.r.imim !is null),
nowUs() - t0);
if (r.r.imim is null)
return r;
static XIMCallback XIMCallback destroyCbdestroyCb; // must outlive the IM
destroyCb.client_data = null;
destroyCb.callback = cast(XIMProc)&imDestroyed;
const _error_ dcErrdcErr = XSetIMValues(r.r.imim, XNDestroyCallback.XNDestroyCallback.ptrptr, &destroyCb, null);
emitf("step", "name=XSetIMValues destroy_callback=%s",
dcErr is null ? "registered"."registered".ptrptr : dcErr);
// What the IM offers, verbatim.
XIMStyles* _error_ stylesstyles;
char[64] _error_ nmnm;
if (XGetIMValues(r.r.imim, XNQueryInputStyle.XNQueryInputStyle.ptrptr, &styles, null) !is null
|| styles is null)
{
emit("step name=XGetIMValues queryInputStyle=FAILED");
return r;
}
foreach ((parameter) ii; 0 .. styles.count_styles)
emitf("xim_style_offered", "style=0x%04lx name=%s",
styles.supported_styles[i], styleName(styles.supported_styles[i], nm));
// Preference walk (F07 spec: Position first, fall back through Nothing).
const _error_ prefpref = getenv("WSI_XIM_STYLE");
c_ulong[4] _error_ wantedwanted = [
XIMPreeditPosition | XIMStatusNothing,
XIMPreeditCallbacks | XIMStatusNothing,
XIMPreeditNothing | XIMStatusNothing,
XIMPreeditNone | XIMStatusNone,
];
if (pref !is null && pref[0] == 'c') // callbacks (on-the-spot) first
wanted[0 .. 2] = [XIMPreeditCallbacks | XIMStatusNothing,
XIMPreeditPosition | XIMStatusNothing];
if (pref !is null && pref[0] == 'n') // nothing (root-window style) only
wanted = [XIMPreeditNothing | XIMStatusNothing,
XIMPreeditNone | XIMStatusNone, 0, 0];
foreach ((parameter) ww; wanted)
{
bool _error_ offeredoffered = false;
foreach ((parameter) ii; 0 .. styles.count_styles)
offered |= styles.supported_styles[i] == w;
if (w == 0 || !offered)
continue;
if (w & XIMPreeditPosition)
{
// Over-the-spot: the IM draws the pre-edit at XNSpotLocation
// with XNFontSet; both are *required* create-time attributes.
XVaNestedList _error_ plistplist = XVaCreateNestedList(0,
XNSpotLocation.XNSpotLocation.ptrptr, &spot, XNFontSet.XNFontSet.ptrptr, fs, null);
r.r.icic = XCreateIC(r.r.imim, XNInputStyle.XNInputStyle.ptrptr, w,
XNClientWindow.XNClientWindow.ptrptr, win, XNFocusWindow.XNFocusWindow.ptrptr, win,
XNPreeditAttributes.XNPreeditAttributes.ptrptr, plist, null);
XFree(plist);
}
else if (w & XIMPreeditCallbacks)
{
// On-the-spot: the app draws the pre-edit; the IM drives it
// through these four callbacks.
static XIMCallback XIMCallback startCbstartCb, XIMCallback doneCbdoneCb, XIMCallback drawCbdrawCb, XIMCallback caretCbcaretCb;
startCb.callback = cast(XIMProc)&preeditStart;
doneCb.callback = cast(XIMProc)&preeditDone;
drawCb.callback = cast(XIMProc)&preeditDraw;
caretCb.callback = cast(XIMProc)&preeditCaret;
XVaNestedList _error_ plistplist = XVaCreateNestedList(0,
XNPreeditStartCallback.XNPreeditStartCallback.ptrptr, &startCb,
XNPreeditDoneCallback.XNPreeditDoneCallback.ptrptr, &doneCb,
XNPreeditDrawCallback.XNPreeditDrawCallback.ptrptr, &drawCb,
XNPreeditCaretCallback.XNPreeditCaretCallback.ptrptr, &caretCb, null);
r.r.icic = XCreateIC(r.r.imim, XNInputStyle.XNInputStyle.ptrptr, w,
XNClientWindow.XNClientWindow.ptrptr, win, XNFocusWindow.XNFocusWindow.ptrptr, win,
XNPreeditAttributes.XNPreeditAttributes.ptrptr, plist, null);
XFree(plist);
}
else
r.r.icic = XCreateIC(r.r.imim, XNInputStyle.XNInputStyle.ptrptr, w,
XNClientWindow.XNClientWindow.ptrptr, win, XNFocusWindow.XNFocusWindow.ptrptr, win, null);
if (r.r.icic !is null)
{
r.r.stylestyle = w;
emitf("xim_style_negotiated", "style=0x%04lx name=%s", w,
styleName(w, nm));
break;
}
emitf("step", "name=XCreateIC style=0x%04lx FAILED (falling back)", w);
}
XFree(styles);
if (r.r.icic is null)
return r;
// The IM may need events the app did not select (e.g. the built-in IM
// wants KeyRelease for compose) — the app MUST add XNFilterEvents to its
// own mask or XFilterEvent never sees them.
XGetICValues(r.r.icic, XNFilterEvents.XNFilterEvents.ptrptr, &r.r.filterMaskfilterMask, null);
emitf("step", "name=XGetICValues filterEvents=0x%lx", r.r.filterMaskfilterMask);
return r;
}
// ---------------------------------------------------------------------------
// Block-cell rendering: committed cells colored by codepoint, pre-edit cells
// gray with an underline, caret as a black bar. F07 req 1, sans fonts.
enum (constant) int app.CellW = 14CellW = 14, (constant) int app.CellH = 22CellH = 22, (constant) int app.OriginX = 12OriginX = 12, (constant) int app.OriginY = 40OriginY = 40;
void app.drawdraw(XImage* (parameter) XImage* imgimg) @nogc nothrow
{
const _error_ ww = img.img.widthwidth, _error_ hh = img.img.heightheight;
if (img.bits_per_pixel != 32)
return;
foreach ((parameter) yy; 0 .. h)
{
auto _error_ rowrow = cast(uint*)(img.data + y * img.bytes_per_line);
row[0 .. w] = 0xf0f0e8;
if (y == 0 || y == h - 1)
row[0 .. w] = 0;
row[0] = 0;
row[w - 1] = 0;
}
void void cell(int i, uint color, bool underline)cell(int (parameter) int ii, uint (parameter) uint colorcolor, bool (parameter) bool underlineunderline)
{
const _error_ x0x0 = OriginX + i * CellW;
if (x0 + CellW >= w)
return;
foreach ((parameter) yy; OriginY .. OriginY + CellH)
{
auto _error_ rowrow = cast(uint*)(img.data + y * img.bytes_per_line);
const _error_ ulul = underline && y >= OriginY + CellH - 3;
row[x0 + 1 .. x0 + CellW - 1] = ul ? 0x303030 : color;
}
}
// committed │ pre-edit (inline at the caret) │ rest of committed
int _error_ colcol = 0;
foreach ((parameter) ii; 0 .. g_text.g_text.caretcaret)
cell(col++, 0x4060c0 + (g_text.g_text.cellscells[i][0] & 0x3f) * 0x300, false);
const _error_ preStartpreStart = col;
foreach ((parameter) ii; 0 .. g_pre.g_pre.countcount)
cell(col++, 0xb0b0b0, true);
foreach ((parameter) ii; g_text.g_text.caretcaret .. g_text.g_text.countcount)
cell(col++, 0x4060c0 + (g_text.g_text.cellscells[i][0] & 0x3f) * 0x300, false);
// caret bar (inside the pre-edit while composing)
const _error_ cxcx = OriginX + (preStart + g_pre.g_pre.caretcaret) * CellW;
foreach ((parameter) yy; OriginY - 2 .. OriginY + CellH + 2)
{
auto _error_ rowrow = cast(uint*)(img.data + y * img.bytes_per_line);
if (cx + 2 < w)
row[cx .. cx + 2] = 0;
}
}
int int D main()main()
{
void instrument.initInstrument(const(char)* demoName) nothrow @nogcNames the demo, starts the monotonic clock, and emits init_start.
Call this first, before any platform API call.
initInstrument("f07_x11");
const (local variable) const(char*) envAutoenvAuto = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_AUTO_EXIT");
const (local variable) const(bool) autoExitautoExit = (local variable) const(char*) envAutoenvAuto !is null && (local variable) const(char*) envAutoenvAuto[0] == '1';
const (local variable) const(char*) envDurenvDur = char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("WSI_DURATION_MS");
const (local variable) const(int) durationMsdurationMs = (local variable) const(char*) envDurenvDur !is null ? int core.stdc.stdlib.atoi(scope const(char*) nptr) nothrow @nogcatoi((local variable) const(char*) envDurenvDur) : 10_000;
// -- The locale coupling, step by step (F07's first pathology) ------------
// XIM is the only input path in this tree whose availability depends on
// the C runtime locale: XOpenIM consults it (and XMODIFIERS) at open time.
const (local variable) const(char*) locloc = char* core.stdc.locale.setlocale(int category, scope const(char*) locale) nothrow @nogc @systemsetlocale((constant) int core.stdc.locale.LC_ALL = 6LC_ALL, "");
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=setlocale result=%s", (local variable) const(char*) locloc !is null ? (local variable) const(char*) locloc : "(failed)".(constant) immutable(char)* "(failed)".ptr = "(failed)"ptr);
const (local variable) const(_error_) supportedsupported = XSupportsLocale();
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=XSupportsLocale supported=%d", supported);
const (local variable) const(_error_) modsmods = XSetLocaleModifiers("");
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=XSetLocaleModifiers result=%s XMODIFIERS=%s",
mods !is null ? mods : "(failed)".ptr,
char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("XMODIFIERS") !is null ? char* core.stdc.stdlib.getenv(scope const(char*) name) nothrow @nogcgetenv("XMODIFIERS") : "(unset)".(constant) immutable(char)* "(unset)".ptr = "(unset)"ptr);
Display* (local variable) _error_ dpydpy = XOpenDisplay(null);
if (dpy is null)
{
int core.stdc.stdio.printf(scope const(char*) format, scope const ...) nothrow @nogcprintf("SKIP: no X11 display (XOpenDisplay returned null)\n");
return 0;
}
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=XOpenDisplay fd=%d", XConnectionNumber(dpy));
const (local variable) const(_error_) screenscreen = XDefaultScreen(dpy);
Visual* (local variable) _error_ visualvisual = XDefaultVisual(dpy, screen);
const (local variable) const(_error_) depthdepth = XDefaultDepth(dpy, screen);
// -- Window ----------------------------------------------------------------
int (local variable) int widthwidth = 640, (local variable) int heightheight = 120;
Window (local variable) _error_ winwin = XCreateSimpleWindow(dpy, XRootWindow(dpy, screen), 0, 0,
width, height, 1, XBlackPixel(dpy, screen), XWhitePixel(dpy, screen));
XStoreName(dpy, win, "Sparkles · X11 F07 text input");
Atom (local variable) _error_ wmDeletewmDelete = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
XSetWMProtocols(dpy, win, &wmDelete, 1);
enum c_long (constant) long app.main.baseMask = 2260995LbaseMask = (enum value) app.KeyPressMask = 1LKeyPressMask | (enum value) app.KeyReleaseMask = 2LKeyReleaseMask | (enum value) app.ExposureMask = 32768LExposureMask
| (enum value) app.StructureNotifyMask = 131072LStructureNotifyMask | (enum value) app.FocusChangeMask = 2097152LFocusChangeMask;
XSelectInput(dpy, win, baseMask);
XMapWindow(dpy, win);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("window_created", "xid=0x%lx size=%dx%d", win, (local variable) int widthwidth, (local variable) int heightheight);
// -- Fontset (required by XIMPreeditPosition) ------------------------------
char** (local variable) char** missingmissing;
int (local variable) int nMissingnMissing;
char* (local variable) char* defStrdefStr;
XFontSet (local variable) _error_ fsfs = XCreateFontSet(dpy,
"-*-fixed-medium-r-normal--14-*-*-*-*-*-*-*,-*-*-*-*-*-*-14-*-*-*-*-*-*-*",
&missing, &nMissing, &defStr);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("step", "name=XCreateFontSet ok=%d missing_charsets=%d",
cast(int)(fs !is null), (local variable) int nMissingnMissing);
if ((local variable) char** missingmissing !is null)
XFreeStringList(missing);
// -- IM lifecycle hooks, then the IM itself --------------------------------
// Registered BEFORE XOpenIM so an IM server that starts later (fcitx5 in
// the run.sh choreography) is announced; Xlib also fires it immediately
// when an IM for the current modifiers is already reachable.
XRegisterIMInstantiateCallback(dpy, null, null, null,
cast(XIDProc)&imInstantiated, null);
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("step name=XRegisterIMInstantiateCallback");
XPoint (local variable) _error_ spotspot = XPoint(OriginX, cast(short)(OriginY + CellH)); // caret baseline
(struct) app.ImIm (local variable) _error_ imim = openInputMethod(dpy, win, fs, spot);
if (im.(field) _error_ im.icic !is null && im.(field) _error_ im.filterMaskfilterMask != 0)
XSelectInput(dpy, win, baseMask | im.im.filterMaskfilterMask);
GC (local variable) _error_ gcgc = XDefaultGC(dpy, screen);
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) malloc = void* core.stdc.stdlib.malloc(ulong size) nothrow @nogcmalloc;
XImage* (local variable) _error_ imgimg = XCreateImage(dpy, visual, cast(uint) depth, ZPixmap, 0,
cast(char*) malloc(cast((unresolved type) size_tsize_t) width * height * 4),
cast(uint) width, cast(uint) height, 32, 0);
// -- Helpers bound to the loop's state -------------------------------------
char[1024] (local variable) char[1024] lineBuflineBuf;
void void app.main.updateSpot() nothrow @nogc @systemupdateSpot()
{
if (im.(field) _error_ im.icic is null || !(im.(field) _error_ im.stylestyle & (enum value) app.XIMPreeditPosition = 4LUXIMPreeditPosition))
return;
spot.x = cast(short)(OriginX + g_text.g_text.caretcaret * CellW);
spot.spot.yy = cast(short)(OriginY + CellH);
XVaNestedList (local variable) _error_ plistplist = XVaCreateNestedList(0, XNSpotLocation.XNSpotLocation.ptrptr, &spot, null);
XSetICValues(im.im.icic, XNPreeditAttributes.XNPreeditAttributes.ptrptr, plist, null);
XFree(plist);
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("spot", "x=%d y=%d (caret cell %d)", spot.x, spot.(field) _error_ spot.yy, (__gshared global) app.CellLine app.g_textg_text.(field) int app.CellLine.caretcaret);
}
const (local variable) const(_error_) fdfd = XConnectionNumber(dpy);
bool (local variable) bool runningrunning = true, (local variable) bool focusedfocused = false;
int (local variable) int commitscommits = 0, (local variable) int filteredfiltered = 0, (local variable) int keyEventskeyEvents = 0;
(__gshared global) bool app.g_dirtyg_dirty = true;
while ((local variable) bool runningrunning)
{
while (XPending(dpy) > 0)
{
XEvent (local variable) _error_ evev;
XNextEvent(dpy, &ev);
// THE GATE. Every event — not just key events — must be offered
// to the IM first; a True return means the IM swallowed it (it
// becomes pre-edit fuel / compose state) and the app must NOT
// process it. Skipping this breaks every XIM on earth.
if (XFilterEvent(&ev, 0) == True)
{
++(local variable) int filteredfiltered;
if (ev.type == (enum value) app.KeyPress = 2KeyPress || ev.type == (enum value) app.KeyRelease = 3KeyRelease)
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("filtered", "type=%s keycode=%u",
ev.type == (enum value) app.KeyPress = 2KeyPress ? "KeyPress".ptr : "KeyRelease".ptr,
ev.xkey.keycode);
else
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("filtered", "type=%d", ev.type);
continue;
}
switch (ev.type)
{
case KeyPress:
++keyEvents;
char[256] _error_ bufbuf;
KeySym _error_ symsym = 0;
int _error_ statusstatus = XLookupNone, _error_ nn = 0;
if (im.im.icic !is null)
{
n = Xutf8LookupString(im.im.icic, &ev.xkey, buf.buf.ptrptr,
cast(int) buf.buf.lengthlength - 1, &sym, &status);
}
else // no IM at all: raw Latin-1 fallback path
{
n = XLookupString(&ev.xkey, buf.buf.ptrptr, cast(int) buf.buf.lengthlength - 1,
&sym, null);
status = n > 0 ? XLookupBoth : XLookupKeySym;
}
buf[n] = '\0';
emitf("lookup", "via=%s status=%d sym=0x%lx len=%d text=%s",
im.im.icic !is null ? "Xutf8LookupString"."Xutf8LookupString".ptrptr : "XLookupString"."XLookupString".ptrptr,
status, sym, n, buf.buf.ptrptr);
if ((status == XLookupChars || status == XLookupBoth) && n > 0)
{
g_text.g_text.insertinsert(buf.buf.ptrptr, n);
++commits;
emitf("commit", "text=%s line=%s caret=%d", buf.buf.ptrptr,
g_text.g_text.strstr(lineBuf), g_text.g_text.caretcaret);
updateSpot();
g_dirty = true;
}
if (status == XLookupKeySym || status == XLookupBoth)
{
const _error_ oldold = g_text.g_text.caretcaret;
if (sym == XK_BackSpace && g_text.g_text.caretcaret > 0)
g_text.g_text.removeremove(--g_text.g_text.caretcaret, 1);
else if (sym == XK_Left && g_text.g_text.caretcaret > 0)
--g_text.g_text.caretcaret;
else if (sym == XK_Right && g_text.g_text.caretcaret < g_text.g_text.countcount)
++g_text.g_text.caretcaret;
else if (sym == XK_Return)
{
emitf("line_submitted", "text=%s", g_text.g_text.strstr(lineBuf));
g_text.g_text.countcount = 0;
g_text.g_text.caretcaret = 0;
}
else if (sym == XK_Escape)
break; // reaches us only when the IM didn't want it
if (g_text.g_text.caretcaret != old || sym == XK_BackSpace || sym == XK_Return)
{
emitf("caret", "cell=%d line=%s", g_text.g_text.caretcaret,
g_text.g_text.strstr(lineBuf));
updateSpot();
g_dirty = true;
}
}
break;
case KeyRelease:
++keyEvents;
break;
case MapNotify:
// Bare Xvfb has no WM: self-focus so injected XTEST events
// land here (same as the F06 demo).
XSetInputFocus(dpy, win, RevertToParent, CurrentTime);
XFlush(dpy);
break;
case FocusIn:
focused = true;
emit("focus state=in");
if (im.im.icic !is null)
XSetICFocus(im.im.icic);
break;
case FocusOut:
focused = false;
emitf("focus", "state=out preedit_pending=%d", g_pre.g_pre.countcount);
if (im.im.icic !is null)
XUnsetICFocus(im.im.icic);
break;
case Expose:
if (ev.xexpose.ev.xexpose.countcount == 0)
g_dirty = true;
break;
case ClientMessage:
if (cast(Atom) ev.xclient.data.l[0] == wmDelete)
running = false;
break;
default:
break;
}
}
// IM lifecycle transitions signalled from the callbacks.
if ((__gshared global) bool app.g_imDestroyedg_imDestroyed)
{
(__gshared global) bool app.g_imDestroyedg_imDestroyed = false;
im = Im.init; // XIM/XIC are already freed by Xlib — just forget
(__gshared global) app.CellLine app.g_preg_pre.(field) int app.CellLine.countcount = 0;
(__gshared global) app.CellLine app.g_preg_pre.(field) int app.CellLine.caretcaret = 0;
(__gshared global) bool app.g_dirtyg_dirty = true;
}
if ((__gshared global) bool app.g_imInstantiatedg_imInstantiated)
{
(__gshared global) bool app.g_imInstantiatedg_imInstantiated = false;
if (im.(field) _error_ im.imim is null)
{
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("step name=reopen_im (instantiate callback fired)");
im = openInputMethod(dpy, win, fs, spot);
if (im.(field) _error_ im.icic !is null)
{
if (im.(field) _error_ im.filterMaskfilterMask != 0)
XSelectInput(dpy, win, baseMask | im.im.filterMaskfilterMask);
if ((local variable) bool focusedfocused)
XSetICFocus(im.im.icic);
void app.main.updateSpot() nothrow @nogc @systemupdateSpot();
}
}
}
if ((__gshared global) bool app.g_dirtyg_dirty && img !is null)
{
draw(img);
XPutImage(dpy, win, gc, img, 0, 0, 0, 0,
cast(uint) width, cast(uint) height);
XFlush(dpy);
(__gshared global) bool app.g_dirtyg_dirty = false;
}
if ((local variable) const(bool) autoExitautoExit && long instrument.nowUs() nothrow @nogcMicroseconds elapsed since initInstrument.
nowUs() > cast(long) (local variable) const(int) durationMsdurationMs * 1000)
{
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("auto_exit");
break;
}
pollfd (local variable) _error_ pfdpfd;
pfd.pfd.fdfd = fd;
pfd.events = POLLIN;
pfd.revents = 0;
poll(&pfd, 1, autoExit ? 100 : -1);
}
char[64] (local variable) char[64] nmnm;
void instrument.emitf(scope const(char)* kind, scope const(char)* fmt, ...) nothrow @nogcEmit an event with a printf-formatted key=value ... payload.
emitf("summary", "im=%d style=%s commits=%d filtered=%d key_events=%d "
~ "preedit_draws=%d line=%s", cast(int)(im.(field) _error_ im.imim !is null),
im.(field) _error_ im.imim !is null ? const(char)* app.styleName(ulong s, return ref char[64] buf) nothrow @nogcstyleName(im.(field) _error_ im.stylestyle, (local variable) char[64] nmnm) : "(none)".ptr,
(local variable) int commitscommits, (local variable) int filteredfiltered, (local variable) int keyEventskeyEvents, (__gshared global) int app.g_preeditDrawsg_preeditDraws, (__gshared global) app.CellLine app.g_textg_text.const(char)* app.CellLine.str(return ref char[1024] buf) nothrow @nogcNUL-terminated concatenation, for the log.
str((local variable) char[1024] lineBuflineBuf));
if (im.(field) _error_ im.icic !is null)
XDestroyIC(im.im.icic);
if (im.(field) _error_ im.imim !is null)
XCloseIM(im.im.imim);
img.f.destroy_image(img);
XDestroyWindow(dpy, win);
XCloseDisplay(dpy);
void instrument.emit(scope const(char)* kind) nothrow @nogcEmit an event with no key=value payload.
emit("teardown");
return 0;
}