mirror of
https://github.com/TomHarte/CLK.git
synced 2024-11-26 08:49:37 +00:00
Merge pull request #763 from TomHarte/LogicalKeyboards
Adds support for 'logical' keyboard entry
This commit is contained in:
commit
1a539521f2
@ -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_;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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];
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
};
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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));
|
||||
|
@ -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()) {
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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_;
|
||||
|
@ -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_;
|
||||
|
@ -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 = ""/Users/thomasharte/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Soft/Master System/R-Type (NTSC).sms""
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = "--logical-keyboard"
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
argument = ""/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""
|
||||
isEnabled = "YES">
|
||||
</CommandLineArgument>
|
||||
<CommandLineArgument
|
||||
|
@ -67,7 +67,7 @@
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
buildConfiguration = "Release"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
enableASanStackUseAfterReturn = "YES"
|
||||
|
@ -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>
|
||||
|
@ -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):
|
||||
|
@ -28,8 +28,9 @@ typedef NS_ENUM(NSInteger, CSMachineVideoSignal) {
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
|
||||
CSMachineKeyboardInputModeKeyboard,
|
||||
CSMachineKeyboardInputModeJoystick
|
||||
CSMachineKeyboardInputModeKeyboardPhysical,
|
||||
CSMachineKeyboardInputModeKeyboardLogical,
|
||||
CSMachineKeyboardInputModeJoystick,
|
||||
};
|
||||
|
||||
@interface CSMissingROM: NSObject
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user