app.dhover×410 error×122all
// F07 — IME / text input, Win32 implementation
// (../../../features/f07-text-input.md). Extends the scaffold
// (../scaffold/app.d) into an IME observatory with two layers:
//
//   * TSF probe (the spec-mandated decision record): a minimal COM bring-up of
//     the Text Services Framework from plain D — CoInitializeEx ->
//     CoCreateInstance(CLSID_TF_ThreadMgr) -> ITfThreadMgr.Activate ->
//     CreateDocumentMgr -> CreateContext(punk=null) -> Push -> AssociateFocus.
//     druntime's core.sys.windows has no msctf.h projection, so the two vtbls
//     and three GUIDs are hand-declared below. The probe logs every HRESULT
//     and a tsf_verdict, then tears everything down: real TSF text input
//     requires the app to implement ITextStoreACP (~30 methods) + sink
//     interfaces, which is the documented reason the demo's editor speaks
//     IMM32 (see the findings doc, ../../f07-text-input.md).
//   * IMM32 editor (implemented fully): an editable line with a caret
//     (CreateCaret/SetCaretPos), the WM_IME_STARTCOMPOSITION ->
//     WM_IME_COMPOSITION (ImmGetCompositionStringW GCS_COMPSTR + GCS_COMPATTR
//     + GCS_CURSORPOS, GCS_RESULTSTR) -> WM_IME_ENDCOMPOSITION choreography,
//     inline underlined pre-edit rendering, and ImmSetCandidateWindow
//     (CANDIDATEFORM CFS_EXCLUDE anchored at the caret, re-set on every caret
//     move). WM_IME_SETCONTEXT / WM_IME_NOTIFY / ImmGetDefaultIMEWnd are
//     logged; an ImmAssociateContext(null) probe disables and restores the IME.
//
// Under Wine without a real IME the composition messages cannot fire from
// typing, so a WSI_AUTO_EXIT=1 script probes what imm32 provides: typed ASCII
// (caret + candidate-anchor movement), an ImmSetCompositionStringW(SCS_SETSTR)
// self-injection, NI_COMPOSITIONSTR commit/cancel, and the associate-null
// round-trip. The full CJK choreography is the Tier-C manual script in the
// findings doc.
//
// Only druntime's built-in core.sys.windows bindings — no third-party packages.
module 
(module) app
app
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.windows
windows
.
(module) core.sys.windows.windows

Windows API header module

Translated from MinGW API for MS-Windows 4.0

Source

core/sys/windows/windows.d

windows
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.windows
windows
.
(module) core.sys.windows.imm

Windows API header module

Translated from MinGW Windows headers

Source

core/sys/windows/imm.d

imm
;
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.windows
windows
.
(module) core.sys.windows.objbase

Windows API header module

Translated from MinGW Windows headers

Source

core/sys/windows/objbase.d

objbase
: CoInitializeEx, CoUninitialize, CoCreateInstance, COINIT;
module `core.sys.windows.objbase` import `CoInitializeEx` not found
module `core.sys.windows.objbase` import `CoUninitialize` not found
module `core.sys.windows.objbase` import `CoCreateInstance` not found
module `core.sys.windows.objbase` import `COINIT` not found
import
(package) core
core
.
(package) core.sys
sys
.
(package) core.sys.windows
windows
.
(module) core.sys.windows.wtypes

Windows API header module

Translated from MinGW Windows headers

Source

core/sys/windows/wtypes.d

