1
0
mirror of https://github.com/TomHarte/CLK.git synced 2024-06-01 22:41:32 +00:00

Introduces 'non-exclusive' emulator-space keyboards.

i.e. sets of keys that don't amount to an entire keyboard in the modern sense. Experimentally used by the Master System for its reset key.
This commit is contained in:
Thomas Harte 2018-10-24 21:59:30 -04:00
parent 278585fd94
commit a8645f80bf
34 changed files with 280 additions and 143 deletions

View File

@ -35,9 +35,13 @@ void MultiKeyboardMachine::type_string(const std::string &string) {
}
}
void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
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);
}
Inputs::Keyboard &MultiKeyboardMachine::get_keyboard() {
return keyboard_;
}
//void MultiKeyboardMachine::keyboard_did_change_key(Inputs::Keyboard *keyboard, Inputs::Keyboard::Key key, bool is_pressed) {
// 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);
// }
//}

View File

@ -32,10 +32,11 @@ 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;
Inputs::Keyboard &get_keyboard() override;
private:
std::vector<::KeyboardMachine::Machine *> machines_;
Inputs::Keyboard keyboard_;
};
}

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