From 997662dcfb4e5350df17e3844310ec050ae57cab Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 25 Feb 2026 14:15:12 -0800 Subject: [PATCH 1/3] stop wave keybindings when composing... --- frontend/app/view/term/termwrap.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index e6f83303e2..75d67f58a4 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -42,6 +42,7 @@ const TermFileName = "term"; const TermCacheFileName = "cache:term:full"; const MinDataProcessedForCache = 100 * 1024; export const SupportsImageInput = true; +const IMEDedupWindowMs = 20; // detect webgl support function detectWebGLSupport(): boolean { @@ -205,7 +206,12 @@ export class TermWrap { return true; }) ); - this.terminal.attachCustomKeyEventHandler(waveOptions.keydownHandler); + this.terminal.attachCustomKeyEventHandler((e: KeyboardEvent) => { + if (e.isComposing || !waveOptions.keydownHandler) { + return true; + } + return waveOptions.keydownHandler(e); + }); this.connectElem = connectElem; this.mainFileSubject = null; this.heldData = []; @@ -236,6 +242,9 @@ export class TermWrap { resetCompositionState() { this.isComposing = false; this.composingData = ""; + this.lastComposedText = ""; + this.lastCompositionEnd = 0; + this.firstDataAfterCompositionSent = false; } private handleCompositionStart = (e: CompositionEvent) => { @@ -364,7 +373,6 @@ export class TermWrap { // IME Deduplication (for Capslock input method switching) // When switching input methods with Capslock during composition, some systems send the // composed text twice. We allow the first send and block subsequent duplicates. - const IMEDedupWindowMs = 50; const now = Date.now(); const timeSinceCompositionEnd = now - this.lastCompositionEnd; if (timeSinceCompositionEnd < IMEDedupWindowMs && data === this.lastComposedText && this.lastComposedText) { From afac1a0992d193e17db9fe4ce945f0d9db09d351 Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 25 Feb 2026 15:19:11 -0800 Subject: [PATCH 2/3] fix IME issues --- frontend/app/view/term/termwrap.ts | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index 75d67f58a4..41a7763b5c 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -362,29 +362,13 @@ export class TermWrap { return; } - // IME Composition Handling - // Block all data during composition - only send the final text after compositionend - // This prevents xterm.js from sending intermediate composition data (e.g., during compositionupdate) + // IME fix: suppress isComposing=true events unless they immediately follow + // a compositionend (within 20ms). This handles CapsLock input method switching + // where the composition buffer gets flushed as a spurious isComposing=true event if (this.isComposing) { - dlog("Blocked data during composition:", data); - return; - } - - // IME Deduplication (for Capslock input method switching) - // When switching input methods with Capslock during composition, some systems send the - // composed text twice. We allow the first send and block subsequent duplicates. - const now = Date.now(); - const timeSinceCompositionEnd = now - this.lastCompositionEnd; - if (timeSinceCompositionEnd < IMEDedupWindowMs && data === this.lastComposedText && this.lastComposedText) { - if (!this.firstDataAfterCompositionSent) { - // First send after composition - allow it but mark as sent - this.firstDataAfterCompositionSent = true; - dlog("First data after composition, allowing:", data); - } else { - // Second send of the same data - this is a duplicate from Capslock switching, block it - dlog("Blocked duplicate IME data:", data); - this.lastComposedText = ""; // Clear to allow same text to be typed again later - this.firstDataAfterCompositionSent = false; + const timeSinceCompositionEnd = Date.now() - this.lastCompositionEnd; + if (timeSinceCompositionEnd > IMEDedupWindowMs) { + dlog("Suppressed IME data (composing, not near compositionend):", data); return; } } From a419cc8baf4a5228eb86916f04a5cd2b30e8388e Mon Sep 17 00:00:00 2001 From: sawka Date: Wed, 25 Feb 2026 15:23:57 -0800 Subject: [PATCH 3/3] dont block modifier key keypresses when composing... so Cmd-1 etc will still work --- frontend/app/view/term/termwrap.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/app/view/term/termwrap.ts b/frontend/app/view/term/termwrap.ts index 41a7763b5c..0270c2aed1 100644 --- a/frontend/app/view/term/termwrap.ts +++ b/frontend/app/view/term/termwrap.ts @@ -94,8 +94,6 @@ export class TermWrap { onLinkHover?: (uri: string | null, mouseX: number, mouseY: number) => void; // IME composition state tracking - // Prevents duplicate input when switching input methods during composition (e.g., using Capslock) - // xterm.js sends data during compositionupdate AND after compositionend, causing duplicates isComposing: boolean = false; composingData: string = ""; lastCompositionEnd: number = 0; @@ -207,7 +205,10 @@ export class TermWrap { }) ); this.terminal.attachCustomKeyEventHandler((e: KeyboardEvent) => { - if (e.isComposing || !waveOptions.keydownHandler) { + if (e.isComposing && !e.ctrlKey && !e.altKey && !e.metaKey) { + return true; + } + if (!waveOptions.keydownHandler) { return true; } return waveOptions.keydownHandler(e);