diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp index 560d18be3..42d0c0835 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.cpp @@ -36,6 +36,14 @@ void MultiKeyboardMachine::type_string(const std::string &string) { } } +bool MultiKeyboardMachine::can_type(char c) { + bool can_type = true; + for(const auto &machine: machines_) { + can_type &= machine->can_type(c); + } + return can_type; +} + Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() { return keyboard_; } diff --git a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp index 274380002..a828546fe 100644 --- a/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp +++ b/Analyser/Dynamic/MultiMachine/Implementation/MultiKeyboardMachine.hpp @@ -51,6 +51,7 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine { void clear_all_keys() final; void set_key_state(uint16_t key, bool is_pressed) final; void type_string(const std::string &) final; + bool can_type(char c) final; Inputs::Keyboard &get_keyboard() final; }; diff --git a/Machines/AmstradCPC/AmstradCPC.cpp b/Machines/AmstradCPC/AmstradCPC.cpp index a6b4a2375..ec6565d74 100644 --- a/Machines/AmstradCPC/AmstradCPC.cpp +++ b/Machines/AmstradCPC/AmstradCPC.cpp @@ -786,7 +786,7 @@ template class ConcreteMachine: public CRTMachine::Machine, public MediaTarget::Machine, public KeyboardMachine::MappedMachine, - public Utility::TypeRecipient, + public Utility::TypeRecipient, public CPU::Z80::BusHandler, public ClockingHint::Observer, public Configurable::Device, @@ -1079,16 +1079,19 @@ template class ConcreteMachine: // MARK: - Keyboard void type_string(const std::string &string) final { - std::unique_ptr mapper(new CharacterMapper()); - Utility::TypeRecipient::add_typer(string, std::move(mapper)); + Utility::TypeRecipient::add_typer(string); + } + + bool can_type(char c) final { + return Utility::TypeRecipient::can_type(c); } HalfCycles get_typer_delay() final { - return Cycles(4000000); // Wait 1 second before typing. + return z80_.get_is_resetting() ? Cycles(3'400'000) : Cycles(0); } HalfCycles get_typer_frequency() final { - return Cycles(160000); // Type one character per frame. + return Cycles(80'000); // Perform one key transition per frame. } // See header; sets a key as either pressed or released. @@ -1231,7 +1234,8 @@ template class ConcreteMachine: KeyboardState key_state_; AmstradCPC::KeyboardMapper keyboard_mapper_; - uint8_t ram_[1024 * 1024]; + bool has_run_ = false; + uint8_t ram_[128 * 1024]; }; } diff --git a/Machines/AmstradCPC/Keyboard.cpp b/Machines/AmstradCPC/Keyboard.cpp index f3476505f..8b49d9d61 100644 --- a/Machines/AmstradCPC/Keyboard.cpp +++ b/Machines/AmstradCPC/Keyboard.cpp @@ -85,7 +85,7 @@ uint16_t *CharacterMapper::sequence_for_character(char character) { /* ACK */ X, /* BEL */ X, /* BS */ KEYS(KeyDelete), /* HT */ X, /* LF */ KEYS(KeyReturn), /* VT */ X, - /* FF */ X, /* CR */ X, + /* FF */ X, /* CR */ KEYS(KeyReturn), /* SO */ X, /* SI */ X, /* DLE */ X, /* DC1 */ X, /* DC2 */ X, /* DC3 */ X, @@ -142,7 +142,7 @@ uint16_t *CharacterMapper::sequence_for_character(char character) { /* x */ KEYS(KeyX), /* y */ KEYS(KeyY), /* z */ KEYS(KeyZ), /* { */ X, /* | */ SHIFT(KeyAt), /* } */ X, - /* ~ */ X + /* ~ */ X, /* DEL */ KEYS(KeyDelete), }; #undef KEYS #undef SHIFT @@ -150,3 +150,7 @@ uint16_t *CharacterMapper::sequence_for_character(char character) { return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); } + +bool CharacterMapper::needs_pause_after_key(uint16_t key) { + return key != KeyControl && key != KeyShift; +} diff --git a/Machines/AmstradCPC/Keyboard.hpp b/Machines/AmstradCPC/Keyboard.hpp index 65544faba..5aadbf5e3 100644 --- a/Machines/AmstradCPC/Keyboard.hpp +++ b/Machines/AmstradCPC/Keyboard.hpp @@ -38,7 +38,10 @@ struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper { }; struct CharacterMapper: public ::Utility::CharacterMapper { - uint16_t *sequence_for_character(char character); + uint16_t *sequence_for_character(char character) override; + + bool needs_pause_after_reset_all_keys() override { return false; } + bool needs_pause_after_key(uint16_t key) override; }; }; diff --git a/Machines/Apple/AppleII/AppleII.cpp b/Machines/Apple/AppleII/AppleII.cpp index 90f53048a..0e1a3c037 100644 --- a/Machines/Apple/AppleII/AppleII.cpp +++ b/Machines/Apple/AppleII/AppleII.cpp @@ -858,6 +858,11 @@ template class ConcreteMachine: string_serialiser_ = std::make_unique(string, true); } + bool can_type(char c) final { + // Make an effort to type the entire printable ASCII range. + return c >= 32 && c < 127; + } + // MARK:: Configuration options. std::vector> get_options() final { return Apple::II::get_options(); diff --git a/Machines/Atari/ST/AtariST.cpp b/Machines/Atari/ST/AtariST.cpp index c8056e9e1..7be6a7747 100644 --- a/Machines/Atari/ST/AtariST.cpp +++ b/Machines/Atari/ST/AtariST.cpp @@ -44,7 +44,7 @@ namespace ST { std::vector> get_options() { return Configurable::standard_options( - static_cast(Configurable::DisplayRGB | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape) + static_cast(Configurable::DisplayRGB | Configurable::DisplayCompositeColour) ); } diff --git a/Machines/Commodore/Vic-20/Keyboard.cpp b/Machines/Commodore/Vic-20/Keyboard.cpp index e868c9772..017c47cb2 100644 --- a/Machines/Commodore/Vic-20/Keyboard.cpp +++ b/Machines/Commodore/Vic-20/Keyboard.cpp @@ -24,7 +24,7 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { BIND(Z, KeyZ); BIND(X, KeyX); BIND(C, KeyC); BIND(V, KeyV); BIND(B, KeyB); BIND(N, KeyN); BIND(M, KeyM); - BIND(BackTick, KeyLeft); + BIND(BackTick, KeyLeftArrow); BIND(Hyphen, KeyPlus); BIND(Equals, KeyDash); BIND(F11, KeyGBP); @@ -35,8 +35,8 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { BIND(CloseSquareBracket, KeyAsterisk); BIND(Backslash, KeyRestore); - BIND(Hash, KeyUp); - BIND(F10, KeyUp); + BIND(Hash, KeyUpArrow); + BIND(F10, KeyUpArrow); BIND(Semicolon, KeyColon); BIND(Quote, KeySemicolon); @@ -66,6 +66,14 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { BIND(F3, KeyF3); BIND(F5, KeyF5); BIND(F7, KeyF7); + + // Mappings to virtual keys. + BIND(Left, KeyLeft); + BIND(Up, KeyUp); + BIND(F2, KeyF2); + BIND(F4, KeyF4); + BIND(F6, KeyF6); + BIND(F8, KeyF8); } #undef BIND return KeyboardMachine::MappedMachine::KeyNotMapped; diff --git a/Machines/Commodore/Vic-20/Keyboard.hpp b/Machines/Commodore/Vic-20/Keyboard.hpp index 7437f3420..8e66d2278 100644 --- a/Machines/Commodore/Vic-20/Keyboard.hpp +++ b/Machines/Commodore/Vic-20/Keyboard.hpp @@ -20,7 +20,7 @@ enum Key: uint16_t { Key2 = key(7, 0x01), Key4 = key(7, 0x02), Key6 = key(7, 0x04), Key8 = key(7, 0x08), Key0 = key(7, 0x10), KeyDash = key(7, 0x20), KeyHome = key(7, 0x40), KeyF7 = key(7, 0x80), KeyQ = key(6, 0x01), KeyE = key(6, 0x02), KeyT = key(6, 0x04), KeyU = key(6, 0x08), - KeyO = key(6, 0x10), KeyAt = key(6, 0x20), KeyUp = key(6, 0x40), KeyF5 = key(6, 0x80), + KeyO = key(6, 0x10), KeyAt = key(6, 0x20), KeyUpArrow = key(6, 0x40), KeyF5 = key(6, 0x80), KeyCBM = key(5, 0x01), KeyS = key(5, 0x02), KeyF = key(5, 0x04), KeyH = key(5, 0x08), KeyK = key(5, 0x10), KeyColon = key(5, 0x20), KeyEquals = key(5, 0x40), KeyF3 = key(5, 0x80), KeySpace = key(4, 0x01), KeyZ = key(4, 0x02), KeyC = key(4, 0x04), KeyB = key(4, 0x08), @@ -29,12 +29,21 @@ enum Key: uint16_t { KeyN = key(3, 0x10), KeyComma = key(3, 0x20), KeySlash = key(3, 0x40), KeyDown = key(3, 0x80), KeyControl = key(2, 0x01), KeyA = key(2, 0x02), KeyD = key(2, 0x04), KeyG = key(2, 0x08), KeyJ = key(2, 0x10), KeyL = key(2, 0x20), KeySemicolon = key(2, 0x40), KeyRight = key(2, 0x80), - KeyLeft = key(1, 0x01), KeyW = key(1, 0x02), KeyR = key(1, 0x04), KeyY = key(1, 0x08), + KeyLeftArrow= key(1, 0x01), KeyW = key(1, 0x02), KeyR = key(1, 0x04), KeyY = key(1, 0x08), KeyI = key(1, 0x10), KeyP = key(1, 0x20), KeyAsterisk = key(1, 0x40), KeyReturn = key(1, 0x80), Key1 = key(0, 0x01), Key3 = key(0, 0x02), Key5 = key(0, 0x04), Key7 = key(0, 0x08), Key9 = key(0, 0x10), KeyPlus = key(0, 0x20), KeyGBP = key(0, 0x40), KeyDelete = key(0, 0x80), - KeyRestore = 0xfffd + // Virtual keys. + KeyUp = 0xfff0, + KeyLeft = 0xfff1, + KeyF2 = 0xfff2, + KeyF4 = 0xfff3, + KeyF6 = 0xfff4, + KeyF8 = 0xfff5, + + // Physical keys not within the usual matrix. + KeyRestore = 0xfffd, #undef key }; diff --git a/Machines/Commodore/Vic-20/Vic20.cpp b/Machines/Commodore/Vic-20/Vic20.cpp index 2440c1fdb..94e328c00 100644 --- a/Machines/Commodore/Vic-20/Vic20.cpp +++ b/Machines/Commodore/Vic-20/Vic20.cpp @@ -287,7 +287,7 @@ class ConcreteMachine: public Configurable::Device, public CPU::MOS6502::BusHandler, public MOS::MOS6522::IRQDelegatePortHandler::Delegate, - public Utility::TypeRecipient, + public Utility::TypeRecipient, public Storage::Tape::BinaryTapePlayer::Delegate, public Machine, public ClockingHint::Observer, @@ -479,10 +479,28 @@ class ConcreteMachine: } void set_key_state(uint16_t key, bool is_pressed) final { - if(key != KeyRestore) + if(key < 0xfff0) { keyboard_via_port_handler_->set_key_state(key, is_pressed); - else - user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed); + } else { + switch(key) { + case KeyRestore: + user_port_via_.set_control_line_input(MOS::MOS6522::Port::A, MOS::MOS6522::Line::One, !is_pressed); + break; +#define ShiftedMap(source, target) \ + case source: \ + keyboard_via_port_handler_->set_key_state(KeyLShift, is_pressed); \ + keyboard_via_port_handler_->set_key_state(target, is_pressed); \ + break; + + ShiftedMap(KeyUp, KeyDown); + ShiftedMap(KeyLeft, KeyRight); + ShiftedMap(KeyF2, KeyF1); + ShiftedMap(KeyF4, KeyF3); + ShiftedMap(KeyF6, KeyF5); + ShiftedMap(KeyF8, KeyF7); +#undef ShiftedMap + } + } } void clear_all_keys() final { @@ -645,7 +663,11 @@ class ConcreteMachine: } void type_string(const std::string &string) final { - Utility::TypeRecipient::add_typer(string, std::make_unique()); + Utility::TypeRecipient::add_typer(string); + } + + bool can_type(char c) final { + return Utility::TypeRecipient::can_type(c); } void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) final { diff --git a/Machines/Electron/Electron.cpp b/Machines/Electron/Electron.cpp index a5c890207..89c6793b8 100644 --- a/Machines/Electron/Electron.cpp +++ b/Machines/Electron/Electron.cpp @@ -46,7 +46,7 @@ class ConcreteMachine: public Configurable::Device, public CPU::MOS6502::BusHandler, public Tape::Delegate, - public Utility::TypeRecipient, + public Utility::TypeRecipient, public Activity::Source { public: ConcreteMachine(const Analyser::Static::Acorn::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : @@ -346,10 +346,10 @@ class ConcreteMachine: } } - cycles_since_display_update_ += Cycles(static_cast(cycles)); - cycles_since_audio_update_ += Cycles(static_cast(cycles)); + cycles_since_display_update_ += Cycles(int(cycles)); + cycles_since_audio_update_ += Cycles(int(cycles)); if(cycles_since_audio_update_ > Cycles(16384)) update_audio(); - tape_.run_for(Cycles(static_cast(cycles))); + tape_.run_for(Cycles(int(cycles))); cycles_until_display_interrupt_ -= cycles; if(cycles_until_display_interrupt_ < 0) { @@ -358,8 +358,8 @@ class ConcreteMachine: queue_next_display_interrupt(); } - if(typer_) typer_->run_for(Cycles(static_cast(cycles))); - if(plus3_) plus3_->run_for(Cycles(4*static_cast(cycles))); + if(typer_) typer_->run_for(Cycles(int(cycles))); + if(plus3_) plus3_->run_for(Cycles(4*int(cycles))); if(shift_restart_counter_) { shift_restart_counter_ -= cycles; if(shift_restart_counter_ <= 0) { @@ -405,15 +405,19 @@ class ConcreteMachine: } HalfCycles get_typer_delay() final { - return m6502_.get_is_resetting() ? Cycles(625*25*128) : Cycles(0); // wait one second if resetting + return m6502_.get_is_resetting() ? Cycles(750'000) : Cycles(0); } HalfCycles get_typer_frequency() final { - return Cycles(625*128*2); // accept a new character every two frames + return Cycles(60'000); } void type_string(const std::string &string) final { - Utility::TypeRecipient::add_typer(string, std::make_unique()); + Utility::TypeRecipient::add_typer(string); + } + + bool can_type(char c) final { + return Utility::TypeRecipient::can_type(c); } KeyboardMapper *get_keyboard_mapper() final { diff --git a/Machines/Electron/Keyboard.cpp b/Machines/Electron/Keyboard.cpp index e54454171..1ecd358f4 100644 --- a/Machines/Electron/Keyboard.cpp +++ b/Machines/Electron/Keyboard.cpp @@ -66,7 +66,7 @@ uint16_t *CharacterMapper::sequence_for_character(char character) { /* ACK */ X, /* BEL */ X, /* BS */ KEYS(KeyDelete), /* HT */ X, /* LF */ KEYS(KeyReturn), /* VT */ X, - /* FF */ X, /* CR */ X, + /* FF */ X, /* CR */ KEYS(KeyReturn), /* SO */ X, /* SI */ X, /* DLE */ X, /* DC1 */ X, /* DC2 */ X, /* DC3 */ X, @@ -123,7 +123,7 @@ uint16_t *CharacterMapper::sequence_for_character(char character) { /* x */ SHIFT(KeyX), /* y */ SHIFT(KeyY), /* z */ SHIFT(KeyZ), /* { */ CTRL(KeyUp), /* | */ SHIFT(KeyRight), /* } */ CTRL(KeyDown), - /* ~ */ CTRL(KeyLeft) + /* ~ */ CTRL(KeyLeft), /* DEL */ KEYS(KeyDelete), }; #undef KEYS #undef SHIFT @@ -131,3 +131,7 @@ uint16_t *CharacterMapper::sequence_for_character(char character) { return table_lookup_sequence_for_character(key_sequences, sizeof(key_sequences), character); } + +bool CharacterMapper::needs_pause_after_key(uint16_t key) { + return key != KeyControl && key != KeyShift && key != KeyFunc; +} diff --git a/Machines/Electron/Keyboard.hpp b/Machines/Electron/Keyboard.hpp index 04a3b76cd..39c36d3ac 100644 --- a/Machines/Electron/Keyboard.hpp +++ b/Machines/Electron/Keyboard.hpp @@ -38,7 +38,10 @@ struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper { }; struct CharacterMapper: public ::Utility::CharacterMapper { - uint16_t *sequence_for_character(char character); + uint16_t *sequence_for_character(char character) override; + + bool needs_pause_after_reset_all_keys() override { return false; } + bool needs_pause_after_key(uint16_t key) override; }; }; diff --git a/Machines/KeyboardMachine.hpp b/Machines/KeyboardMachine.hpp index 84fcd517f..e4af3adb1 100644 --- a/Machines/KeyboardMachine.hpp +++ b/Machines/KeyboardMachine.hpp @@ -45,6 +45,11 @@ class Machine: public KeyActions { */ virtual void type_string(const std::string &); + /*! + @returns @c true if this machine can type the character @c c as part of a @c type_string; @c false otherwise. + */ + virtual bool can_type(char c) { return false; } + /*! Provides a destination for keyboard input. */ diff --git a/Machines/MSX/MSX.cpp b/Machines/MSX/MSX.cpp index 150f9eeb5..9f8fccb0d 100644 --- a/Machines/MSX/MSX.cpp +++ b/Machines/MSX/MSX.cpp @@ -369,6 +369,11 @@ class ConcreteMachine: ); } + bool can_type(char c) final { + // Make an effort to type the entire printable ASCII range. + return c >= 32 && c < 127; + } + // MARK: MSX::MemoryMap void map(int slot, std::size_t source_address, uint16_t destination_address, std::size_t length) final { assert(!(destination_address & 8191)); diff --git a/Machines/Oric/Oric.cpp b/Machines/Oric/Oric.cpp index b25162f6e..21d54f189 100644 --- a/Machines/Oric/Oric.cpp +++ b/Machines/Oric/Oric.cpp @@ -224,7 +224,6 @@ template class Co public Configurable::Device, public CPU::MOS6502::BusHandler, public MOS::MOS6522::IRQDelegatePortHandler::Delegate, - public Utility::TypeRecipient, public Storage::Tape::BinaryTapePlayer::Delegate, public DiskController::Delegate, public ClockingHint::Observer, @@ -590,6 +589,11 @@ template class Co string_serialiser_ = std::make_unique(string, true); } + bool can_type(char c) final { + // Make an effort to type the entire printable ASCII range. + return c >= 32 && c < 127; + } + // DiskController::Delegate void disk_controller_did_change_paged_item(DiskController *controller) final { switch(controller->get_paged_item()) { diff --git a/Machines/Utility/Typer.cpp b/Machines/Utility/Typer.cpp index 18a847968..efdda2036 100644 --- a/Machines/Utility/Typer.cpp +++ b/Machines/Utility/Typer.cpp @@ -8,63 +8,125 @@ #include "Typer.hpp" -#include - using namespace Utility; -Typer::Typer(const std::string &string, HalfCycles delay, HalfCycles frequency, std::unique_ptr character_mapper, Delegate *delegate) : +Typer::Typer(const std::string &string, HalfCycles delay, HalfCycles frequency, CharacterMapper &character_mapper, Delegate *delegate) : frequency_(frequency), counter_(-delay), delegate_(delegate), - character_mapper_(std::move(character_mapper)) { - std::ostringstream string_stream; - string_stream << Typer::BeginString << string << Typer::EndString; - string_ = string_stream.str(); + character_mapper_(character_mapper) { + // Retain only those characters that actually map to something. + if(sequence_for_character(Typer::BeginString)) { + string_ += Typer::BeginString; + } + if(sequence_for_character(Typer::EndString)) { + string_ += Typer::EndString; + } + + append(string); } void Typer::run_for(const HalfCycles duration) { - if(string_pointer_ < string_.size()) { - if(counter_ < 0 && counter_ + duration >= 0) { - if(!type_next_character()) { - delegate_->typer_reset(this); - } - } + if(string_pointer_ >= string_.size()) { + return; + } - counter_ += duration; - while(string_pointer_ < string_.size() && counter_ > frequency_) { - counter_ -= frequency_; - if(!type_next_character()) { - delegate_->typer_reset(this); - } + if(counter_ < 0 && counter_ + duration >= 0) { + if(!type_next_character()) { + delegate_->typer_reset(this); + } + } + + counter_ += duration; + while(string_pointer_ < string_.size() && counter_ > frequency_) { + counter_ -= frequency_; + if(!type_next_character()) { + delegate_->typer_reset(this); } } } -bool Typer::try_type_next_character() { - uint16_t *sequence = character_mapper_->sequence_for_character(string_[string_pointer_]); +void Typer::append(const std::string &string) { + // Remove any characters that are already completely done; + // otherwise things may accumulate here indefinitely. + // Note that sequence_for_character may seek to look one backwards, + // so keep 'the character before' if there was one. + if(string_pointer_ > 1) { + string_.erase(string_.begin(), string_.begin() + ssize_t(string_pointer_) - 1); + string_pointer_ = 1; + } + // If the final character in the string is not Typer::EndString + // then this machine doesn't need Begin and End, so don't worry about it. + ssize_t insertion_position = ssize_t(string_.size()); + if(string_.back() == Typer::EndString) --insertion_position; + + string_.reserve(string_.size() + string.size()); + for(const char c : string) { + if(sequence_for_character(c)) { + string_.insert(string_.begin() + insertion_position, c); + ++insertion_position; + } + } +} + +const uint16_t *Typer::sequence_for_character(char c) const { + const uint16_t *const sequence = character_mapper_.sequence_for_character(c); if(!sequence || sequence[0] == KeyboardMachine::MappedMachine::KeyNotMapped) { - return false; + return nullptr; + } + return sequence; +} + +uint16_t Typer::try_type_next_character() { + const uint16_t *const sequence = sequence_for_character(string_[string_pointer_]); + + if(!sequence) { + return 0; } - if(!phase_) delegate_->clear_all_keys(); - else { - delegate_->set_key_state(sequence[phase_ - 1], true); - return sequence[phase_] != KeyboardMachine::MappedMachine::KeyEndSequence; + // Advance phase. + ++phase_; + + // If this is the start of the output sequence, start with a reset all keys. + // Then pause if either: (i) the machine requires it; or (ii) this is the same + // character that was just typed, in which case the gap in presses will need to + // be clear. + if(phase_ == 1) { + delegate_->clear_all_keys(); + if(character_mapper_.needs_pause_after_reset_all_keys() || + (string_pointer_ > 0 && string_[string_pointer_ - 1] == string_[string_pointer_])) { + return 0xffff; // Arbitrarily. Anything non-zero will do. + } + ++phase_; } - return true; + // If the sequence is over, stop. + if(sequence[phase_ - 2] == KeyboardMachine::MappedMachine::KeyEndSequence) { + return 0; + } + + // Otherwise, type the key. + delegate_->set_key_state(sequence[phase_ - 2], true); + + return sequence[phase_ - 2]; } bool Typer::type_next_character() { if(string_pointer_ == string_.size()) return false; - if(!try_type_next_character()) { - phase_ = 0; - string_pointer_++; - if(string_pointer_ == string_.size()) return false; - } else { - phase_++; + while(true) { + const uint16_t key_pressed = try_type_next_character(); + + if(!key_pressed) { + phase_ = 0; + ++string_pointer_; + if(string_pointer_ == string_.size()) return false; + } + + if(character_mapper_.needs_pause_after_key(key_pressed)) { + break; + } } return true; @@ -74,7 +136,8 @@ bool Typer::type_next_character() { uint16_t *CharacterMapper::table_lookup_sequence_for_character(KeySequence *sequences, std::size_t length, char character) { std::size_t ucharacter = static_cast((unsigned char)character); - if(ucharacter > (length / sizeof(KeySequence))) return nullptr; + if(ucharacter >= (length / sizeof(KeySequence))) return nullptr; if(sequences[ucharacter][0] == KeyboardMachine::MappedMachine::KeyNotMapped) return nullptr; return sequences[ucharacter]; } + diff --git a/Machines/Utility/Typer.hpp b/Machines/Utility/Typer.hpp index fbf11a6e8..867d055c8 100644 --- a/Machines/Utility/Typer.hpp +++ b/Machines/Utility/Typer.hpp @@ -28,6 +28,18 @@ class CharacterMapper { /// @returns The EndSequence-terminated sequence of keys that would cause @c character to be typed. virtual uint16_t *sequence_for_character(char character) = 0; + /// The typer will automatically reset all keys in between each sequence that it types. + /// By default it will pause for one key's duration when doing so. Character mappers + /// can eliminate that pause by overriding this method. + /// @returns @c true if the typer should pause after performing a reset; @c false otherwise. + virtual bool needs_pause_after_reset_all_keys() { return true; } + + /// The typer will pause between every entry in a keyboard sequence. On some machines + /// that may not be necessary — it'll often depends on whether the machine needs time to + /// observe a modifier like shift before it sees the actual keypress. + /// @returns @c true if the typer should pause after forwarding @c key; @c false otherwise. + virtual bool needs_pause_after_key(uint16_t key) { return true; } + protected: typedef uint16_t KeySequence[16]; @@ -51,14 +63,21 @@ class Typer { public: class Delegate: public KeyboardMachine::KeyActions { public: + /// Informs the delegate that this typer has reached the end of its content. virtual void typer_reset(Typer *typer) = 0; }; - Typer(const std::string &string, HalfCycles delay, HalfCycles frequency, std::unique_ptr character_mapper, Delegate *delegate); + Typer(const std::string &string, HalfCycles delay, HalfCycles frequency, CharacterMapper &character_mapper, Delegate *delegate); + /// Advances for @c duration. void run_for(const HalfCycles duration); + + /// Types the next character now, if there is one. + /// @returns @c true if there was anything left to type; @c false otherwise. bool type_next_character(); - bool is_completed(); + + /// Adds the contents of @c str to the end of the current string. + void append(const std::string &str); const char BeginString = 0x02; // i.e. ASCII start of text const char EndString = 0x03; // i.e. ASCII end of text @@ -72,20 +91,36 @@ class Typer { int phase_ = 0; Delegate *delegate_; - std::unique_ptr character_mapper_; + CharacterMapper &character_mapper_; - bool try_type_next_character(); + uint16_t try_type_next_character(); + const uint16_t *sequence_for_character(char) const; }; /*! Provides a default base class for type recipients: classes that want to attach a single typer at a time and which may or may not want to nominate an initial delay and typing frequency. */ +template class TypeRecipient: public Typer::Delegate { protected: + template TypeRecipient(Args&&... args) : character_mapper(std::forward(args)...) {} + /// Attaches a typer to this class that will type @c string using @c character_mapper as a source. - void add_typer(const std::string &string, std::unique_ptr character_mapper) { - typer_ = std::make_unique(string, get_typer_delay(), get_typer_frequency(), std::move(character_mapper), this); + void add_typer(const std::string &string) { + if(!typer_) { + typer_ = std::make_unique(string, get_typer_delay(), get_typer_frequency(), character_mapper, this); + } else { + typer_->append(string); + } + } + + /*! + @returns @c true if the character mapper provides a mapping for @c c; @c false otherwise. + */ + bool can_type(char c) { + const auto sequence = character_mapper.sequence_for_character(c); + return sequence && sequence[0] != KeyboardMachine::MappedMachine::KeyNotMapped; } /*! @@ -108,6 +143,7 @@ class TypeRecipient: public Typer::Delegate { private: std::unique_ptr previous_typer_; + CMApper character_mapper; }; } diff --git a/Machines/ZX8081/Keyboard.cpp b/Machines/ZX8081/Keyboard.cpp index e3d177164..5dac570e4 100644 --- a/Machines/ZX8081/Keyboard.cpp +++ b/Machines/ZX8081/Keyboard.cpp @@ -28,6 +28,10 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) { BIND(FullStop, KeyDot); BIND(Enter, KeyEnter); BIND(Space, KeySpace); + + // Virtual keys follow. + BIND(Backspace, KeyDelete); + BIND(Escape, KeyBreak); } #undef BIND return KeyboardMachine::MappedMachine::KeyNotMapped; @@ -46,7 +50,7 @@ uint16_t *CharacterMapper::sequence_for_character(char character) { /* ACK */ X, /* BEL */ X, /* BS */ SHIFT(Key0), /* HT */ X, /* LF */ KEYS(KeyEnter), /* VT */ X, - /* FF */ X, /* CR */ X, + /* FF */ X, /* CR */ KEYS(KeyEnter), /* SO */ X, /* SI */ X, /* DLE */ X, /* DC1 */ X, /* DC2 */ X, /* DC3 */ X, @@ -112,7 +116,7 @@ uint16_t *CharacterMapper::sequence_for_character(char character) { /* ACK */ X, /* BEL */ X, /* BS */ SHIFT(Key0), /* HT */ X, /* LF */ KEYS(KeyEnter), /* VT */ X, - /* FF */ X, /* CR */ X, + /* FF */ X, /* CR */ KEYS(KeyEnter), /* SO */ X, /* SI */ X, /* DLE */ X, /* DC1 */ X, /* DC2 */ X, /* DC3 */ X, @@ -179,3 +183,7 @@ uint16_t *CharacterMapper::sequence_for_character(char character) { else return table_lookup_sequence_for_character(zx80_key_sequences, sizeof(zx80_key_sequences), character); } + +bool CharacterMapper::needs_pause_after_key(uint16_t key) { + return key != KeyShift; +} diff --git a/Machines/ZX8081/Keyboard.hpp b/Machines/ZX8081/Keyboard.hpp index 412281a81..294787c9b 100644 --- a/Machines/ZX8081/Keyboard.hpp +++ b/Machines/ZX8081/Keyboard.hpp @@ -23,6 +23,10 @@ enum Key: uint16_t { KeyP = 0x0500 | 0x01, KeyO = 0x0500 | 0x02, KeyI = 0x0500 | 0x04, KeyU = 0x0500 | 0x08, KeyY = 0x0500 | 0x10, KeyEnter = 0x0600 | 0x01, KeyL = 0x0600 | 0x02, KeyK = 0x0600 | 0x04, KeyJ = 0x0600 | 0x08, KeyH = 0x0600 | 0x10, KeySpace = 0x0700 | 0x01, KeyDot = 0x0700 | 0x02, KeyM = 0x0700 | 0x04, KeyN = 0x0700 | 0x08, KeyB = 0x0700 | 0x10, + + // Add some virtual keys; these do not exist on a real ZX80 or ZX81. They're just a convenience. + KeyDelete = 0x0801, + KeyBreak = 0x0802, }; struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper { @@ -32,7 +36,9 @@ struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper { class CharacterMapper: public ::Utility::CharacterMapper { public: CharacterMapper(bool is_zx81); - uint16_t *sequence_for_character(char character); + uint16_t *sequence_for_character(char character) override; + + bool needs_pause_after_key(uint16_t key) override; private: bool is_zx81_; diff --git a/Machines/ZX8081/ZX8081.cpp b/Machines/ZX8081/ZX8081.cpp index 16d1f1db5..8afde474e 100644 --- a/Machines/ZX8081/ZX8081.cpp +++ b/Machines/ZX8081/ZX8081.cpp @@ -62,11 +62,12 @@ template class ConcreteMachine: public MediaTarget::Machine, public KeyboardMachine::MappedMachine, public Configurable::Device, - public Utility::TypeRecipient, + public Utility::TypeRecipient, public CPU::Z80::BusHandler, public Machine { public: ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : + Utility::TypeRecipient(is_zx81), z80_(*this), tape_player_(ZX8081ClockRate), ay_(GI::AY38910::Personality::AY38910, audio_queue_), @@ -340,15 +341,38 @@ template class ConcreteMachine: } void type_string(const std::string &string) final { - Utility::TypeRecipient::add_typer(string, std::make_unique(is_zx81)); + Utility::TypeRecipient::add_typer(string); + } + + bool can_type(char c) final { + return Utility::TypeRecipient::can_type(c); } // MARK: - Keyboard void set_key_state(uint16_t key, bool is_pressed) final { - if(is_pressed) - key_states_[key >> 8] &= static_cast(~key); - else - key_states_[key >> 8] |= static_cast(key); + const auto line = key >> 8; + + // Check for special cases. + if(line == 8) { + switch(key) { + case KeyDelete: + // Map delete to shift+0. + set_key_state(KeyShift, is_pressed); + set_key_state(Key0, is_pressed); + break; + + case KeyBreak: + // Map break to shift+space. + set_key_state(KeyShift, is_pressed); + set_key_state(KeySpace, is_pressed); + break; + } + } else { + if(is_pressed) + key_states_[line] &= uint8_t(~key); + else + key_states_[line] |= uint8_t(key); + } } void clear_all_keys() final { @@ -373,8 +397,13 @@ template class ConcreteMachine: } // MARK: - Typer timing - HalfCycles get_typer_delay() final { return Cycles(7000000); } - HalfCycles get_typer_frequency() final { return Cycles(390000); } + HalfCycles get_typer_delay() final { + return z80_.get_is_resetting() ? Cycles(7'000'000) : Cycles(0); + } + + HalfCycles get_typer_frequency() final { + return Cycles(146'250); + } KeyboardMapper *get_keyboard_mapper() final { return &keyboard_mapper_; diff --git a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme index 3fddce824..4cd4a9880 100644 --- a/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme +++ b/OSBindings/Mac/Clock Signal.xcodeproj/xcshareddata/xcschemes/Clock Signal Kiosk.xcscheme @@ -31,7 +31,7 @@ + + + + - + - + @@ -121,18 +121,6 @@ - - - - - - - - - - - - @@ -178,7 +166,12 @@ - + + + + + + @@ -227,6 +220,7 @@ + diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index e517113e5..f60789319 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -531,8 +531,12 @@ class MachineDocument: } // MARK: Joystick-via-the-keyboard selection - @IBAction func useKeyboardAsKeyboard(_ sender: NSMenuItem?) { - machine.inputMode = .keyboard + @IBAction func useKeyboardAsPhysicalKeyboard(_ sender: NSMenuItem?) { + machine.inputMode = .keyboardPhysical + } + + @IBAction func useKeyboardAsLogicalKeyboard(_ sender: NSMenuItem?) { + machine.inputMode = .keyboardLogical } @IBAction func useKeyboardAsJoystick(_ sender: NSMenuItem?) { @@ -545,13 +549,22 @@ class MachineDocument: override func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { if let menuItem = item as? NSMenuItem { switch item.action { - case #selector(self.useKeyboardAsKeyboard): + case #selector(self.useKeyboardAsPhysicalKeyboard): if machine == nil || !machine.hasExclusiveKeyboard { menuItem.state = .off return false } - menuItem.state = machine.inputMode == .keyboard ? .on : .off + menuItem.state = machine.inputMode == .keyboardPhysical ? .on : .off + return true + + case #selector(self.useKeyboardAsLogicalKeyboard): + if machine == nil || !machine.hasExclusiveKeyboard { + menuItem.state = .off + return false + } + + menuItem.state = machine.inputMode == .keyboardLogical ? .on : .off return true case #selector(self.useKeyboardAsJoystick): diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h index d4830b7f5..a437e5f4d 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.h +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.h @@ -28,8 +28,9 @@ typedef NS_ENUM(NSInteger, CSMachineVideoSignal) { }; typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) { - CSMachineKeyboardInputModeKeyboard, - CSMachineKeyboardInputModeJoystick + CSMachineKeyboardInputModeKeyboardPhysical, + CSMachineKeyboardInputModeKeyboardLogical, + CSMachineKeyboardInputModeJoystick, }; @interface CSMissingROM: NSObject diff --git a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm index a671a87b7..35f5dfb81 100644 --- a/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm +++ b/OSBindings/Mac/Clock Signal/Machine/CSMachine.mm @@ -201,9 +201,10 @@ struct ActivityObserver: public Activity::Observer { return nil; } + // Use the keyboard as a joystick if the machine has no keyboard, or if it has a 'non-exclusive' keyboard. _inputMode = (_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive()) - ? CSMachineKeyboardInputModeKeyboard : CSMachineKeyboardInputModeJoystick; + ? CSMachineKeyboardInputModeKeyboardPhysical : CSMachineKeyboardInputModeJoystick; _leds = [[NSMutableArray alloc] init]; Activity::Source *const activity_source = _machine->activity_source(); @@ -429,7 +430,7 @@ struct ActivityObserver: public Activity::Observer { - (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed { auto keyboard_machine = _machine->keyboard_machine(); - if(keyboard_machine && (self.inputMode == CSMachineKeyboardInputModeKeyboard || !keyboard_machine->get_keyboard().is_exclusive())) { + if(keyboard_machine && (self.inputMode != CSMachineKeyboardInputModeJoystick || !keyboard_machine->get_keyboard().is_exclusive())) { Inputs::Keyboard::Key mapped_key = Inputs::Keyboard::Key::Help; // Make an innocuous default guess. #define BIND(source, dest) case source: mapped_key = Inputs::Keyboard::Key::dest; break; // Connect the Carbon-era Mac keyboard scancodes to Clock Signal's 'universal' enumeration in order @@ -503,9 +504,25 @@ struct ActivityObserver: public Activity::Observer { } } + // If this is logical mode and this key maps to a symbol, supply it + // as something to type. If this isn't logical mode, or this key doesn't + // map to a symbol, pass it along as a standard press. + if(self.inputMode == CSMachineKeyboardInputModeKeyboardLogical) { + @synchronized(self) { + if(pressedKey && keyboard_machine->can_type(pressedKey)) { + if(isPressed) { + char string[2] = { pressedKey, 0 }; + keyboard_machine->type_string(string); + } + return; + } + } + } + @synchronized(self) { keyboard.set_key_pressed(mapped_key, pressedKey, isPressed); } + return; } } diff --git a/OSBindings/SDL/main.cpp b/OSBindings/SDL/main.cpp index dec48042a..2ffbeb13e 100644 --- a/OSBindings/SDL/main.cpp +++ b/OSBindings/SDL/main.cpp @@ -469,7 +469,7 @@ int main(int argc, char *argv[]) { ParsedArguments arguments = parse_arguments(argc, argv); // This may be printed either as - const std::string usage_suffix = " [file] [OPTIONS] [--rompath={path to ROMs}] [--speed={speed multiplier, e.g. 1.5}]"; + const std::string usage_suffix = " [file] [OPTIONS] [--rompath={path to ROMs}] [--speed={speed multiplier, e.g. 1.5}]"; /* [--logical-keyboard] */ // Print a help message if requested. if(arguments.selections.find("help") != arguments.selections.end() || arguments.selections.find("h") != arguments.selections.end()) { @@ -610,6 +610,12 @@ int main(int argc, char *argv[]) { } } + // Check whether a 'logical' keyboard has been requested. + constexpr bool logical_keyboard = false; //arguments.selections.find("logical-keyboard") != arguments.selections.end(); + /* Logical keyboard entry is currently disabled; the attempt below to get logical input via SDL_GetKeyName is + too flawed — all letters are always capitals, shifted symbols are reported correctly on their first + press only, etc. I need to see whether other options are available. If not then SDL may not allow this feature.*/ + // Wire up the best-effort updater, its delegate, and the speaker delegate. machine_runner.machine = machine.get(); machine_runner.machine_mutex = &machine_mutex; @@ -890,14 +896,24 @@ int main(int argc, char *argv[]) { const bool is_pressed = event.type == SDL_KEYDOWN; if(keyboard_machine) { + // Grab the key's symbol. + char key_value = '\0'; + const char *key_name = SDL_GetKeyName(event.key.keysym.sym); + if(key_name[0] >= 0 && key_name[1] == 0) key_value = key_name[0]; + + // If a logical mapping was selected and a symbol was found, type it. + if(logical_keyboard && key_value != '\0' && keyboard_machine->can_type(key_value)) { + if(is_pressed) { + char string[] = { key_value, 0 }; + keyboard_machine->type_string(string); + } + break; + } + + // Otherwise, supply as a normal keypress. Inputs::Keyboard::Key key = Inputs::Keyboard::Key::Space; if(!KeyboardKeyForSDLScancode(event.key.keysym.scancode, key)) break; - if(keyboard_machine->get_keyboard().observed_keys().find(key) != keyboard_machine->get_keyboard().observed_keys().end()) { - char key_value = '\0'; - const char *key_name = SDL_GetKeyName(event.key.keysym.sym); - if(key_name[0] >= 0) key_value = key_name[0]; - keyboard_machine->get_keyboard().set_key_pressed(key, key_value, is_pressed); break; } diff --git a/Processors/6502/Implementation/6502Implementation.hpp b/Processors/6502/Implementation/6502Implementation.hpp index c32f544e2..be8c07d66 100644 --- a/Processors/6502/Implementation/6502Implementation.hpp +++ b/Processors/6502/Implementation/6502Implementation.hpp @@ -705,7 +705,7 @@ void ProcessorBase::set_reset_line(bool active) { } bool ProcessorBase::get_is_resetting() { - return !!(interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn)); + return interrupt_requests_ & (InterruptRequestFlags::Reset | InterruptRequestFlags::PowerOn); } void ProcessorBase::set_power_on(bool active) { diff --git a/Processors/Z80/Implementation/Z80Storage.cpp b/Processors/Z80/Implementation/Z80Storage.cpp index 66c13cb62..14bb76de8 100644 --- a/Processors/Z80/Implementation/Z80Storage.cpp +++ b/Processors/Z80/Implementation/Z80Storage.cpp @@ -550,3 +550,7 @@ bool ProcessorBase::is_starting_new_instruction() { current_instruction_page_ == &base_page_ && scheduled_program_counter_ == &base_page_.fetch_decode_execute[0]; } + +bool ProcessorBase::get_is_resetting() { + return request_status_ & (Interrupt::PowerOn | Interrupt::Reset); +} diff --git a/Processors/Z80/Z80.hpp b/Processors/Z80/Z80.hpp index d0ee4a1a9..718ebea27 100644 --- a/Processors/Z80/Z80.hpp +++ b/Processors/Z80/Z80.hpp @@ -219,6 +219,13 @@ class ProcessorBase: public ProcessorStorage { */ inline void set_reset_line(bool value); + /*! + Gets whether the Z80 would reset at the next opportunity. + + @returns @c true if the line is logically active; @c false otherwise. + */ + bool get_is_resetting(); + /*! This emulation automatically sets itself up in power-on state at creation, which has the effect of triggering a reset at the first opportunity. Use @c reset_power_on to disable that behaviour.