From 8493a993afb465f2708c0a064829ff17f8b33bd3 Mon Sep 17 00:00:00 2001
From: Sergey Fedorov <barracuda@macos-powerpc.org>
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) <noreply@anthropic.com>
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<Upp::Ctrl> 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> 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

