From 8493a993afb465f2708c0a064829ff17f8b33bd3 Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Thu, 16 Jul 2026 08:23:44 +0000 Subject: [PATCH] Fix broken non-space key input on native 10.6 PPC Commit e32fd8c5d removed the manual character dispatch from CocoView keyDown: on the premise that interpretKeyEvents: always drives insertText:/PreeditText for printable characters. That premise holds on modern macOS and under Rosetta, but NOT on native 10.6 PowerPC, where interpretKeyEvents: does not call insertText: for plain characters (the very reason c4ffbcfa5 added the manual dispatch). Removing it left the KeyEvent kVK_Space special-case as the only surviving character source, so spaces worked but every other key was dead. Reinstate the manual dispatch, but gate it on a new per-keystroke flag coco_text_inserted so each printable character is delivered exactly once on every platform: - keyDown: resets the flag, runs interpretKeyEvents:, then manually dispatches the character only if the flag was not set (i.e. insertText: did not fire). Command/Control-modified keys and space are skipped. - PreeditText sets the flag for every printable char it delivers (all except space, which it never handles), so the manual path stands down when insertText: fired - no doubling on Rosetta/modern. - Space stays exclusively on KeyEvent's kVK_Space special-case (PreeditText and the manual path both skip ' '), so it is dispatched once and never doubled. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01SZW7vLXvGgspwkoPrMWt3q --- uppsrc/CtrlCore/CocoProc.mm | 49 +++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/uppsrc/CtrlCore/CocoProc.mm b/uppsrc/CtrlCore/CocoProc.mm index 5eec29011..3b0593219 100644 --- a/uppsrc/CtrlCore/CocoProc.mm +++ b/uppsrc/CtrlCore/CocoProc.mm @@ -18,6 +18,14 @@ static bool coco_mouse_right; static int coco_flags; static Upp::Ptr coco_capture; +// Set by insertText:/PreeditText while a keyDown: is being processed, so that keyDown: +// knows AppKit's interpretKeyEvents: already delivered the character(s) and must not +// dispatch them a second time. On platforms where interpretKeyEvents: does NOT route +// plain characters through insertText: (e.g. native 10.6 PowerPC), this stays false and +// keyDown: dispatches the character itself. Only meaningful on the main thread within +// a single keyDown: call. +static bool coco_text_inserted; + Upp::Ptr Upp::Ctrl::lastActive; namespace Upp { @@ -257,6 +265,8 @@ struct MMImp { WString x = ToWString((CFStringRef)([e characters])); if([e keyCode] == kVK_ANSI_KeypadEnter && *x != 13) ctrl->DispatchKey(13, 1); + // Space is delivered exclusively here (PreeditText and keyDown:'s manual path + // both skip it), so it is dispatched exactly once and never doubled. if([e keyCode] == kVK_Space && !(k & K_SHIFT)) ctrl->DispatchKey(' ', 1); } @@ -349,11 +359,17 @@ struct MMImp { static void PreeditText(Ctrl *ctrl, const WString& s) { - if(ctrl) - for(Upp::wchar ch : s) { - if(ch >= 32 && ch != 127 && ch != ' ') + for(Upp::wchar ch : s) { + // Space is intentionally routed through KeyEvent's special-case, not here, so + // do not flag or dispatch it (flagging it would suppress that special-case and + // drop the space). Every other printable char IS delivered here, so flag it so + // keyDown: does not dispatch it a second time. + if(ch >= 32 && ch != 127 && ch != ' ') { + coco_text_inserted = true; + if(ctrl) ctrl->DispatchKey(ch, 1); } + } } static void CancelPreedit() @@ -452,12 +468,31 @@ void CocoMenuBarAction(void *bar, id sender); - (void)keyDown:(NSEvent *)e { Upp::GuiLock __; - // interpretKeyEvents drives insertText:/PreeditText for printable characters (incl. IME). - // Do not also dispatch characters manually here - that double-fires every keystroke - // (e.g. duplicated spaces/characters), since KeyEvent below independently handles - // non-character keys (arrows, function keys, the Space special-case, etc). + // interpretKeyEvents: is supposed to drive insertText:/PreeditText for printable + // characters (incl. IME). On modern macOS (also under Rosetta) it does, and + // PreeditText sets coco_text_inserted. But on native 10.6 PowerPC it does NOT call + // insertText: for plain characters, so we must dispatch them ourselves below. + // The flag ensures each character is delivered exactly once either way - no doubling + // where insertText: fires, no dropped keys where it does not. + coco_text_inserted = false; [self interpretKeyEvents: [NSArray arrayWithObject: e]]; + if(!coco_text_inserted) { + NSString *chars = [e characters]; + if(chars && [chars length] > 0) { + unichar ch = [chars characterAtIndex:0]; + // Printable, non-command character that interpretKeyEvents: did not deliver. + // Space is deliberately excluded: it is delivered by KeyEvent's special-case + // below, matching the original design. + if(ch >= 32 && ch != 127 && ch != ' ' && + !([e modifierFlags] & (NSEventModifierFlagCommand | NSEventModifierFlagControl))) { + Upp::Ctrl *ctrl = CocoViewGetCtrl(self); + if(ctrl) + Upp::MMImp::DispatchKey(ctrl, ch); + } + } + } + if(!Upp::MMImp::KeyEvent(CocoViewGetCtrl(self), e, 0)) [super keyDown:e]; } -- 2.43.0