1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-05 10:28:58 +00:00

Merge pull request #763 from TomHarte/LogicalKeyboards

Adds support for 'logical' keyboard entry
This commit is contained in:
Thomas Harte 2020-03-01 23:21:46 -05:00 committed by GitHub
commit 1a539521f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 408 additions and 117 deletions

View File

@ -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_;
}

View File

@ -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;
};

View File

@ -786,7 +786,7 @@ template <bool has_fdc> class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::MappedMachine,
public Utility::TypeRecipient,
public Utility::TypeRecipient<CharacterMapper>,
public CPU::Z80::BusHandler,
public ClockingHint::Observer,
public Configurable::Device,
@ -1079,16 +1079,19 @@ template <bool has_fdc> class ConcreteMachine:
// MARK: - Keyboard
void type_string(const std::string &string) final {
std::unique_ptr<CharacterMapper> mapper(new CharacterMapper());
Utility::TypeRecipient::add_typer(string, std::move(mapper));
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
}
bool can_type(char c) final {
return Utility::TypeRecipient<CharacterMapper>::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 <bool has_fdc> class ConcreteMachine:
KeyboardState key_state_;
AmstradCPC::KeyboardMapper keyboard_mapper_;
uint8_t ram_[1024 * 1024];
bool has_run_ = false;
uint8_t ram_[128 * 1024];
};
}

View File

@ -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;
}

View File

@ -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;
};
};

View File

@ -858,6 +858,11 @@ template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(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<std::unique_ptr<Configurable::Option>> get_options() final {
return Apple::II::get_options();

View File

@ -44,7 +44,7 @@ namespace ST {
std::vector<std::unique_ptr<Configurable::Option>> get_options() {
return Configurable::standard_options(
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayCompositeColour | Configurable::QuickLoadTape)
static_cast<Configurable::StandardOptions>(Configurable::DisplayRGB | Configurable::DisplayCompositeColour)
);
}

View File

@ -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;

View File

@ -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
};

View File

@ -287,7 +287,7 @@ class ConcreteMachine:
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,
public Utility::TypeRecipient,
public Utility::TypeRecipient<CharacterMapper>,
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<CharacterMapper>());
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
}
bool can_type(char c) final {
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
void tape_did_change_input(Storage::Tape::BinaryTapePlayer *tape) final {

View File

@ -46,7 +46,7 @@ class ConcreteMachine:
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public Tape::Delegate,
public Utility::TypeRecipient,
public Utility::TypeRecipient<CharacterMapper>,
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<int>(cycles));
cycles_since_audio_update_ += Cycles(static_cast<int>(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<int>(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<int>(cycles)));
if(plus3_) plus3_->run_for(Cycles(4*static_cast<int>(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<CharacterMapper>());
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
}
bool can_type(char c) final {
return Utility::TypeRecipient<CharacterMapper>::can_type(c);
}
KeyboardMapper *get_keyboard_mapper() final {

View File

@ -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;
}

View File

@ -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;
};
};

View File

@ -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.
*/

View File

@ -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));

View File

