1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-07-09 06:29:33 +00:00

Merge pull request #573 from TomHarte/SmallKeyboard

Extends the concept of a 'keyboard' to sets of keys less than a full keyboard in size
This commit is contained in:
Thomas Harte 2018-10-24 22:32:55 -04:00 committed by GitHub
commit 38c130df2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 319 additions and 145 deletions

View File

@ -10,7 +10,8 @@
using namespace Analyser::Dynamic;
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) {
MultiKeyboardMachine::MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines) :
keyboard_(machines_) {
for(const auto &machine: machines) {
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) machines_.push_back(keyboard_machine);
@ -35,9 +36,34 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
}
}
void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
return keyboard_;
}
MultiKeyboardMachine::MultiKeyboard::MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines)
: machines_(machines) {
for(const auto &machine: machines_) {
uint16_t mapped_key = machine->get_keyboard_mapper()->mapped_key_for_key(key);
if(mapped_key != KeyNotMapped) machine->set_key_state(mapped_key, is_pressed);
observed_keys_.insert(machine->get_keyboard().observed_keys().begin(), machine->get_keyboard().observed_keys().end());
is_exclusive_ |= machine->get_keyboard().is_exclusive();
}
}
void MultiKeyboardMachine::MultiKeyboard::set_key_pressed(Key key, char value, bool is_pressed) {
for(const auto &machine: machines_) {
machine->get_keyboard().set_key_pressed(key, value, is_pressed);
}
}
void MultiKeyboardMachine::MultiKeyboard::reset_all_keys() {
for(const auto &machine: machines_) {
machine->get_keyboard().reset_all_keys();
}
}
const std::set<Inputs::Keyboard::Key> &MultiKeyboardMachine::MultiKeyboard::observed_keys() {
return observed_keys_;
}
bool MultiKeyboardMachine::MultiKeyboard::is_exclusive() {
return is_exclusive_;
}

View File