wtypes
: CLSCTX;
module `core.sys.windows.wtypes` import `CLSCTX` not found
import instrument;
unable to read module `instrument` Expected 'instrument.d' or 'instrument/package.d' in one of the following import paths:
unable to read module `instrument` Expected 'instrument.d' or 'instrument/package.d' in one of the following import paths:
pragma(lib, "imm32"); pragma(lib, "ole32"); // --------------------------------------------------------------------------- // druntime's core.sys.windows.imm declares `alias DWORD HIMC` — but the real // HIMC is a pointer-sized DECLARE_HANDLE, so on Win64 those prototypes // truncate the handle (and none are nothrow, which a WndProc needs). The // constants/structs (WM_IME_*, GCS_*, CFS_*, CANDIDATEFORM, …) are fine; // the used function surface is redeclared correctly here (module-level // declarations take precedence over imports). A finding in its own right — // see ../../f07-text-input.md. alias
(alias) app.HIMC = _error_
HIMC
= HANDLE;
undefined identifier `HANDLE`
extern (Windows) nothrow @nogc {
(alias) app.HIMC = _error_
HIMC
app.ImmGetContext
ImmGetContext
(HWND);
undefined identifier `HWND`
BOOL
app.ImmReleaseContext
ImmReleaseContext
(HWND,
(alias) app.HIMC = _error_
HIMC
);
undefined identifier `BOOL`
undefined identifier `HWND`
(alias) app.HIMC = _error_
HIMC
app.ImmAssociateContext
ImmAssociateContext
(HWND,
(alias) app.HIMC = _error_
HIMC
);
undefined identifier `HWND`
HWND
app.ImmGetDefaultIMEWnd
ImmGetDefaultIMEWnd
(HWND);
undefined identifier `HWND`
undefined identifier `HWND`
BOOL
app.ImmGetOpenStatus
ImmGetOpenStatus
(
(alias) app.HIMC = _error_
HIMC
);
undefined identifier `BOOL`
BOOL
app.ImmGetConversionStatus
ImmGetConversionStatus
(
(alias) app.HIMC = _error_
HIMC
, LPDWORD, LPDWORD);
undefined identifier `BOOL`
undefined identifier `LPDWORD`
undefined identifier `LPDWORD`
UINT
app.ImmGetDescriptionW
ImmGetDescriptionW
(HKL, LPWSTR, UINT);
undefined identifier `UINT`
undefined identifier `HKL`
undefined identifier `LPWSTR`
undefined identifier `UINT`
LONG
app.ImmGetCompositionStringW
ImmGetCompositionStringW
(
(alias) app.HIMC = _error_
HIMC
, DWORD, PVOID, DWORD);
undefined identifier `LONG`
undefined identifier `DWORD`
undefined identifier `PVOID`
undefined identifier `DWORD`
BOOL
app.ImmSetCompositionStringW
ImmSetCompositionStringW
(
(alias) app.HIMC = _error_
HIMC
, DWORD, PCVOID, DWORD, PCVOID, DWORD);
undefined identifier `BOOL`
undefined identifier `DWORD`
undefined identifier `PCVOID`
undefined identifier `DWORD`
undefined identifier `PCVOID`
undefined identifier `DWORD`
BOOL
app.ImmNotifyIME
ImmNotifyIME
(
(alias) app.HIMC = _error_
HIMC
, DWORD, DWORD, DWORD);
undefined identifier `BOOL`
undefined identifier `DWORD`
undefined identifier `DWORD`
undefined identifier `DWORD`
BOOL
app.ImmSetCandidateWindow
ImmSetCandidateWindow
(
(alias) app.HIMC = _error_
HIMC
, PCANDIDATEFORM);
undefined identifier `BOOL`
undefined identifier `PCANDIDATEFORM`
BOOL
app.ImmSetCompositionWindow
ImmSetCompositionWindow
(
(alias) app.HIMC = _error_
HIMC
, PCOMPOSITIONFORM);
undefined identifier `BOOL`
undefined identifier `PCOMPOSITIONFORM`
} // --------------------------------------------------------------------------- // TSF surface — hand-declared. druntime has IUnknown but nothing from msctf.h; // these are the exact vtbl orders of the msctf.h interfaces (Windows SDK). alias
(alias) app.TfClientId = _error_
TfClientId
= DWORD;
undefined identifier `DWORD`
alias
(alias) app.TfEditCookie = _error_
TfEditCookie
= DWORD;
undefined identifier `DWORD`
enum
(constant) int app.TF_POPF_ALL = 1
TF_POPF_ALL
= 0x1;
interface
(interface) app.ITfContext
ITfContext
: IUnknown {} // opaque here — no method is called on it
undefined identifier `IUnknown`
interface
(interface) app.ITfDocumentMgr
ITfDocumentMgr
: IUnknown
undefined identifier `IUnknown`
{ extern (Windows) nothrow: HRESULT
app.ITfDocumentMgr.CreateContext
CreateContext
(
(alias) app.TfClientId = _error_
TfClientId
tidOwner, DWORD dwFlags, IUnknown punk,
undefined identifier `HRESULT`
undefined identifier `DWORD`
undefined identifier `IUnknown`
(interface) app.ITfContext
ITfContext
* ppic,
(alias) app.TfEditCookie = _error_
TfEditCookie
* pecTextStore);
HRESULT
app.ITfDocumentMgr.Push
Push
(
(interface) app.ITfContext
ITfContext
pic);
undefined identifier `HRESULT`
HRESULT
app.ITfDocumentMgr.Pop
Pop
(DWORD dwFlags);
undefined identifier `HRESULT`
undefined identifier `DWORD`
HRESULT
app.ITfDocumentMgr.GetTop
GetTop
(
(interface) app.ITfContext
ITfContext
* ppic);
undefined identifier `HRESULT`
HRESULT
app.ITfDocumentMgr.GetBase
GetBase
(
(interface) app.ITfContext
ITfContext
* ppic);
undefined identifier `HRESULT`
HRESULT
app.ITfDocumentMgr.EnumContexts
EnumContexts
(void** ppEnum);
undefined identifier `HRESULT`
} interface
(interface) app.ITfThreadMgr
ITfThreadMgr
: IUnknown
undefined identifier `IUnknown`
{ extern (Windows) nothrow: HRESULT
app.ITfThreadMgr.Activate
Activate
(
(alias) app.TfClientId = _error_
TfClientId
* ptid);
undefined identifier `HRESULT`
HRESULT
app.ITfThreadMgr.Deactivate
Deactivate
();
undefined identifier `HRESULT`
HRESULT
app.ITfThreadMgr.CreateDocumentMgr
CreateDocumentMgr
(
(interface) app.ITfDocumentMgr
ITfDocumentMgr
* ppdim);
undefined identifier `HRESULT`
HRESULT
app.ITfThreadMgr.EnumDocumentMgrs
EnumDocumentMgrs
(void** ppEnum);
undefined identifier `HRESULT`
HRESULT
app.ITfThreadMgr.GetFocus
GetFocus
(
(interface) app.ITfDocumentMgr
ITfDocumentMgr
* ppdimFocus);
undefined identifier `HRESULT`
HRESULT
app.ITfThreadMgr.SetFocus
SetFocus
(
(interface) app.ITfDocumentMgr
ITfDocumentMgr
pdimFocus);
undefined identifier `HRESULT`
HRESULT
app.ITfThreadMgr.AssociateFocus
AssociateFocus
(HWND
(parameter) HWND hwnd
hwnd
,
(interface) app.ITfDocumentMgr
ITfDocumentMgr
pdimNew,
(interface) app.ITfDocumentMgr
ITfDocumentMgr
* ppdimPrev);
undefined identifier `HRESULT`
undefined identifier `HWND`
HRESULT
app.ITfThreadMgr.IsThreadFocus
IsThreadFocus
(BOOL* pfThreadFocus);
undefined identifier `HRESULT`
undefined identifier `BOOL`
HRESULT
app.ITfThreadMgr.GetFunctionProvider
GetFunctionProvider
(REFCLSID clsid, void** ppFuncProv);
undefined identifier `HRESULT`
undefined identifier `REFCLSID`
HRESULT
app.ITfThreadMgr.EnumFunctionProviders
EnumFunctionProviders
(void** ppEnum);
undefined identifier `HRESULT`
HRESULT
app.ITfThreadMgr.GetGlobalCompartment
GetGlobalCompartment
(void** ppCompMgr);
undefined identifier `HRESULT`
} immutable GUID
(immutable global) immutable(_error_) app.CLSID_TF_ThreadMgr
CLSID_TF_ThreadMgr
=
undefined identifier `GUID`
{0x529a9e6b, 0x6587, 0x4f23, [0xab, 0x9e, 0x9c, 0x7d, 0x68, 0x3e, 0x3c, 0x50]}; immutable GUID
(immutable global) immutable(_error_) app.IID_ITfThreadMgr
IID_ITfThreadMgr
=
undefined identifier `GUID`
{0xaa80e801, 0x2021, 0x11d2, [0x93, 0xe0, 0x00, 0x60, 0xb0, 0x67, 0xb8, 0x6e]}; // The minimal bring-up: how far plain D gets into TSF without ITextStoreACP. // Every step logs its HRESULT; the verdict line records where it stopped. // (Not nothrow: IUnknown.Release in druntime is a throwing prototype. The // probe runs from main, never from the WndProc, so that is acceptable.) void
app.tsfProbe
tsfProbe
(HWND
(parameter) HWND hwnd
hwnd
)
undefined identifier `HWND`
{ auto
_error_ hr
hr
= CoInitializeEx(null, COINIT.COINIT_APARTMENTTHREADED);
logEvent("tsf step=CoInitializeEx hr=0x%08lx", hr);
(unresolved type) ITfThreadMgr
ITfThreadMgr
_error_ tm
tm
;
hr = CoCreateInstance(&CLSID_TF_ThreadMgr, null, CLSCTX.CLSCTX_INPROC_SERVER, &IID_ITfThreadMgr, cast(void**)&tm); logEvent("tsf step=CoCreateInstance clsid=TF_ThreadMgr hr=0x%08lx ptr=%d", hr, tm !is null ? 1 : 0); if (tm is null) { logEvent("tsf_verdict reached=none note=thread_mgr_unavailable"); return; }
(unresolved type) TfClientId
TfClientId
_error_ tid
tid
;
hr = tm.
tm.Activate
Activate
(&tid);
logEvent("tsf step=Activate hr=0x%08lx client_id=0x%lx", hr, tid);
(unresolved type) ITfDocumentMgr
ITfDocumentMgr
_error_ dm
dm
;
hr = tm.
tm.CreateDocumentMgr
CreateDocumentMgr
(&dm);
logEvent("tsf step=CreateDocumentMgr hr=0x%08lx ptr=%d", hr, dm !is null ? 1 : 0);
(unresolved type) ITfContext
ITfContext
_error_ ic
ic
;
(unresolved type) TfEditCookie
TfEditCookie
_error_ cookie
cookie
;
if (dm !is null) { // punk=null is allowed ("the context does not have a text store") but // an IME can then neither read nor edit the document — the exact gap // a real integration fills by implementing ITextStoreACP. hr = dm.
dm.CreateContext
CreateContext
(tid, 0, null, &ic, &cookie);
logEvent("tsf step=CreateContext punk=null hr=0x%08lx cookie=0x%lx", hr, cookie); if (ic !is null) { hr = dm.
dm.Push
Push
(ic);
logEvent("tsf step=Push hr=0x%08lx", hr); }
(unresolved type) ITfDocumentMgr
ITfDocumentMgr
_error_ prev
prev
;
hr = tm.
tm.AssociateFocus
AssociateFocus
(hwnd, dm, &prev);
logEvent("tsf step=AssociateFocus hr=0x%08lx prev=%d", hr, prev !is null ? 1 : 0); if (prev !is null) prev.Release(); } logEvent("tsf_verdict reached=%s note=no_ITextStoreACP_so_no_editable_store", ic !is null ? "context_pushed_and_focused".ptr : "thread_mgr_only".ptr); // Full teardown so the IMM32 half below runs on a clean thread.
(unresolved type) ITfDocumentMgr
ITfDocumentMgr
_error_ prev2
prev2
;
tm.
tm.AssociateFocus
AssociateFocus
(hwnd, null, &prev2);
if (prev2 !is null) prev2.Release(); if (dm !is null) dm.
dm.Pop
Pop
(TF_POPF_ALL);
if (ic !is null) ic.Release(); if (dm !is null) dm.Release(); hr = tm.
tm.Deactivate
Deactivate
();
logEvent("tsf step=Deactivate hr=0x%08lx", hr); tm.Release(); } // --------------------------------------------------------------------------- // Editor state. Committed text + caret index, plus the live pre-edit run that // is rendered inline (underlined) at the caret during a composition. enum
(constant) int app.MARGIN = 12
MARGIN
= 12; // text origin inside the client area
enum UINT_PTR
(constant) _error_ app.TIMER_ID = __error
TIMER_ID
= 1;
undefined identifier `UINT_PTR`
enum
(constant) int app.TICK_MS = 150
TICK_MS
= 150; // one script step per tick
struct
(struct) app.Demo
Demo
{ HWND
(field) _error_ app.Demo.hwnd
hwnd
;
undefined identifier `HWND`
(alias) app.HIMC = _error_
HIMC
(field) _error_ app.Demo.himcSaved
himcSaved
; // != null while the associate-null probe is active
wchar[256]
(field) wchar[256] app.Demo.text
text
; // committed text
int
(field) int app.Demo.len
len
,
(field) int app.Demo.caret
caret
; // committed length / caret index into text[]
wchar[64]
(field) wchar[64] app.Demo.preedit
preedit
; // live composition string (GCS_COMPSTR)
int
(field) int app.Demo.preLen
preLen
,
(field) int app.Demo.preCursor
preCursor
; // pre-edit length / cursor (GCS_CURSORPOS)
int
(field) int app.Demo.lineHeight
lineHeight
= 16;
bool
(field) bool app.Demo.composing
composing
,
(field) bool app.Demo.autoExit
autoExit
,
(field) bool app.Demo.focused
focused
;
uint
(field) uint app.Demo.step
step
;
uint
(field) uint app.Demo.nStart
nStart
,
(field) uint app.Demo.nComp
nComp
,
(field) uint app.Demo.nEnd
nEnd
,
(field) uint app.Demo.nSetCtx
nSetCtx
,
(field) uint app.Demo.nNotify
nNotify
,
(field) uint app.Demo.nImeChar
nImeChar
,
(field) uint app.Demo.nChar
nChar
,
(field) uint app.Demo.nCommit
nCommit
;
} __gshared
(struct) app.Demo
Demo
_error_ app.g
g
;
void
void app.utf8z(scope const(wchar)[] s, scope char[] o) nothrow @nogc
utf8z
(scope const(wchar)[]
(parameter) const(wchar)[] s
s
, scope char[]
(parameter) char[] o
o
) nothrow @nogc
{
(alias) object.size_t = ulong
size_t
(local variable) ulong n
n
;
foreach (
(parameter) ulong i
i
, wchar
(parameter) wchar u
u
;
(parameter) const(wchar)[] s
s
) // unpaired-surrogate-naive: fine for log lines
{ dchar
(local variable) dchar c
c
=
(local variable) wchar u
u
;
if (
(local variable) wchar u
u
>= 0xd800 &&
(local variable) wchar u
u
<= 0xdbff &&
(local variable) ulong i
i
+ 1 <
(parameter) const(wchar)[] s
s
.
(field) ulong const(wchar)[].length
length
)
continue; // high unit: emit at the low unit if (
(local variable) wchar u
u
>= 0xdc00 &&
(local variable) wchar u
u
<= 0xdfff &&
(local variable) ulong i
i
> 0)
(local variable) dchar c
c
= 0x10000 + ((
(parameter) const(wchar)[] s
s
[
(local variable) ulong i
i
- 1] - 0xd800) << 10) + (
(local variable) wchar u
u
- 0xdc00);
if (
(local variable) ulong n
n
+ 5 >
(parameter) char[] o
o
.
(field) ulong char[].length
length
)
break; if (
(local variable) dchar c
c
< 0x80)
(parameter) char[] o
o
[
(local variable) ulong n
n
++] = cast(char)
(local variable) dchar c
c
;
else if (
(local variable) dchar c
c
< 0x800)
{
(parameter) char[] o
o
[
(local variable) ulong n
n
++] = cast(char)(0xc0 | (
(local variable) dchar c
c
>> 6));
(parameter) char[] o
o
[
(local variable) ulong n
n
++] = cast(char)(0x80 | (
(local variable) dchar c
c
& 0x3f));
} else if (
(local variable) dchar c
c
< 0x10000)
{
(parameter) char[] o
o
[
(local variable) ulong n
n
++] = cast(char)(0xe0 | (
(local variable) dchar c
c
>> 12));
(parameter) char[] o
o
[
(local variable) ulong n
n
++] = cast(char)(0x80 | ((
(local variable) dchar c
c
>> 6) & 0x3f));
(parameter) char[] o
o
[
(local variable) ulong n
n
++] = cast(char)(0x80 | (
(local variable) dchar c
c
& 0x3f));
} else {
(parameter) char[] o
o
[
(local variable) ulong n
n
++] = cast(char)(0xf0 | (
(local variable) dchar c
c
>> 18));
(parameter) char[] o
o
[
(local variable) ulong n
n
++] = cast(char)(0x80 | ((
(local variable) dchar c
c
>> 12) & 0x3f));
(parameter) char[] o
o
[
(local variable) ulong n
n
++] = cast(char)(0x80 | ((
(local variable) dchar c
c
>> 6) & 0x3f));
(parameter) char[] o
o
[
(local variable) ulong n
n
++] = cast(char)(0x80 | (
(local variable) dchar c
c
& 0x3f));
} }
(parameter) char[] o
o
[
(local variable) ulong n
n
] = 0;
} // Pixel x of the caret = extent of committed-before-caret + pre-edit-cursor. int
app.caretX
caretX
(HDC
(parameter) HDC hdc
hdc
) nothrow
undefined identifier `HDC`
{ SIZE
_error_ sz
sz
;
int
_error_ x
x
= MARGIN;
if (g.
g.caret
caret
> 0 && GetTextExtentPoint32W(hdc, g.
g.text
text
.ptr, g.
g.caret
caret
, &sz))
x += sz.cx; if (g.
g.composing
composing
&& g.
g.preCursor
preCursor
> 0
&& GetTextExtentPoint32W(hdc, g.
g.preedit
preedit
.ptr, g.
g.preCursor
preCursor
, &sz))
x += sz.cx; return x; } // The candidate-window anchoring contract: tell the IME where the caret is so // the candidate list opens beside it and never covers the composition. The // CFS_EXCLUDE rect is the caret line; re-sent on EVERY caret move. void
void app.anchorCandidateWindow() nothrow
anchorCandidateWindow
() nothrow
{
(alias) app.HIMC = _error_
HIMC
(local variable) _error_ himc
himc
= ImmGetContext(g.
g.hwnd
hwnd
);
if (himc is null) { logEvent("candidate_anchor skipped=no_himc");
undefined identifier `logEvent`
return; } HDC
(local variable) _error_ hdc
hdc
= GetDC(g.
g.hwnd
hwnd
);
undefined identifier `HDC`
undefined identifier `GetDC`
HGDIOBJ
(local variable) _error_ old
old
= SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT));
undefined identifier `HGDIOBJ`
undefined identifier `SelectObject`
const
(local variable) const(_error_) x
x
= caretX(hdc);
SelectObject(hdc, old);
undefined identifier `SelectObject`
ReleaseDC(g.
g.hwnd
hwnd
, hdc);
undefined identifier `ReleaseDC`
CANDIDATEFORM
(local variable) _error_ cf
cf
;
undefined identifier `CANDIDATEFORM`
cf.dwIndex = 0; cf.dwStyle = CFS_EXCLUDE; cf.ptCurrentPos = POINT(x, MARGIN + g.
g.lineHeight
lineHeight
);
cf.rcArea = RECT(x, MARGIN, x + 2, MARGIN + g.
g.lineHeight
lineHeight
);
const
(local variable) const(_error_) ok
ok
= ImmSetCandidateWindow(himc, &cf);
// Keep the composition window (the IME's inline-fallback UI) at the caret // too — IMEs that ignore CANDIDATEFORM still honor COMPOSITIONFORM. COMPOSITIONFORM
(local variable) _error_ comp
comp
;
undefined identifier `COMPOSITIONFORM`
comp.dwStyle = CFS_POINT; comp.ptCurrentPos = POINT(x, MARGIN); const
(local variable) const(_error_) ok2
ok2
= ImmSetCompositionWindow(himc, &comp);
ImmReleaseContext(g.
g.hwnd
hwnd
, himc);
logEvent("candidate_anchor x=%d y=%d style=CFS_EXCLUDE ok=%d comp_ok=%d",
undefined identifier `logEvent`
x, MARGIN + g.
g.lineHeight
lineHeight
, ok ? 1 : 0, ok2 ? 1 : 0);
} void
void app.moveCaret(int delta) nothrow
moveCaret
(int
(parameter) int delta
delta
) nothrow
{ const
(local variable) const(_error_) next
next
= g.
(field) _error_ g.caret
caret
+
(parameter) int delta
delta
;
if (next < 0 || next > g.
(field) _error_ g.len
len
)
return; g.
g.caret
caret
= next;
logEvent("caret index=%d", g.
g.caret
caret
);
undefined identifier `logEvent`
InvalidateRect(g.
g.hwnd
hwnd
, null, TRUE);
undefined identifier `InvalidateRect`
void app.anchorCandidateWindow() nothrow
anchorCandidateWindow
(); // the caret moved -> re-report the rectangle
} void
void app.insertText(scope const(wchar)[] s) nothrow
insertText
(scope const(wchar)[]
(parameter) const(wchar)[] s
s
) nothrow
{ foreach (
(parameter) const(wchar) u
u
;
(parameter) const(wchar)[] s
s
)
{ if (g.
(field) _error_ g.len
len
>= g.
(field) _error_ g.text
text
.
(field) _error_ g.text.length
length
)
break; foreach_reverse (
(parameter) _error_ i
i
; g.
(field) _error_ g.caret
caret
.. g.
(field) _error_ g.len
len
)
g.
g.text
text
[i + 1] = g.
g.text
text
[i];
g.
(field) _error_ g.text
text
[g.
g.caret
caret
++] = u;
g.
(field) _error_ g.len
len
++;
} InvalidateRect(g.
g.hwnd
hwnd
, null, TRUE);
undefined identifier `InvalidateRect`
void app.anchorCandidateWindow() nothrow
anchorCandidateWindow
();
} // --------------------------------------------------------------------------- // WM_IME_COMPOSITION payload readers. int
app.readImeString
readImeString
(
(alias) app.HIMC = _error_
HIMC
(parameter) HIMC himc
himc
, DWORD what, scope wchar[]
(parameter) wchar[] o
o
) nothrow
undefined identifier `DWORD`
{ const
_error_ n
n
= ImmGetCompositionStringW(himc, what, o.ptr, cast(DWORD)(o.
o.length
length
* 2));
return n > 0 ? n / 2 : 0; // byte count -> wchar count (negative = error) } void
app.onImeComposition
onImeComposition
(HWND
(parameter) HWND hwnd
hwnd
, LPARAM lParam) nothrow
undefined identifier `HWND`
undefined identifier `LPARAM`
{ ++g.
g.nComp
nComp
;
logEvent("msg name=WM_IME_COMPOSITION flags=0x%llx", cast(ulong) lParam);
(unresolved type) HIMC
HIMC
_error_ himc
himc
= ImmGetContext(hwnd);
if (himc is null) return; if (lParam & GCS_RESULTSTR) // committed text replaces the pre-edit { wchar[64]
_error_ r
r
;
const
_error_ n
n
= readImeString(himc, GCS_RESULTSTR, r);
char[256]
_error_ u8
u8
;
utf8z(r[0 .. n], u8); logEvent("commit text=%s units=%d", u8.ptr, n); ++g.
g.nCommit
nCommit
;
g.
g.preLen
preLen
= 0;
insertText(r[0 .. n]); } if (lParam & GCS_COMPSTR) // the live pre-edit + its attributes + cursor { g.
g.preLen
preLen
= readImeString(himc, GCS_COMPSTR, g.
g.preedit
preedit
);
ubyte[64]
_error_ attr
attr
;
const
_error_ an
an
= ImmGetCompositionStringW(himc, GCS_COMPATTR, attr.ptr, attr.
attr.length
length
);
const
_error_ cur
cur
= ImmGetCompositionStringW(himc, GCS_CURSORPOS, null, 0);
g.
g.preCursor
preCursor
= cur >= 0 ? cur : 0;
char[256]
_error_ u8
u8
;
utf8z(g.
g.preedit
preedit
[0 .. g.preLen], u8);
char[80]
_error_ a8
a8
;
(unresolved type) size_t
size_t
_error_ ai
ai
;
foreach (
(parameter) i
i
; 0 .. (an > 0 && an < 40 ? an : 0)) // ATTR_* byte per wchar
a8[ai++] = cast(char)('0' + (attr[i] % 10)); a8[ai] = 0; logEvent("preedit text=%s units=%d cursor=%d attr=%s", u8.ptr, g.
g.preLen
preLen
, g.
g.preCursor
preCursor
, ai ? a8.ptr : "-".ptr);
} if (lParam == 0) // composition canceled: clear the pre-edit { g.
g.preLen
preLen
= g.
g.preCursor
preCursor
= 0;
logEvent("preedit_cleared reason=flags_zero"); } ImmReleaseContext(hwnd, himc); InvalidateRect(hwnd, null, TRUE); anchorCandidateWindow(); } // --------------------------------------------------------------------------- // Painting: committed text, then the pre-edit inline at the caret with a 1-px // underline (the visual contract: pre-edit must be distinct from committed). void
app.paint
paint
(HWND
(parameter) HWND hwnd
hwnd
) nothrow
undefined identifier `HWND`
{ PAINTSTRUCT
_error_ ps
ps
;
HDC
_error_ hdc
hdc
= BeginPaint(hwnd, &ps);
RECT
_error_ rc
rc
;
GetClientRect(hwnd, &rc); FillRect(hdc, &rc, cast(HBRUSH)(COLOR_WINDOW + 1)); HGDIOBJ
_error_ oldFont
oldFont
= SelectObject(hdc, GetStockObject(DEFAULT_GUI_FONT));
SetBkMode(hdc, TRANSPARENT); TEXTMETRICW
_error_ tm
tm
;
GetTextMetricsW(hdc, &tm); g.
g.lineHeight
lineHeight
= tm.tmHeight;
int
_error_ x
x
= MARGIN;
SIZE
_error_ sz
sz
;
TextOutW(hdc, x, MARGIN, g.
g.text
text
.ptr, g.
g.caret
caret
); // committed, before caret
if (g.
g.caret
caret
&& GetTextExtentPoint32W(hdc, g.
g.text
text
.ptr, g.
g.caret
caret
, &sz))
x += sz.cx; if (g.
g.preLen
preLen
) // the pre-edit run, underlined
{ TextOutW(hdc, x, MARGIN, g.
g.preedit
preedit
.ptr, g.
g.preLen
preLen
);
GetTextExtentPoint32W(hdc, g.
g.preedit
preedit
.ptr, g.
g.preLen
preLen
, &sz);
HPEN
_error_ pen
pen
= CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
HGDIOBJ
_error_ oldPen
oldPen
= SelectObject(hdc, pen);
MoveToEx(hdc, x, MARGIN + g.
g.lineHeight
lineHeight
+ 1, null);
LineTo(hdc, x + sz.cx, MARGIN + g.
g.lineHeight
lineHeight
+ 1);
SelectObject(hdc, oldPen); DeleteObject(pen); x += sz.cx; } TextOutW(hdc, x, MARGIN, g.
g.text
text
.ptr + g.
g.caret
caret
, g.
g.len
len
- g.
g.caret
caret
); // after caret
if (g.
g.focused
focused
)
SetCaretPos(caretX(hdc), MARGIN); SelectObject(hdc, oldFont); EndPaint(hwnd, &ps); } // --------------------------------------------------------------------------- // SendInput at scancode level (same shape as ../f06-keyboard/app.d). void
void app.injectScans(scope const(ushort)[] seq) nothrow
injectScans
(scope const(ushort)[]
(parameter) const(ushort)[] seq
seq
) nothrow
{ INPUT[8]
(local variable) _error_ inp
inp
;
undefined identifier `INPUT`
foreach (
(parameter) ulong i
i
,
(parameter) const(ushort) e
e
;
(parameter) const(ushort)[] seq
seq
)
{ inp[i].type = INPUT_KEYBOARD; inp[i].ki.wScan = e & 0xff; inp[i].ki.dwFlags = KEYEVENTF_SCANCODE | ((e & 0x8000) ? KEYEVENTF_KEYUP : 0); } const
(local variable) const(_error_) sent
sent
= SendInput(cast(UINT) seq.
seq.length
length
, inp.ptr, INPUT.sizeof);
undefined identifier `SendInput`
logEvent("inject kind=scancode events=%d sent=%u", cast(int) seq.
seq.length
length
, sent);
undefined identifier `logEvent`
} // --------------------------------------------------------------------------- // The script: what an IME-less host (Wine headless) lets us prove, one step // per timer tick. Steps 2-4 are the SCS_SETSTR/NI_COMPOSITIONSTR self- // injection probe — if imm32 echoes WM_IME_COMPOSITION back, the choreography // handlers above capture it. void
app.runStep
runStep
(HWND
(parameter) HWND hwnd
hwnd
) nothrow
undefined identifier `HWND`
{ switch (g.
g.step
step
++)
{ case 0: logEvent("script step=probe_imm");
(unresolved type) HIMC
HIMC
_error_ himc
himc
= ImmGetContext(hwnd);
logEvent("imm context himc=0x%zx", cast(
(unresolved type) size_t
size_t
) himc);
logEvent("imm default_ime_wnd hwnd=0x%zx", cast(
(unresolved type) size_t
size_t
) ImmGetDefaultIMEWnd(hwnd));
logEvent("imm open_status open=%d", himc ? (ImmGetOpenStatus(himc) ? 1 : 0) : -1); DWORD
_error_ conv
conv
,
_error_ sent
sent
;
const
_error_ cs
cs
= himc ? ImmGetConversionStatus(himc, &conv, &sent) : FALSE;
logEvent("imm conversion_status ok=%d conversion=0x%lx sentence=0x%lx", cs ? 1 : 0, conv, sent); WCHAR[64]
_error_ desc
desc
;
const
_error_ dn
dn
= ImmGetDescriptionW(GetKeyboardLayout(0), desc.ptr, desc.
desc.length
length
);
logEvent("imm description len=%u note=%s", dn, dn ? "ime_layout".ptr : "not_an_ime_layout".ptr); if (himc) ImmReleaseContext(hwnd, himc); break; case 1: // plain typing: caret moves, candidate anchor re-sent per char logEvent("script step=type_ascii text=ab"); injectScans([0x1e, 0x1e | 0x8000, 0x30, 0x30 | 0x8000]); // a, b break; case 2: // self-injection: ask the IME to set the composition string logEvent("script step=set_composition_string text=nihao");
(unresolved type) HIMC
HIMC
_error_ himc
himc
= ImmGetContext(hwnd);
auto
_error_ s
s
= "nihao"w;
const
_error_ ok
ok
= ImmSetCompositionStringW(himc, SCS_SETSTR,
cast(void*) s.ptr, cast(DWORD)(s.
s.length
length
* 2), null, 0);
logEvent("imm set_composition_string scs=SCS_SETSTR ok=%d err=%lu", ok ? 1 : 0, ok ? 0 : GetLastError()); ImmReleaseContext(hwnd, himc); break; case 3: // commit whatever composition exists logEvent("script step=notify_complete");
(unresolved type) HIMC
HIMC
_error_ himc
himc
= ImmGetContext(hwnd);
const
_error_ ok
ok
= ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
logEvent("imm notify_ime action=CPS_COMPLETE ok=%d", ok ? 1 : 0); ImmReleaseContext(hwnd, himc); break; case 4: // and the cancel path logEvent("script step=notify_cancel");
(unresolved type) HIMC
HIMC
_error_ himc
himc
= ImmGetContext(hwnd);
auto
_error_ s
s
= "x"w;
ImmSetCompositionStringW(himc, SCS_SETSTR, cast(void*) s.ptr, 2, null, 0); const
_error_ ok
ok
= ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_CANCEL, 0);
logEvent("imm notify_ime action=CPS_CANCEL ok=%d", ok ? 1 : 0); ImmReleaseContext(hwnd, himc); break; case 5: // IME disable: ImmAssociateContext(null) detaches the context logEvent("script step=associate_null"); g.
g.himcSaved
himcSaved
= ImmAssociateContext(hwnd, null);
logEvent("imm associate_context new=0 old=0x%zx now=0x%zx", cast(
(unresolved type) size_t
size_t
) g.
g.himcSaved
himcSaved
, cast(
(unresolved type) size_t
size_t
) ImmGetContext(hwnd));
injectScans([0x2e, 0x2e | 0x8000]); // 'c' still arrives: plain WM_CHAR break; case 6: // restore logEvent("script step=associate_restore"); ImmAssociateContext(hwnd, g.
g.himcSaved
himcSaved
);
logEvent("imm associate_context new=0x%zx now=0x%zx", cast(
(unresolved type) size_t
size_t
) g.
g.himcSaved
himcSaved
, cast(
(unresolved type) size_t
size_t
) ImmGetContext(hwnd));
break; default: KillTimer(hwnd, TIMER_ID); char[256]
_error_ u8
u8
;
utf8z(g.
g.text
text
[0 .. g.len], u8);
logEvent("summary text=%s caret=%d start=%u comp=%u end=%u setctx=%u notify=%u imechar=%u char=%u commit=%u", u8.ptr, g.
g.caret
caret
, g.
g.nStart
nStart
, g.
g.nComp
nComp
, g.
g.nEnd
nEnd
, g.
g.nSetCtx
nSetCtx
, g.
g.nNotify
nNotify
,
g.
g.nImeChar
nImeChar
, g.
g.nChar
nChar
, g.
g.nCommit
nCommit
);
if (g.
g.autoExit
autoExit
)
DestroyWindow(hwnd); else logEvent("script_done note=window_stays_open_for_real_ime_typing"); } } // --------------------------------------------------------------------------- extern (Windows) LRESULT
app.wndProc
wndProc
(HWND
(parameter) HWND hwnd
hwnd
, UINT
(parameter) UINT msg
msg
, WPARAM wParam, LPARAM lParam) nothrow
undefined identifier `LRESULT`
undefined identifier `HWND`
undefined identifier `UINT`
undefined identifier `WPARAM`
undefined identifier `LPARAM`
{ switch (msg) { case WM_IME_SETCONTEXT: ++g.
g.nSetCtx
nSetCtx
;
logEvent("msg name=WM_IME_SETCONTEXT active=%d show=0x%llx", cast(int) wParam, cast(ulong) lParam); // We render the composition inline, so suppress the IME's own // composition-window UI; candidate UI stays (the IME draws the list). return DefWindowProcW(hwnd, msg, wParam, lParam & ~cast(LPARAM) ISC_SHOWUICOMPOSITIONWINDOW); case WM_IME_STARTCOMPOSITION: ++g.
g.nStart
nStart
;
logEvent("msg name=WM_IME_STARTCOMPOSITION"); g.
g.composing
composing
= true;
anchorCandidateWindow(); return 0; // not DefWindowProc: we own the composition rendering case WM_IME_COMPOSITION: onImeComposition(hwnd, lParam); return 0; case WM_IME_ENDCOMPOSITION: ++g.
g.nEnd
nEnd
;
logEvent("msg name=WM_IME_ENDCOMPOSITION"); g.
g.composing
composing
= false;
g.
g.preLen
preLen
= g.
g.preCursor
preCursor
= 0;
InvalidateRect(hwnd, null, TRUE); return 0; case WM_IME_NOTIFY: ++g.
g.nNotify
nNotify
;
logEvent("msg name=WM_IME_NOTIFY cmd=0x%llx", cast(ulong) wParam); goto default; // IMN_* housekeeping belongs to DefWindowProc case WM_IME_CHAR: // result chars arrive here only if GCS_RESULTSTR is ++g.
g.nImeChar
nImeChar
; // left unhandled — counting it proves we consumed it
logEvent("msg name=WM_IME_CHAR utf16=0x%04llx", cast(ulong) wParam); return 0; case WM_CHAR: ++g.
g.nChar
nChar
;
const wchar
_error_ u
u
= cast(wchar) wParam;
logEvent("char utf16=0x%04x", u); if (u == 8 && g.
g.caret
caret
> 0) // backspace
{ foreach (
(parameter) i
i
; g.
g.caret
caret
.. g.
g.len
len
)
g.
g.text
text
[i - 1] = g.
g.text
text
[i];
g.
g.len
len
--;
moveCaret(-1); } else if (u >= 0x20) { wchar[1]
_error_ one
one
= u;
insertText(one[]); } return 0; case WM_KEYDOWN: if (wParam == VK_LEFT) moveCaret(-1); else if (wParam == VK_RIGHT) moveCaret(1); return 0; case WM_SETFOCUS: g.
g.focused
focused
= true;
CreateCaret(hwnd, null, 1, g.
g.lineHeight
lineHeight
);
ShowCaret(hwnd); InvalidateRect(hwnd, null, TRUE); return 0; case WM_KILLFOCUS: g.
g.focused
focused
= false;
DestroyCaret(); return 0; case WM_PAINT: paint(hwnd); return 0; case WM_TIMER: if (wParam == TIMER_ID) runStep(hwnd); return 0; case WM_CLOSE: logEvent("close_requested"); goto default; case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProcW(hwnd, msg, wParam, lParam); } } // --------------------------------------------------------------------------- bool
bool app.envFlag(const(wchar)* name) nothrow
envFlag
(const(wchar)*
(parameter) const(wchar)* name
name
) nothrow
{ WCHAR[8]
(local variable) _error_ buf
buf
;
undefined identifier `WCHAR`
const
(local variable) const(_error_) n
n
= GetEnvironmentVariableW(name, buf.ptr, buf.
buf.length
length
);
undefined identifier `GetEnvironmentVariableW`
return n >= 1 && n < buf.
(field) _error_ buf.length
length
&& buf[0] == '1';
} int
int D main()
main
()
{ instrumentInit("f07_text_input_win32");
undefined identifier `instrumentInit`
logEvent("init_start");
undefined identifier `logEvent`
g.
g.autoExit
autoExit
= envFlag("WSI_AUTO_EXIT"w.ptr);
logEvent("mode auto_exit=%d", g.
g.autoExit
autoExit
? 1 : 0);
undefined identifier `logEvent`
HINSTANCE
(local variable) _error_ hInst
hInst
= GetModuleHandleW(null);
undefined identifier `HINSTANCE`
undefined identifier `GetModuleHandleW`
auto
(local variable) wstring clsName
clsName
= "wsi-f07-class"w;
WNDCLASSEXW
(local variable) _error_ wc
wc
;
undefined identifier `WNDCLASSEXW`
wc.cbSize = WNDCLASSEXW.sizeof; wc.lpfnWndProc = &wndProc; wc.hInstance = hInst; wc.lpszClassName = clsName.ptr; wc.hCursor = LoadCursorW(null, IDC_ARROW); if (!RegisterClassExW(&wc))
undefined identifier `RegisterClassExW`
{ logEvent("error what=RegisterClassExW code=%lu", GetLastError());
undefined identifier `logEvent`
return 1; } g.
g.hwnd
hwnd
= CreateWindowExW(0, clsName.ptr, "wsi-f07-text-input"w.ptr,
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 480, 160, null, null, hInst, null); if (g.
(field) _error_ g.hwnd
hwnd
is null)
{ logEvent("error what=CreateWindowExW code=%lu", GetLastError());
undefined identifier `logEvent`
return 1; } logEvent("window_created");
undefined identifier `logEvent`
// The decision record: TSF first (and torn down), IMM32 as the editor. tsfProbe(g.
g.hwnd
hwnd
);
ShowWindow(g.
g.hwnd
hwnd
, SW_SHOW);
undefined identifier `ShowWindow`
UpdateWindow(g.
g.hwnd
hwnd
);
undefined identifier `UpdateWindow`
SetForegroundWindow(g.
g.hwnd
hwnd
);
undefined identifier `SetForegroundWindow`
SetFocus(g.
g.hwnd
hwnd
);
undefined identifier `SetFocus`
void app.anchorCandidateWindow() nothrow
anchorCandidateWindow
();
SetTimer(g.
g.hwnd
hwnd
, TIMER_ID, TICK_MS, null);
undefined identifier `SetTimer`
MSG
(local variable) _error_ msg
msg
;
undefined identifier `MSG`
while (GetMessageW(&msg, null, 0, 0) > 0)
undefined identifier `GetMessageW`
{ TranslateMessage(&msg);
undefined identifier `TranslateMessage`
DispatchMessageW(&msg);
undefined identifier `DispatchMessageW`
} CoUninitialize(); logEvent("exit code=%d", cast(int) msg.wParam);
undefined identifier `logEvent`
return cast(int) msg.wParam; }