From 512ef8e09f9a5c2396913d21272fbc241f830eb6 Mon Sep 17 00:00:00 2001 From: Kentaro Ohkouchi Date: Sun, 19 Jul 2026 22:01:05 +0900 Subject: [PATCH] fix(win32): drop modifiers from IME-committed text to avoid CSI-u garbling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Japanese IMEs such as CorvusSKK confirm a conversion with Ctrl+J, so Ctrl is still physically held at the moment the committed characters arrive via WM_CHAR. handleTextInput passed those live modifiers straight through to the core, and the legacy encoder turns any "Ctrl + single codepoint" into a fixterms "CSI ;u" sequence (see input/key_encode.zig). Committed text therefore reached the shell as garbage, e.g. 相 became ESC[30456;5u and 今日 became ESC[20170;5uESC[26085;5u. Non-ASCII WM_CHAR/WM_SYSCHAR output is either composition (Ctrl held for an IME commit, or AltGr = Ctrl+RightAlt) or an intentional "Alt + " shortcut. Only the former should have its modifiers dropped, so keep Alt when it is held without Ctrl (preserving the meta/ESC-prefix encoding) and clear the modifiers for every Ctrl-held case so the raw UTF-8 is emitted. Control characters (< 0x20) are already filtered earlier in the function. Direct kana input and paste do not go through this encoding path and are unaffected. Confirming with Enter (no Ctrl held) never reproduced the bug, which matches the diagnosis. --- src/apprt/win32/App.zig | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/src/apprt/win32/App.zig b/src/apprt/win32/App.zig index 72b2e00c328..1aaa2661431 100644 --- a/src/apprt/win32/App.zig +++ b/src/apprt/win32/App.zig @@ -1259,6 +1259,7 @@ fn handleTextInput(surface: *Surface, msg: UINT, wparam: WPARAM) LRESULT { const len = std.unicode.utf8Encode(codepoint, &utf8_buf) catch 0; if (len > 0) { const input = @import("../../input.zig"); + var effective_mods = mods; var consumed_mods: input.Mods = .{}; // AltGr commonly appears as Left Ctrl + Right Alt on Windows. @@ -1270,9 +1271,37 @@ fn handleTextInput(surface: *Surface, msg: UINT, wparam: WPARAM) LRESULT { consumed_mods.alt = true; } + // IME/dead-key composed text (CJK, accented letters, ...) is + // delivered via WM_CHAR/WM_SYSCHAR while modifier keys may still + // be held physically. For example CorvusSKK confirms a conversion + // with Ctrl+J, so Ctrl is down at the moment the committed + // characters arrive. The core's legacy encoder emits a fixterms + // "CSI ;u" sequence for any Ctrl + single + // codepoint (see input/key_encode.zig), which turns committed + // text like "相" into garbage such as "ESC[30456;5u" in the + // shell. Such modifiers were consumed to compose the character, + // not to trigger a shortcut. Control characters (< 0x20) are + // already filtered above. + // + // Non-ASCII WM_CHAR output can come from either composition + // (Ctrl held for the IME commit, or AltGr = Ctrl+RightAlt) or an + // intentional "Alt + " shortcut. Only the former + // should have its modifiers dropped. When Alt is held without + // Ctrl we keep Alt so the core still encodes the meta/ESC-prefixed + // shortcut; every Ctrl-held case (SKK commit, AltGr) clears the + // modifiers so the raw UTF-8 text is emitted instead of CSI-u. + if (codepoint >= 0x80) { + if (mods.alt and !mods.ctrl) { + consumed_mods = .{}; + } else { + effective_mods = .{}; + consumed_mods = .{}; + } + } + const event = input.KeyEvent{ .action = .press, - .mods = mods, + .mods = effective_mods, .consumed_mods = consumed_mods, .utf8 = utf8_buf[0..len], };