@ -25,6 +25,25 @@ namespace Dynamic {
order of delivered messages.
*/
class MultiKeyboardMachine: public KeyboardMachine::Machine {
private:
std::vector<::KeyboardMachine::Machine *> machines_;
class MultiKeyboard: public Inputs::Keyboard {
public:
MultiKeyboard(const std::vector<::KeyboardMachine::Machine *> &machines);
void set_key_pressed(Key key, char value, bool is_pressed) override;
void reset_all_keys() override;
const std::set<Key> &observed_keys() override;
bool is_exclusive() override;
private:
const std::vector<::KeyboardMachine::Machine *> &machines_;
std::set<Key> observed_keys_;
bool is_exclusive_ = false;
};
MultiKeyboard keyboard_;
public:
MultiKeyboardMachine(const std::vector<std::unique_ptr<::Machine::DynamicMachine>> &machines);
@ -32,10 +51,7 @@ class MultiKeyboardMachine: public KeyboardMachine::Machine {
void clear_all_keys() override;
void set_key_state(uint16_t key, bool is_pressed) override;
void type_string(const std::string &) override;
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
private:
std::vector<::KeyboardMachine::Machine *> machines_;
Inputs::Keyboard &get_keyboard() override;
};
}

View File

@ -702,6 +702,23 @@ HalfCycles TMS9918::get_time_until_interrupt() {
return half_cycles_before_internal_cycles(std::min(local_cycles_until_line_interrupt, time_until_frame_interrupt));
}
HalfCycles TMS9918::get_time_until_line(int line) {
if(line < 0) line += mode_timing_.total_lines;
int cycles_to_next_interrupt_threshold = mode_timing_.line_interrupt_position - write_pointer_.column;
int line_of_next_interrupt_threshold = write_pointer_.row;
if(cycles_to_next_interrupt_threshold <= 0) {
cycles_to_next_interrupt_threshold += 342;
++line_of_next_interrupt_threshold;
}
if(line_of_next_interrupt_threshold > line) {
line += mode_timing_.total_lines;
}
return half_cycles_before_internal_cycles(cycles_to_next_interrupt_threshold + (line - line_of_next_interrupt_threshold)*342);
}
bool TMS9918::get_interrupt_line() {
return ((status_ & StatusInterrupt) && generate_interrupts_) || (enable_line_interrupts_ && line_interrupt_pending_);
}

View File

@ -74,6 +74,16 @@ class TMS9918: public Base {
*/
HalfCycles get_time_until_interrupt();
/*!
Returns the amount of time until the nominated line interrupt position is
reached on line @c line. If no line interrupt position is defined for
this VDP, returns the time until the 'beginning' of that line, whatever
that may mean.
@line is relative to the first pixel line of the display and may be negative.
*/
HalfCycles get_time_until_line(int line);
/*!
@returns @c true if the interrupt line is currently active; @c false otherwise.
*/

View File

@ -10,7 +10,13 @@
using namespace Inputs;
Keyboard::Keyboard() {}
Keyboard::Keyboard() {
for(int k = 0; k < int(Key::Help); ++k) {
observed_keys_.insert(Key(k));
}
}
Keyboard::Keyboard(const std::set<Key> &observed_keys) : observed_keys_(observed_keys), is_exclusive_(false) {}
void Keyboard::set_key_pressed(Key key, char value, bool is_pressed) {
std::size_t key_offset = static_cast<std::size_t>(key);
@ -36,3 +42,11 @@ bool Keyboard::get_key_state(Key key) {
if(key_offset >= key_states_.size()) return false;
return key_states_[key_offset];
}
const std::set<Keyboard::Key> &Keyboard::observed_keys() {
return observed_keys_;
}
bool Keyboard::is_exclusive() {
return is_exclusive_;
}

View File

@ -10,6 +10,7 @@
#define Keyboard_hpp
#include <vector>
#include <set>
namespace Inputs {
@ -20,8 +21,6 @@ namespace Inputs {
*/
class Keyboard {
public:
Keyboard();
enum class Key {
Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, PrintScreen, ScrollLock, Pause,
BackTick, k1, k2, k3, k4, k5, k6, k7, k8, k9, k0, Hyphen, Equals, BackSpace,
@ -39,10 +38,26 @@ class Keyboard {
Help
};
/// Constructs a Keyboard that declares itself to observe all keys.
Keyboard();
/// Constructs a Keyboard that declares itself to observe only members of @c observed_keys.
Keyboard(const std::set<Key> &observed_keys);
// Host interface.
virtual void set_key_pressed(Key key, char value, bool is_pressed);
virtual void reset_all_keys();
/// @returns a set of all Keys that this keyboard responds to.
virtual const std::set<Key> &observed_keys();
/*
@returns @c true if this keyboard, on its original machine, looked
like a complete keyboard i.e. if a user would expect this keyboard
to be the only thing a real keyboard maps to.
*/
virtual bool is_exclusive();
// Delegate interface.
struct Delegate {
virtual void keyboard_did_change_key(Keyboard *keyboard, Key key, bool is_pressed) = 0;
@ -52,8 +67,10 @@ class Keyboard {
bool get_key_state(Key key);
private:
std::set<Key> observed_keys_;
std::vector<bool> key_states_;
Delegate *delegate_ = nullptr;
bool is_exclusive_ = true;
};
}

View File

@ -757,7 +757,7 @@ class i8255PortHandler : public Intel::i8255::PortHandler {
template <bool has_fdc> class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public KeyboardMachine::MappedMachine,
public Utility::TypeRecipient,
public CPU::Z80::BusHandler,
public ClockingHint::Observer,

View File

@ -75,9 +75,9 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define X {KeyboardMachine::MappedMachine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@ -33,7 +33,7 @@ enum Key: uint16_t {
#undef Line
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};

View File

@ -40,7 +40,7 @@ namespace {
template <Analyser::Static::AppleII::Target::Model model> class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public KeyboardMachine::MappedMachine,
public CPU::MOS6502::BusHandler,
public Inputs::Keyboard,
public AppleII::Machine,

View File

@ -68,13 +68,13 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(F7, KeyF7);
}
#undef BIND
return KeyboardMachine::Machine::KeyNotMapped;
return KeyboardMachine::MappedMachine::KeyNotMapped;
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define SHIFT(...) {KeyLShift, __VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define X {KeyboardMachine::MappedMachine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@ -38,7 +38,7 @@ enum Key: uint16_t {
#undef key
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};

View File

@ -281,7 +281,7 @@ class Joystick: public Inputs::ConcreteJoystick {
class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public KeyboardMachine::MappedMachine,
public JoystickMachine::Machine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,

View File

@ -42,7 +42,7 @@ class ConcreteMachine:
public Machine,
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public KeyboardMachine::MappedMachine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public Tape::Delegate,

View File

@ -56,10 +56,10 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define CTRL(...) {KeyControl, __VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define X {KeyboardMachine::MappedMachine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@ -33,7 +33,7 @@ enum Key: uint16_t {
KeyBreak = 0xfffd,
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};

View File

@ -10,27 +10,27 @@
using namespace KeyboardMachine;
Machine::Machine() {
MappedMachine::MappedMachine() {
keyboard_.set_delegate(this);
}
void Machine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
void MappedMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
uint16_t mapped_key = get_keyboard_mapper()->mapped_key_for_key(key);
if(mapped_key != KeyNotMapped) set_key_state(mapped_key, is_pressed);
}
void Machine::reset_all_keys(Inputs::Keyboard *keyboard) {
void MappedMachine::reset_all_keys(Inputs::Keyboard *keyboard) {
// TODO: unify naming.
clear_all_keys();
}
Inputs::Keyboard &Machine::get_keyboard() {
Inputs::Keyboard &MappedMachine::get_keyboard() {
return keyboard_;
}
void Machine::type_string(const std::string &) {
}
Machine::KeyboardMapper *Machine::get_keyboard_mapper() {
MappedMachine::KeyboardMapper *MappedMachine::get_keyboard_mapper() {
return nullptr;
}

View File

@ -33,13 +33,10 @@ struct KeyActions {
};
/*!
Describes the full functionality of being an emulated machine with a keyboard: not just being
able to receive key actions, but being able to vend a generic keyboard and a keyboard mapper.
Describes an emulated machine which exposes a keyboard and accepts a typed string.
*/
class Machine: public Inputs::Keyboard::Delegate, public KeyActions {
class Machine: public KeyActions {
public:
Machine();
/*!
Causes the machine to attempt to type the supplied string.
@ -50,7 +47,16 @@ class Machine: public Inputs::Keyboard::Delegate, public KeyActions {
/*!
Provides a destination for keyboard input.
*/
virtual Inputs::Keyboard &get_keyboard();
virtual Inputs::Keyboard &get_keyboard() = 0;
};
/*!
Provides a base class for machines that want to provide a keyboard mapper,
allowing automatic mapping from keyboard inputs to KeyActions.
*/
class MappedMachine: public Inputs::Keyboard::Delegate, public Machine {
public:
MappedMachine();
/*!
A keyboard mapper attempts to provide a physical mapping between host keys and emulated keys.
@ -76,6 +82,12 @@ class Machine: public Inputs::Keyboard::Delegate, public KeyActions {
*/
virtual KeyboardMapper *get_keyboard_mapper();
/*!
Provides a keyboard that obtains this machine's keyboard mapper, maps
the key and supplies it via the KeyActions.
*/
virtual Inputs::Keyboard &get_keyboard() override;
private:
void keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) override;
void reset_all_keys(Inputs::Keyboard *keyboard) override;

View File

@ -57,5 +57,5 @@ uint16_t MSX::KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
default: break;
}
#undef BIND
return KeyboardMachine::Machine::KeyNotMapped;
return KeyboardMachine::MappedMachine::KeyNotMapped;
}

View File

@ -33,7 +33,7 @@ enum Key: uint16_t {
#undef Line
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};

View File

@ -139,7 +139,7 @@ class ConcreteMachine:
public CPU::Z80::BusHandler,
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public KeyboardMachine::MappedMachine,
public Configurable::Device,
public JoystickMachine::Machine,
public MemoryMap,

View File

@ -15,6 +15,7 @@
#include "../CRTMachine.hpp"
#include "../JoystickMachine.hpp"
#include "../KeyboardMachine.hpp"
#include "../../ClockReceiver/ForceInline.hpp"
@ -81,6 +82,8 @@ class ConcreteMachine:
public Machine,
public CPU::Z80::BusHandler,
public CRTMachine::Machine,
public KeyboardMachine::Machine,
public Inputs::Keyboard::Delegate,
public Configurable::Device,
public JoystickMachine::Machine {
@ -94,7 +97,8 @@ class ConcreteMachine:
(target.model == Target::Model::SG1000) ? TI::SN76489::Personality::SN76489 : TI::SN76489::Personality::SMS,
audio_queue_,
sn76489_divider),
speaker_(sn76489_) {
speaker_(sn76489_),
keyboard_({Inputs::Keyboard::Key::Enter, Inputs::Keyboard::Key::Escape}) {
// Pick the clock rate based on the region.
const double clock_rate = target.region == Target::Region::Europe ? 3546893.0 : 3579540.0;
speaker_.set_input_rate(static_cast<float>(clock_rate / sn76489_divider));
@ -146,6 +150,8 @@ class ConcreteMachine:
// Apple a relatively low low-pass filter. More guidance needed here.
speaker_.set_high_frequency_cutoff(8000);
keyboard_.set_delegate(this);
}
~ConcreteMachine() {
@ -164,6 +170,8 @@ class ConcreteMachine:
(region_ == Target::Region::Europe) ?
TI::TMS::TVStandard::PAL : TI::TMS::TVStandard::NTSC);
get_crt()->set_video_signal(Outputs::CRT::VideoSignal::Composite);
time_until_debounce_ = vdp_->get_time_until_line(-1);
}
void close_output() override {
@ -213,7 +221,7 @@ class ConcreteMachine:
}
if(write_pointers_[address >> 10]) write_pointers_[address >> 10][address & 1023] = *cycle.value;
else LOG("Ignored write to ROM");
// else LOG("Ignored write to ROM");
break;
case CPU::Z80::PartialMachineCycle::Input:
@ -320,6 +328,15 @@ class ConcreteMachine:
}
}
// The pause button is debounced and takes effect only one line before pixels
// begin; time_until_debounce_ keeps track of the time until then.
time_until_debounce_ -= cycle.length;
if(time_until_debounce_ <= HalfCycles(0)) {
z80_.set_non_maskable_interrupt_line(pause_is_pressed_);
update_video();
time_until_debounce_ = vdp_->get_time_until_line(-1);
}
return HalfCycles(0);
}
@ -333,6 +350,23 @@ class ConcreteMachine:
return joysticks_;
}
// MARK: - Keyboard (i.e. the pause and reset buttons).
Inputs::Keyboard &get_keyboard() override {
return keyboard_;
}
void keyboard_did_change_key(Inputs::Keyboard *, Inputs::Keyboard::Key key, bool is_pressed) override {
if(key == Inputs::Keyboard::Key::Enter) {
pause_is_pressed_ = is_pressed;
} else if(key == Inputs::Keyboard::Key::Escape) {
reset_is_pressed_ = is_pressed;
}
}
void reset_all_keys(Inputs::Keyboard *) override {
}
// MARK: - Configuration options.
std::vector<std::unique_ptr<Configurable::Option>> get_options() override {
return Sega::MasterSystem::get_options();
@ -388,10 +422,13 @@ class ConcreteMachine:
Outputs::Speaker::LowpassSpeaker<TI::SN76489> speaker_;
std::vector<std::unique_ptr<Inputs::Joystick>> joysticks_;
Inputs::Keyboard keyboard_;
bool reset_is_pressed_ = false, pause_is_pressed_ = false;
HalfCycles time_since_vdp_update_;
HalfCycles time_since_sn76489_update_;
HalfCycles time_until_interrupt_;
HalfCycles time_until_debounce_;
uint8_t ram_[8*1024];
uint8_t bios_[8*1024];

View File

@ -48,13 +48,13 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
}
#undef BIND
return KeyboardMachine::Machine::KeyNotMapped;
return KeyboardMachine::MappedMachine::KeyNotMapped;
}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define SHIFT(...) {KeyLeftShift, __VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define X {KeyboardMachine::MappedMachine::KeyNotMapped}
static KeySequence key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@ -35,7 +35,7 @@ enum Key: uint16_t {
KeyNMI = 0xfffd,
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};

View File

@ -194,7 +194,7 @@ class VIAPortHandler: public MOS::MOS6522::IRQDelegatePortHandler {
template <Analyser::Static::Oric::Target::DiskInterface disk_interface> class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public KeyboardMachine::MappedMachine,
public Configurable::Device,
public CPU::MOS6502::BusHandler,
public MOS::MOS6522::IRQDelegatePortHandler::Delegate,

View File

@ -43,14 +43,14 @@ void Typer::run_for(const HalfCycles duration) {
bool Typer::try_type_next_character() {
uint16_t *sequence = character_mapper_->sequence_for_character(string_[string_pointer_]);
if(!sequence || sequence[0] == KeyboardMachine::Machine::KeyNotMapped) {
if(!sequence || sequence[0] == KeyboardMachine::MappedMachine::KeyNotMapped) {
return false;
}
if(!phase_) delegate_->clear_all_keys();
else {
delegate_->set_key_state(sequence[phase_ - 1], true);
return sequence[phase_] != KeyboardMachine::Machine::KeyEndSequence;
return sequence[phase_] != KeyboardMachine::MappedMachine::KeyEndSequence;
}
return true;
@ -75,6 +75,6 @@ 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(sequences[ucharacter][0] == KeyboardMachine::Machine::KeyNotMapped) return nullptr;
if(sequences[ucharacter][0] == KeyboardMachine::MappedMachine::KeyNotMapped) return nullptr;
return sequences[ucharacter];
}

View File

@ -30,15 +30,15 @@ uint16_t KeyboardMapper::mapped_key_for_key(Inputs::Keyboard::Key key) {
BIND(Space, KeySpace);
}
#undef BIND
return KeyboardMachine::Machine::KeyNotMapped;
return KeyboardMachine::MappedMachine::KeyNotMapped;
}
CharacterMapper::CharacterMapper(bool is_zx81) : is_zx81_(is_zx81) {}
uint16_t *CharacterMapper::sequence_for_character(char character) {
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::Machine::KeyEndSequence}
#define X {KeyboardMachine::Machine::KeyNotMapped}
#define KEYS(...) {__VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define SHIFT(...) {KeyShift, __VA_ARGS__, KeyboardMachine::MappedMachine::KeyEndSequence}
#define X {KeyboardMachine::MappedMachine::KeyNotMapped}
static KeySequence zx81_key_sequences[] = {
/* NUL */ X, /* SOH */ X,
/* STX */ X, /* ETX */ X,

View File

@ -25,7 +25,7 @@ enum Key: uint16_t {
KeySpace = 0x0700 | 0x01, KeyDot = 0x0700 | 0x02, KeyM = 0x0700 | 0x04, KeyN = 0x0700 | 0x08, KeyB = 0x0700 | 0x10,
};
struct KeyboardMapper: public KeyboardMachine::Machine::KeyboardMapper {
struct KeyboardMapper: public KeyboardMachine::MappedMachine::KeyboardMapper {
uint16_t mapped_key_for_key(Inputs::Keyboard::Key key);
};

View File

@ -60,7 +60,7 @@ std::vector<std::unique_ptr<Configurable::Option>> get_options() {
template<bool is_zx81> class ConcreteMachine:
public CRTMachine::Machine,
public MediaTarget::Machine,
public KeyboardMachine::Machine,
public KeyboardMachine::MappedMachine,
public Configurable::Device,
public Utility::TypeRecipient,
public CPU::Z80::BusHandler,

View File

@ -301,7 +301,7 @@ class MachineDocument:
if let menuItem = item as? NSMenuItem {
switch item.action {
case #selector(self.useKeyboardAsKeyboard):
if machine == nil || !machine.hasKeyboard {
if machine == nil || !machine.hasExclusiveKeyboard {
menuItem.state = .off
return false
}

View File

@ -76,7 +76,7 @@ typedef NS_ENUM(NSInteger, CSMachineKeyboardInputMode) {
- (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal;
// Input control.
@property (nonatomic, readonly) BOOL hasKeyboard;
@property (nonatomic, readonly) BOOL hasExclusiveKeyboard;
@property (nonatomic, readonly) BOOL hasJoystick;
@property (nonatomic, assign) CSMachineKeyboardInputMode inputMode;
@property (nonatomic, nullable) CSJoystickManager *joystickManager;

View File

@ -91,7 +91,9 @@ struct ActivityObserver: public Activity::Observer {
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(), error));
if(!_machine) return nil;
_inputMode = _machine->keyboard_machine() ? CSMachineKeyboardInputModeKeyboard : CSMachineKeyboardInputModeJoystick;
_inputMode =
(_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive())
? CSMachineKeyboardInputModeKeyboard : CSMachineKeyboardInputModeJoystick;
_leds = [[NSMutableArray alloc] init];
Activity::Source *const activity_source = _machine->activity_source();
@ -301,80 +303,85 @@ struct ActivityObserver: public Activity::Observer {
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
auto keyboard_machine = _machine->keyboard_machine();
if(self.inputMode == CSMachineKeyboardInputModeKeyboard && keyboard_machine) {
// Don't pass anything on if this is not new information.
if(_depressedKeys[key] == !!isPressed) return;
_depressedKeys[key] = !!isPressed;
if(keyboard_machine && (self.inputMode == CSMachineKeyboardInputModeKeyboard || !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
// to pass into the platform-neutral realm.
switch(key) {
BIND(VK_ANSI_0, k0); BIND(VK_ANSI_1, k1); BIND(VK_ANSI_2, k2); BIND(VK_ANSI_3, k3); BIND(VK_ANSI_4, k4);
BIND(VK_ANSI_5, k5); BIND(VK_ANSI_6, k6); BIND(VK_ANSI_7, k7); BIND(VK_ANSI_8, k8); BIND(VK_ANSI_9, k9);
// Pick an ASCII code, if any.
char pressedKey = '\0';
if(characters.length) {
unichar firstCharacter = [characters characterAtIndex:0];
if(firstCharacter < 128) {
pressedKey = (char)firstCharacter;
}
BIND(VK_ANSI_Q, Q); BIND(VK_ANSI_W, W); BIND(VK_ANSI_E, E); BIND(VK_ANSI_R, R); BIND(VK_ANSI_T, T);
BIND(VK_ANSI_Y, Y); BIND(VK_ANSI_U, U); BIND(VK_ANSI_I, I); BIND(VK_ANSI_O, O); BIND(VK_ANSI_P, P);
BIND(VK_ANSI_A, A); BIND(VK_ANSI_S, S); BIND(VK_ANSI_D, D); BIND(VK_ANSI_F, F); BIND(VK_ANSI_G, G);
BIND(VK_ANSI_H, H); BIND(VK_ANSI_J, J); BIND(VK_ANSI_K, K); BIND(VK_ANSI_L, L);
BIND(VK_ANSI_Z, Z); BIND(VK_ANSI_X, X); BIND(VK_ANSI_C, C); BIND(VK_ANSI_V, V);
BIND(VK_ANSI_B, B); BIND(VK_ANSI_N, N); BIND(VK_ANSI_M, M);
BIND(VK_F1, F1); BIND(VK_F2, F2); BIND(VK_F3, F3); BIND(VK_F4, F4);
BIND(VK_F5, F5); BIND(VK_F6, F6); BIND(VK_F7, F7); BIND(VK_F8, F8);
BIND(VK_F9, F9); BIND(VK_F10, F10); BIND(VK_F11, F11); BIND(VK_F12, F12);
BIND(VK_ANSI_Keypad0, KeyPad0); BIND(VK_ANSI_Keypad1, KeyPad1); BIND(VK_ANSI_Keypad2, KeyPad2);
BIND(VK_ANSI_Keypad3, KeyPad3); BIND(VK_ANSI_Keypad4, KeyPad4); BIND(VK_ANSI_Keypad5, KeyPad5);
BIND(VK_ANSI_Keypad6, KeyPad6); BIND(VK_ANSI_Keypad7, KeyPad7); BIND(VK_ANSI_Keypad8, KeyPad8);
BIND(VK_ANSI_Keypad9, KeyPad9);
BIND(VK_ANSI_Equal, Equals); BIND(VK_ANSI_Minus, Hyphen);
BIND(VK_ANSI_RightBracket, CloseSquareBracket); BIND(VK_ANSI_LeftBracket, OpenSquareBracket);
BIND(VK_ANSI_Quote, Quote); BIND(VK_ANSI_Grave, BackTick);
BIND(VK_ANSI_Semicolon, Semicolon);
BIND(VK_ANSI_Backslash, BackSlash); BIND(VK_ANSI_Slash, ForwardSlash);
BIND(VK_ANSI_Comma, Comma); BIND(VK_ANSI_Period, FullStop);
BIND(VK_ANSI_KeypadDecimal, KeyPadDecimalPoint); BIND(VK_ANSI_KeypadEquals, KeyPadEquals);
BIND(VK_ANSI_KeypadMultiply, KeyPadAsterisk); BIND(VK_ANSI_KeypadDivide, KeyPadSlash);
BIND(VK_ANSI_KeypadPlus, KeyPadPlus); BIND(VK_ANSI_KeypadMinus, KeyPadMinus);
BIND(VK_ANSI_KeypadClear, KeyPadDelete); BIND(VK_ANSI_KeypadEnter, KeyPadEnter);
BIND(VK_Return, Enter); BIND(VK_Tab, Tab);
BIND(VK_Space, Space); BIND(VK_Delete, BackSpace);
BIND(VK_Control, LeftControl); BIND(VK_Option, LeftOption);
BIND(VK_Command, LeftMeta); BIND(VK_Shift, LeftShift);
BIND(VK_RightControl, RightControl); BIND(VK_RightOption, RightOption);
BIND(VK_Escape, Escape); BIND(VK_CapsLock, CapsLock);
BIND(VK_Home, Home); BIND(VK_End, End);
BIND(VK_PageUp, PageUp); BIND(VK_PageDown, PageDown);
BIND(VK_RightShift, RightShift);
BIND(VK_Help, Help);
BIND(VK_ForwardDelete, Delete);
BIND(VK_LeftArrow, Left); BIND(VK_RightArrow, Right);
BIND(VK_DownArrow, Down); BIND(VK_UpArrow, Up);
}
@synchronized(self) {
Inputs::Keyboard &keyboard = keyboard_machine->get_keyboard();
// Connect the Carbon-era Mac keyboard scancodes to Clock Signal's 'universal' enumeration in order
// to pass into the platform-neutral realm.
#define BIND(source, dest) case source: keyboard.set_key_pressed(Inputs::Keyboard::Key::dest, pressedKey, isPressed); break
switch(key) {
BIND(VK_ANSI_0, k0); BIND(VK_ANSI_1, k1); BIND(VK_ANSI_2, k2); BIND(VK_ANSI_3, k3); BIND(VK_ANSI_4, k4);
BIND(VK_ANSI_5, k5); BIND(VK_ANSI_6, k6); BIND(VK_ANSI_7, k7); BIND(VK_ANSI_8, k8); BIND(VK_ANSI_9, k9);
BIND(VK_ANSI_Q, Q); BIND(VK_ANSI_W, W); BIND(VK_ANSI_E, E); BIND(VK_ANSI_R, R); BIND(VK_ANSI_T, T);
BIND(VK_ANSI_Y, Y); BIND(VK_ANSI_U, U); BIND(VK_ANSI_I, I); BIND(VK_ANSI_O, O); BIND(VK_ANSI_P, P);
BIND(VK_ANSI_A, A); BIND(VK_ANSI_S, S); BIND(VK_ANSI_D, D); BIND(VK_ANSI_F, F); BIND(VK_ANSI_G, G);
BIND(VK_ANSI_H, H); BIND(VK_ANSI_J, J); BIND(VK_ANSI_K, K); BIND(VK_ANSI_L, L);
BIND(VK_ANSI_Z, Z); BIND(VK_ANSI_X, X); BIND(VK_ANSI_C, C); BIND(VK_ANSI_V, V);
BIND(VK_ANSI_B, B); BIND(VK_ANSI_N, N); BIND(VK_ANSI_M, M);
BIND(VK_F1, F1); BIND(VK_F2, F2); BIND(VK_F3, F3); BIND(VK_F4, F4);
BIND(VK_F5, F5); BIND(VK_F6, F6); BIND(VK_F7, F7); BIND(VK_F8, F8);
BIND(VK_F9, F9); BIND(VK_F10, F10); BIND(VK_F11, F11); BIND(VK_F12, F12);
BIND(VK_ANSI_Keypad0, KeyPad0); BIND(VK_ANSI_Keypad1, KeyPad1); BIND(VK_ANSI_Keypad2, KeyPad2);
BIND(VK_ANSI_Keypad3, KeyPad3); BIND(VK_ANSI_Keypad4, KeyPad4); BIND(VK_ANSI_Keypad5, KeyPad5);
BIND(VK_ANSI_Keypad6, KeyPad6); BIND(VK_ANSI_Keypad7, KeyPad7); BIND(VK_ANSI_Keypad8, KeyPad8);
BIND(VK_ANSI_Keypad9, KeyPad9);
BIND(VK_ANSI_Equal, Equals); BIND(VK_ANSI_Minus, Hyphen);
BIND(VK_ANSI_RightBracket, CloseSquareBracket); BIND(VK_ANSI_LeftBracket, OpenSquareBracket);
BIND(VK_ANSI_Quote, Quote); BIND(VK_ANSI_Grave, BackTick);
BIND(VK_ANSI_Semicolon, Semicolon);
BIND(VK_ANSI_Backslash, BackSlash); BIND(VK_ANSI_Slash, ForwardSlash);
BIND(VK_ANSI_Comma, Comma); BIND(VK_ANSI_Period, FullStop);
BIND(VK_ANSI_KeypadDecimal, KeyPadDecimalPoint); BIND(VK_ANSI_KeypadEquals, KeyPadEquals);
BIND(VK_ANSI_KeypadMultiply, KeyPadAsterisk); BIND(VK_ANSI_KeypadDivide, KeyPadSlash);
BIND(VK_ANSI_KeypadPlus, KeyPadPlus); BIND(VK_ANSI_KeypadMinus, KeyPadMinus);
BIND(VK_ANSI_KeypadClear, KeyPadDelete); BIND(VK_ANSI_KeypadEnter, KeyPadEnter);
BIND(VK_Return, Enter); BIND(VK_Tab, Tab);
BIND(VK_Space, Space); BIND(VK_Delete, BackSpace);
BIND(VK_Control, LeftControl); BIND(VK_Option, LeftOption);
BIND(VK_Command, LeftMeta); BIND(VK_Shift, LeftShift);
BIND(VK_RightControl, RightControl); BIND(VK_RightOption, RightOption);
BIND(VK_Escape, Escape); BIND(VK_CapsLock, CapsLock);
BIND(VK_Home, Home); BIND(VK_End, End);
BIND(VK_PageUp, PageUp); BIND(VK_PageDown, PageDown);
BIND(VK_RightShift, RightShift);
BIND(VK_Help, Help);
BIND(VK_ForwardDelete, Delete);
BIND(VK_LeftArrow, Left); BIND(VK_RightArrow, Right);
BIND(VK_DownArrow, Down); BIND(VK_UpArrow, Up);
}
#undef BIND
Inputs::Keyboard &keyboard = keyboard_machine->get_keyboard();
if(keyboard.observed_keys().find(mapped_key) != keyboard.observed_keys().end()) {
// Don't pass anything on if this is not new information.
if(_depressedKeys[key] == !!isPressed) return;
_depressedKeys[key] = !!isPressed;
// Pick an ASCII code, if any.
char pressedKey = '\0';
if(characters.length) {
unichar firstCharacter = [characters characterAtIndex:0];
if(firstCharacter < 128) {
pressedKey = (char)firstCharacter;
}
}
@synchronized(self) {
keyboard.set_key_pressed(mapped_key, pressedKey, isPressed);
}
return;
}
return;
}
auto joystick_machine = _machine->joystick_machine();
@ -536,8 +543,8 @@ struct ActivityObserver: public Activity::Observer {
return !!_machine->joystick_machine();
}
- (BOOL)hasKeyboard {
return !!_machine->keyboard_machine();
- (BOOL)hasExclusiveKeyboard {
return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive();
}
#pragma mark - Activity observation

View File

@ -116,7 +116,7 @@
NSAssert(vdp.get_interrupt_line(), @"Interrupt line wasn't set when promised");
}
- (void)testPrediction {
- (void)testInterruptPrediction {
TI::TMS::TMS9918 vdp(TI::TMS::Personality::SMSVDP);
for(int c = 0; c < 256; ++c) {
@ -134,7 +134,7 @@
vdp.set_register(1, 0x8a);
// Now run through an entire frame...
int half_cycles = 262*224*2;
int half_cycles = 262*228*2;
int last_time_until_interrupt = vdp.get_time_until_interrupt().as_int();
while(half_cycles--) {
// Validate that an interrupt happened if one was expected, and clear anything that's present.
@ -157,4 +157,20 @@
}
}
- (void)testTimeUntilLine {
TI::TMS::TMS9918 vdp(TI::TMS::Personality::SMSVDP);
int time_until_line = vdp.get_time_until_line(-1).as_int();
for(int c = 0; c < 262*228*5; ++c) {
vdp.run_for(HalfCycles(1));
const int time_remaining_until_line = vdp.get_time_until_line(-1).as_int();
--time_until_line;
if(time_until_line) {
NSAssert(time_remaining_until_line == time_until_line, @"Discontinuity found in distance-to-line prediction; expected %d but got %d", time_until_line, time_remaining_until_line);
}
time_until_line = time_remaining_until_line;
}
}
@end

View File

@ -610,7 +610,7 @@ int main(int argc, char *argv[]) {
case SDL_KEYDOWN:
// Syphon off the key-press if it's control+shift+V (paste).
if(event.key.keysym.sym == SDLK_v && (SDL_GetModState()&KMOD_CTRL) && (SDL_GetModState()&KMOD_SHIFT)) {
KeyboardMachine::Machine *keyboard_machine = machine->keyboard_machine();
const auto keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) {
keyboard_machine->type_string(SDL_GetClipboardText());
break;
@ -686,7 +686,7 @@ int main(int argc, char *argv[]) {
SDL_ShowCursor((fullscreen_mode&SDL_WINDOW_FULLSCREEN_DESKTOP) ? SDL_DISABLE : SDL_ENABLE);
// Announce a potential discontinuity in keyboard input.
auto keyboard_machine = machine->keyboard_machine();
const auto keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) {
keyboard_machine->get_keyboard().reset_all_keys();
}
@ -695,17 +695,19 @@ int main(int argc, char *argv[]) {
const bool is_pressed = event.type == SDL_KEYDOWN;
KeyboardMachine::Machine *const keyboard_machine = machine->keyboard_machine();
const auto keyboard_machine = machine->keyboard_machine();
if(keyboard_machine) {
Inputs::Keyboard::Key key = Inputs::Keyboard::Key::Space;
if(!KeyboardKeyForSDLScancode(event.key.keysym.scancode, key)) break;
char key_value = '\0';
const char *key_name = SDL_GetKeyName(event.key.keysym.sym);
if(key_name[0] >= 0) key_value = key_name[0];
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;
keyboard_machine->get_keyboard().set_key_pressed(key, key_value, is_pressed);
break;
}
}
JoystickMachine::Machine *const joystick_machine = machine->joystick_machine();