@ -224,7 +224,6 @@ template <Analyser::Static::Oric::Target::DiskInterface disk_interface> 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 <Analyser::Static::Oric::Target::DiskInterface disk_interface> class Co
string_serialiser_ = std::make_unique<Utility::StringSerialiser>(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()) {

View File

@ -8,63 +8,125 @@
#include "Typer.hpp"
#include <sstream>
using namespace Utility;
Typer::Typer(const std::string &string, HalfCycles delay, HalfCycles frequency, std::unique_ptr<CharacterMapper> 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<std::size_t>((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];
}

View File

@ -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<CharacterMapper> 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<CharacterMapper> 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 <typename CMApper>
class TypeRecipient: public Typer::Delegate {
protected:
template <typename... Args> TypeRecipient(Args&&... args) : character_mapper(std::forward<Args>(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<CharacterMapper> character_mapper) {
typer_ = std::make_unique<Typer>(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<Typer>(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<Typer> previous_typer_;
CMApper character_mapper;
};
}

View File

@ -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;
}

View File

@ -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_;

View File

@ -62,11 +62,12 @@ template<bool is_zx81> class ConcreteMachine:
public MediaTarget::Machine,
public KeyboardMachine::MappedMachine,
public Configurable::Device,
public Utility::TypeRecipient,
public Utility::TypeRecipient<CharacterMapper>,
public CPU::Z80::BusHandler,
public Machine {
public:
ConcreteMachine(const Analyser::Static::ZX8081::Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
Utility::TypeRecipient<CharacterMapper>(is_zx81),
z80_(*this),
tape_player_(ZX8081ClockRate),
ay_(GI::AY38910::Personality::AY38910, audio_queue_),
@ -340,15 +341,38 @@ template<bool is_zx81> class ConcreteMachine:
}
void type_string(const std::string &string) final {
Utility::TypeRecipient::add_typer(string, std::make_unique<CharacterMapper>(is_zx81));
Utility::TypeRecipient<CharacterMapper>::add_typer(string);
}
bool can_type(char c) final {
return Utility::TypeRecipient<CharacterMapper>::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<uint8_t>(~key);
else
key_states_[key >> 8] |= static_cast<uint8_t>(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<bool is_zx81> 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_;

View File

@ -31,7 +31,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Release"
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
disableMainThreadChecker = "YES"
@ -58,6 +58,14 @@
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Master System/R-Type (NTSC).sms&quot;"
isEnabled = "NO">
</CommandLineArgument>
<CommandLineArgument
argument = "--logical-keyboard"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument
argument = "&quot;/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Amstrad CPC/Amstrad CPC [TOSEC]/Amstrad CPC - Applications - [DSK] (TOSEC-v2011-08-31_CM)/Tasword (1984)(Tasman Software).dsk&quot;"
isEnabled = "YES">
</CommandLineArgument>
<CommandLineArgument

View File

@ -67,7 +67,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
enableASanStackUseAfterReturn = "YES"

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15705" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15705"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
@ -121,18 +121,6 @@
<action selector="insertMedia:" target="-1" id="9Hs-9J-dlY"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="rXU-KX-GkZ"/>
<menuItem title="Page Setup…" enabled="NO" keyEquivalent="P" id="qIS-W8-SiK">
<modifierMask key="keyEquivalentModifierMask" shift="YES" command="YES"/>
<connections>
<action selector="runPageLayout:" target="-1" id="Din-rz-gC5"/>
</connections>
</menuItem>
<menuItem title="Print…" enabled="NO" keyEquivalent="p" id="aTl-1u-JFS">
<connections>
<action selector="printDocument:" target="-1" id="qaZ-4w-aoO"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
@ -178,7 +166,12 @@
<items>
<menuItem title="Use Keyboard as Keyboard" state="on" tag="100" keyEquivalent="k" id="TfX-0B-j4U">
<connections>
<action selector="useKeyboardAsKeyboard:" target="-1" id="6fl-fS-Oe9"/>
<action selector="useKeyboardAsPhysicalKeyboard:" target="-1" id="3NX-jl-F4z"/>
</connections>
</menuItem>
<menuItem title="Map Typed Characters by Symbol" tag="102" keyEquivalent="l" id="Nzx-y4-1WD">
<connections>
<action selector="useKeyboardAsLogicalKeyboard:" target="-1" id="mo3-yB-wVB"/>
</connections>
</menuItem>
<menuItem title="Use Keyboard as Joystick" tag="101" enabled="NO" keyEquivalent="j" id="5mn-ch-Xv6">
@ -227,6 +220,7 @@
</menu>
</menuItem>
</items>
<point key="canvasLocation" x="140" y="154"/>
</menu>
</objects>
</document>

View File

@ -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):

View File

@ -28,8 +28,9 @@ typedef NS_ENUM(NSInteger, CSMachineVideoSignal) {
};
typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
CSMachineKeyboardInputModeKeyboard,
CSMachineKeyboardInputModeJoystick
CSMachineKeyboardInputModeKeyboardPhysical,
CSMachineKeyboardInputModeKeyboardLogical,
CSMachineKeyboardInputModeJoystick,
};
@interface CSMissingROM: NSObject

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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) {

View File

@ -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);
}

View File

@ -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.