From 9344f6a824fde825c021d71c1e1504053153e027 Mon Sep 17 00:00:00 2001
From: Thomas Harte <thomas.harte@gmail.com>
Date: Thu, 28 Dec 2023 15:05:55 -0500
Subject: [PATCH] Indicate whether a keypress is a repeat. Treat appropriately
 in the Apple II.

---
 .../Implementation/MultiKeyboardMachine.cpp        |  4 ++--
 .../Implementation/MultiKeyboardMachine.hpp        |  2 +-
 Inputs/Keyboard.cpp                                |  2 +-
 Inputs/Keyboard.hpp                                |  2 +-
 Machines/Apple/AppleII/AppleII.cpp                 | 14 ++++++++++++--
 Machines/KeyboardMachine.hpp                       |  6 +++---
 .../Clock Signal/Documents/MachineDocument.swift   | 12 ++++++------
 OSBindings/Mac/Clock Signal/Machine/CSMachine.h    |  2 +-
 OSBindings/Mac/Clock Signal/Machine/CSMachine.mm   | 10 ++++++++--
 OSBindings/Qt/mainwindow.cpp                       |  2 +-
 OSBindings/SDL/main.cpp                            |  5 +++--
 11 files changed, 39 insertions(+), 22 deletions(-)

diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp
index d2557968a..d5a42ba5b 100644
--- a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp
+++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp
@@ -56,10 +56,10 @@ MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::MachineTy
 	}
 }
 
-bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
+bool MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) {
 	bool was_consumed = false;
 	for(const auto &machine: machines_) {
-		was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed);
+		was_consumed |= machine->get_keyboard().set_key_pressed(key, value, is_pressed, is_repeat);
 	}
 	return was_consumed;
 }
diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp
index c01b93c57..c0bee0596 100644
--- a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp
+++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp
@@ -31,7 +31,7 @@ class MultiKeyboardMachine: public MachineTypes::KeyboardMachine {
 			public:
 				MultiKeyboard(const std::vector<MachineTypes::KeyboardMachine *> &machines);
 
-				bool set_key_pressed(Key key, char value, bool is_pressed) final;
+				bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final;
 				void reset_all_keys() final;
 				const std::set<Key> &observed_keys() const final;
 				bool is_exclusive() const final;
diff --git a/Inputs/Keyboard.cpp b/Inputs/Keyboard.cpp
index f58bca83a..7abdb87e5 100644
--- a/Inputs/Keyboard.cpp
+++ b/Inputs/Keyboard.cpp
@@ -21,7 +21,7 @@ Keyboard::Keyboard(const std::set<Key> &essential_modifiers) : essential_modifie
 Keyboard::Keyboard(const std::set<Key> &observed_keys, const std::set<Key> &essential_modifiers) :
 	observed_keys_(observed_keys), essential_modifiers_(essential_modifiers), is_exclusive_(false) {}
 
-bool Keyboard::set_key_pressed(Key key, char, bool is_pressed) {
+bool Keyboard::set_key_pressed(Key key, char, bool is_pressed, bool) {
 	const size_t key_offset = size_t(key);
 	if(key_offset >= key_states_.size()) {
 		key_states_.resize(key_offset+1, false);
diff --git a/Inputs/Keyboard.hpp b/Inputs/Keyboard.hpp
index 0b4a4120c..3dcbb5cc5 100644
--- a/Inputs/Keyboard.hpp
+++ b/Inputs/Keyboard.hpp
@@ -51,7 +51,7 @@ class Keyboard {
 		// Host interface.
 
 		/// @returns @c true if the key press affects the machine; @c false otherwise.
-		virtual bool set_key_pressed(Key key, char value, bool is_pressed);
+		virtual bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat);
 		virtual void reset_all_keys();
 
 		/// @returns a set of all Keys that this keyboard responds to.
diff --git a/Machines/Apple/AppleII/AppleII.cpp b/Machines/Apple/AppleII/AppleII.cpp
index 292323433..b3f915848 100644
--- a/Machines/Apple/AppleII/AppleII.cpp
+++ b/Machines/Apple/AppleII/AppleII.cpp
@@ -279,7 +279,12 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 				open_apple_is_pressed = closed_apple_is_pressed = control_is_pressed = shift_is_pressed = key_is_down = false;
 			}
 
-			bool set_key_pressed(Key key, char value, bool is_pressed) final {
+			bool set_key_pressed(Key key, char value, bool is_pressed, bool is_repeat) final {
+				// TODO: unless a repeat key is pressed or this is a IIe.
+				if constexpr (!is_iie()) {
+					if(is_repeat && !repeat_is_pressed) return true;
+				}
+
 				// If no ASCII value is supplied, look for a few special cases.
 				switch(key) {
 					case Key::Left:			value = 0x08;	break;
@@ -333,7 +338,11 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 
 					case Key::F1:	case Key::F2:	case Key::F3:	case Key::F4:
 					case Key::F5:	case Key::F6:	case Key::F7:	case Key::F8:
-					case Key::F9:	case Key::F10:	case Key::F11:	case Key::F12:
+					case Key::F9:	case Key::F10:	case Key::F11:
+						repeat_is_pressed = is_pressed;
+					return true;
+
+					case Key::F12:
 					case Key::PrintScreen:
 					case Key::ScrollLock:
 					case Key::Pause:
@@ -402,6 +411,7 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
 				}
 			}
 
+			bool repeat_is_pressed = false;
 			bool shift_is_pressed = false;
 			bool control_is_pressed = false;
 			// The IIe has three keys that are wired directly to the same input as the joystick buttons.
diff --git a/Machines/KeyboardMachine.hpp b/Machines/KeyboardMachine.hpp
index 81c0918b1..57e655112 100644
--- a/Machines/KeyboardMachine.hpp
+++ b/Machines/KeyboardMachine.hpp
@@ -75,12 +75,12 @@ class KeyboardMachine: public KeyActions {
 				(i) if @c symbol can be typed and this is a key down, @c type_string it;
 				(ii) if @c symbol cannot be typed, set @c key as @c is_pressed
 		*/
-		bool apply_key(Inputs::Keyboard::Key key, char symbol, bool is_pressed, bool map_logically) {
+		bool apply_key(Inputs::Keyboard::Key key, char symbol, bool is_pressed, bool is_repeat, bool map_logically) {
 			Inputs::Keyboard &keyboard = get_keyboard();
 
 			if(!map_logically) {
 				// Try a regular keypress first, and stop if that works.
-				if(keyboard.set_key_pressed(key, symbol, is_pressed)) {
+				if(keyboard.set_key_pressed(key, symbol, is_pressed, is_repeat)) {
 					return true;
 				}
 
@@ -103,7 +103,7 @@ class KeyboardMachine: public KeyActions {
 				// That didn't work. Forward as a keypress. As, either:
 				//	(i) this is a key down, but doesn't have a symbol, or is an untypeable symbol; or
 				//	(ii) this is a key up, which it won't be an issue to miscommunicate.
-				return keyboard.set_key_pressed(key, symbol, is_pressed);
+				return keyboard.set_key_pressed(key, symbol, is_pressed, is_repeat);
 			}
 		}
 };
diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift
index cefc57dac..c8c0e81dd 100644
--- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift	
+++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift	
@@ -345,14 +345,14 @@ class MachineDocument:
 	/// Forwards key down events directly to the machine.
 	func keyDown(_ event: NSEvent) {
 		if let machine = self.machine {
-			machine.setKey(event.keyCode, characters: event.characters, isPressed: true)
+			machine.setKey(event.keyCode, characters: event.characters, isPressed: true, isRepeat: event.isARepeat)
 		}
 	}
 
 	/// Forwards key up events directly to the machine.
 	func keyUp(_ event: NSEvent) {
 		if let machine = self.machine {
-			machine.setKey(event.keyCode, characters: event.characters, isPressed: false)
+			machine.setKey(event.keyCode, characters: event.characters, isPressed: false, isRepeat: false)
 		}
 	}
 
@@ -361,19 +361,19 @@ class MachineDocument:
 		if let machine = self.machine {
 			if newModifiers.modifierFlags.contains(.shift) != shiftIsDown {
 				shiftIsDown = newModifiers.modifierFlags.contains(.shift)
-				machine.setKey(VK_Shift, characters: nil, isPressed: shiftIsDown)
+				machine.setKey(VK_Shift, characters: nil, isPressed: shiftIsDown, isRepeat: false)
 			}
 			if newModifiers.modifierFlags.contains(.control) != controlIsDown {
 				controlIsDown = newModifiers.modifierFlags.contains(.control)
-				machine.setKey(VK_Control, characters: nil, isPressed: controlIsDown)
+				machine.setKey(VK_Control, characters: nil, isPressed: controlIsDown, isRepeat: false)
 			}
 			if newModifiers.modifierFlags.contains(.command) != commandIsDown {
 				commandIsDown = newModifiers.modifierFlags.contains(.command)
-				machine.setKey(VK_Command, characters: nil, isPressed: commandIsDown)
+				machine.setKey(VK_Command, characters: nil, isPressed: commandIsDown, isRepeat: false)
 			}
 			if newModifiers.modifierFlags.contains(.option) != optionIsDown {
 				optionIsDown = newModifiers.modifierFlags.contains(.option)
-				machine.setKey(VK_Option, characters: nil, isPressed: optionIsDown)
+				machine.setKey(VK_Option, characters: nil, isPressed: optionIsDown, isRepeat: false)
 			}
 		}
 	}
diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h
index ae801b5f1..eb4aaa143 100644
--- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h	
+++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h	
@@ -67,7 +67,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
 - (void)start;
 - (void)stop;
 
-- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed;
+- (void)setKey:(uint16_t)key characters:(nullable NSString *)characters isPressed:(BOOL)isPressed isRepeat:(BOOL)isRepeat;
 - (void)clearAllKeys;
 
 - (void)setMouseButton:(int)button isPressed:(BOOL)isPressed;
diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm
index 3331eb969..1a49f67b0 100644
--- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm	
+++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm	
@@ -366,7 +366,7 @@ struct ActivityObserver: public Activity::Observer {
 	[self updateJoystickTimer];
 }
 
-- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
+- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed isRepeat:(BOOL)isRepeat {
 	[self applyInputEvent:^{
 		auto keyboard_machine = self->_machine->keyboard_machine();
 		if(keyboard_machine && (self.inputMode != CSMachineKeyboardInputModeJoystick || !keyboard_machine->get_keyboard().is_exclusive())) {
@@ -437,7 +437,13 @@ struct ActivityObserver: public Activity::Observer {
 			}
 
 			@synchronized(self) {
-				if(keyboard_machine->apply_key(mapped_key, pressedKey, isPressed, self.inputMode == CSMachineKeyboardInputModeKeyboardLogical)) {
+				if(keyboard_machine->apply_key(
+					mapped_key,
+					pressedKey,
+					isPressed,
+					isRepeat,
+					self.inputMode == CSMachineKeyboardInputModeKeyboardLogical)
+				) {
 					return;
 				}
 			}
diff --git a/OSBindings/Qt/mainwindow.cpp b/OSBindings/Qt/mainwindow.cpp
index 1e36e338c..f46df4ae8 100644
--- a/OSBindings/Qt/mainwindow.cpp
+++ b/OSBindings/Qt/mainwindow.cpp
@@ -889,7 +889,7 @@ bool MainWindow::processEvent(QKeyEvent *event) {
 			if(!keyboardMachine) return true;
 
 			auto &keyboard = keyboardMachine->get_keyboard();
-			keyboard.set_key_pressed(*key, event->text().size() ? event->text()[0].toLatin1() : '\0', isPressed);
+			keyboard.set_key_pressed(*key, event->text().size() ? event->text()[0].toLatin1() : '\0', isPressed, false);
 			if(keyboard.is_exclusive() || keyboard.observed_keys().find(*key) != keyboard.observed_keys().end()) {
 				return false;
 			}
diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp
index d0c253d30..f29160286 100644
--- a/OSBindings/SDL/main.cpp
+++ b/OSBindings/SDL/main.cpp
@@ -1214,14 +1214,15 @@ int main(int argc, char *argv[]) {
 					// is sufficiently untested on SDL, and somewhat too reliant on empirical timestamp behaviour,
 					// for it to be trustworthy enough otherwise to expose.
 					if(logical_keyboard) {
-						if(keyboard_machine->apply_key(key, keypress.input.size() ? keypress.input[0] : 0, keypress.is_down, logical_keyboard)) {
+						// TODO: is_repeat.
+						if(keyboard_machine->apply_key(key, keypress.input.size() ? keypress.input[0] : 0, keypress.is_down, keypress.repeat, logical_keyboard)) {
 							continue;
 						}
 					} else {
 						// This is a slightly terrible way of obtaining a symbol for the key, e.g. for letters it will always return
 						// the capital letter version, at least empirically. But it'll have to do for now.
 						const char *key_name = SDL_GetKeyName(keypress.keycode);
-						if(keyboard_machine->get_keyboard().set_key_pressed(key, (strlen(key_name) == 1) ? key_name[0] : 0, keypress.is_down)) {
+						if(keyboard_machine->get_keyboard().set_key_pressed(key, (strlen(key_name) == 1) ? key_name[0] : 0, keypress.is_down, keypress.repeat)) {
 							continue;
 						}
 					}