2016-01-05 04:12:47 +00:00
|
|
|
//
|
|
|
|
// CSMachine.m
|
|
|
|
// Clock Signal
|
|
|
|
//
|
|
|
|
// Created by Thomas Harte on 04/01/2016.
|
2018-05-13 19:19:52 +00:00
|
|
|
// Copyright 2016 Thomas Harte. All rights reserved.
|
2016-01-05 04:12:47 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#import "CSMachine.h"
|
2016-09-01 00:43:29 +00:00
|
|
|
#import "CSMachine+Target.h"
|
2016-09-08 09:32:17 +00:00
|
|
|
|
2018-01-01 21:04:13 +00:00
|
|
|
#include "CSROMFetcher.hpp"
|
|
|
|
|
2018-07-11 01:32:28 +00:00
|
|
|
#include "MediaTarget.hpp"
|
2017-10-16 01:25:56 +00:00
|
|
|
#include "JoystickMachine.hpp"
|
|
|
|
#include "KeyboardMachine.hpp"
|
2017-11-25 02:36:22 +00:00
|
|
|
#include "KeyCodes.h"
|
|
|
|
#include "MachineForTarget.hpp"
|
|
|
|
#include "StandardOptions.hpp"
|
|
|
|
#include "Typer.hpp"
|
2018-06-17 22:53:56 +00:00
|
|
|
#include "../../../../Activity/Observer.hpp"
|
2016-01-05 04:12:47 +00:00
|
|
|
|
2018-01-25 01:14:15 +00:00
|
|
|
#import "CSStaticAnalyser+TargetVector.h"
|
2017-11-08 03:29:57 +00:00
|
|
|
#import "NSBundle+DataResource.h"
|
|
|
|
#import "NSData+StdVector.h"
|
|
|
|
|
2018-04-16 01:11:30 +00:00
|
|
|
#include <bitset>
|
|
|
|
|
2018-11-04 01:54:25 +00:00
|
|
|
#import <OpenGL/OpenGL.h>
|
|
|
|
#include <OpenGL/gl3.h>
|
|
|
|
|
2018-11-07 03:23:38 +00:00
|
|
|
#include "../../../../Outputs/OpenGL/ScanTarget.hpp"
|
2019-02-28 02:05:02 +00:00
|
|
|
#include "../../../../Outputs/OpenGL/Screenshot.hpp"
|
2018-11-04 03:40:39 +00:00
|
|
|
|
2019-09-25 00:13:09 +00:00
|
|
|
@interface CSMachine()
|
2017-12-18 02:26:06 +00:00
|
|
|
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length;
|
2018-03-22 13:23:01 +00:00
|
|
|
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker;
|
2018-06-17 22:53:56 +00:00
|
|
|
- (void)addLED:(NSString *)led;
|
2016-06-01 02:16:20 +00:00
|
|
|
@end
|
|
|
|
|
2017-09-01 01:22:23 +00:00
|
|
|
struct LockProtectedDelegate {
|
2018-05-13 19:34:31 +00:00
|
|
|
// Contractual promise is: machine, the pointer **and** the object **, may be accessed only
|
2017-09-01 01:22:23 +00:00
|
|
|
// in sections protected by the machineAccessLock;
|
|
|
|
NSLock *machineAccessLock;
|
|
|
|
__unsafe_unretained CSMachine *machine;
|
|
|
|
};
|
|
|
|
|
2017-12-18 02:26:06 +00:00
|
|
|
struct SpeakerDelegate: public Outputs::Speaker::Speaker::Delegate, public LockProtectedDelegate {
|
2018-03-22 13:23:01 +00:00
|
|
|
void speaker_did_complete_samples(Outputs::Speaker::Speaker *speaker, const std::vector<int16_t> &buffer) override {
|
2017-09-01 01:22:23 +00:00
|
|
|
[machineAccessLock lock];
|
2017-07-16 19:01:39 +00:00
|
|
|
[machine speaker:speaker didCompleteSamples:buffer.data() length:(int)buffer.size()];
|
2017-09-01 01:22:23 +00:00
|
|
|
[machineAccessLock unlock];
|
2016-01-14 03:38:59 +00:00
|
|
|
}
|
2018-03-22 13:23:01 +00:00
|
|
|
void speaker_did_change_input_clock(Outputs::Speaker::Speaker *speaker) override {
|
|
|
|
[machineAccessLock lock];
|
|
|
|
[machine speakerDidChangeInputClock:speaker];
|
|
|
|
[machineAccessLock unlock];
|
|
|
|
}
|
2016-01-14 03:38:59 +00:00
|
|
|
};
|
|
|
|
|
2018-06-17 22:53:56 +00:00
|
|
|
struct ActivityObserver: public Activity::Observer {
|
|
|
|
void register_led(const std::string &name) override {
|
|
|
|
[machine addLED:[NSString stringWithUTF8String:name.c_str()]];
|
|
|
|
}
|
|
|
|
|
|
|
|
void set_led_status(const std::string &name, bool lit) override {
|
2018-06-19 01:22:51 +00:00
|
|
|
[machine.delegate machine:machine led:[NSString stringWithUTF8String:name.c_str()] didChangeToLit:lit];
|
2018-06-17 22:53:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void announce_drive_event(const std::string &name, DriveEvent event) override {
|
2018-06-19 01:22:51 +00:00
|
|
|
[machine.delegate machine:machine ledShouldBlink:[NSString stringWithUTF8String:name.c_str()]];
|
2018-06-17 22:53:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
__unsafe_unretained CSMachine *machine;
|
|
|
|
};
|
|
|
|
|
2019-07-22 02:05:22 +00:00
|
|
|
@interface CSMissingROM (Mutability)
|
2019-07-23 01:18:30 +00:00
|
|
|
@property (nonatomic, nonnull, copy) NSString *machineName;
|
2019-07-22 02:05:22 +00:00
|
|
|
@property (nonatomic, nonnull, copy) NSString *fileName;
|
|
|
|
@property (nonatomic, nullable, copy) NSString *descriptiveName;
|
|
|
|
@property (nonatomic, readwrite) NSUInteger size;
|
|
|
|
@property (nonatomic, copy) NSArray<NSNumber *> *crc32s;
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation CSMissingROM {
|
2019-07-23 01:18:30 +00:00
|
|
|
NSString *_machineName;
|
2019-07-22 02:05:22 +00:00
|
|
|
NSString *_fileName;
|
|
|
|
NSString *_descriptiveName;
|
|
|
|
NSUInteger _size;
|
|
|
|
NSArray<NSNumber *> *_crc32s;
|
|
|
|
}
|
|
|
|
|
2019-07-23 01:18:30 +00:00
|
|
|
- (NSString *)machineName {
|
|
|
|
return _machineName;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setMachineName:(NSString *)machineName {
|
|
|
|
_machineName = [machineName copy];
|
|
|
|
}
|
|
|
|
|
2019-07-22 02:05:22 +00:00
|
|
|
- (NSString *)fileName {
|
|
|
|
return _fileName;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setFileName:(NSString *)fileName {
|
|
|
|
_fileName = [fileName copy];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)descriptiveName {
|
|
|
|
return _descriptiveName;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setDescriptiveName:(NSString *)descriptiveName {
|
|
|
|
_descriptiveName = [descriptiveName copy];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSUInteger)size {
|
|
|
|
return _size;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setSize:(NSUInteger)size {
|
|
|
|
_size = size;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray<NSNumber *> *)crc32s {
|
|
|
|
return _crc32s;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setCrc32s:(NSArray<NSNumber *> *)crc32s {
|
|
|
|
_crc32s = [crc32s copy];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSString *)description {
|
2019-08-10 06:43:30 +00:00
|
|
|
return [NSString stringWithFormat:@"%@/%@, %lu bytes, CRCs: %@", _fileName, _descriptiveName, (unsigned long)_size, _crc32s];
|
2019-07-22 02:05:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2016-01-05 04:12:47 +00:00
|
|
|
@implementation CSMachine {
|
2016-01-14 03:38:59 +00:00
|
|
|
SpeakerDelegate _speakerDelegate;
|
2018-06-17 22:53:56 +00:00
|
|
|
ActivityObserver _activityObserver;
|
2017-09-01 01:22:23 +00:00
|
|
|
NSLock *_delegateMachineAccessLock;
|
2018-01-25 01:14:15 +00:00
|
|
|
|
2018-01-25 03:35:54 +00:00
|
|
|
CSStaticAnalyser *_analyser;
|
2018-01-25 01:14:15 +00:00
|
|
|
std::unique_ptr<Machine::DynamicMachine> _machine;
|
2018-07-22 20:55:47 +00:00
|
|
|
JoystickMachine::Machine *_joystickMachine;
|
2018-04-16 01:11:30 +00:00
|
|
|
|
2018-07-20 02:43:01 +00:00
|
|
|
CSJoystickManager *_joystickManager;
|
2018-04-16 01:11:30 +00:00
|
|
|
std::bitset<65536> _depressedKeys;
|
2018-06-17 22:53:56 +00:00
|
|
|
NSMutableArray<NSString *> *_leds;
|
2018-11-04 03:40:39 +00:00
|
|
|
|
2018-11-08 03:53:46 +00:00
|
|
|
std::unique_ptr<Outputs::Display::OpenGL::ScanTarget> _scanTarget;
|
2016-06-21 01:47:27 +00:00
|
|
|
}
|
|
|
|
|
2019-07-22 02:05:22 +00:00
|
|
|
- (instancetype)initWithAnalyser:(CSStaticAnalyser *)result missingROMs:(inout NSMutableArray<CSMissingROM *> *)missingROMs {
|
2016-06-21 01:47:27 +00:00
|
|
|
self = [super init];
|
2017-08-01 02:32:26 +00:00
|
|
|
if(self) {
|
2018-01-25 03:35:54 +00:00
|
|
|
_analyser = result;
|
2018-01-25 23:28:19 +00:00
|
|
|
|
|
|
|
Machine::Error error;
|
2019-07-21 22:41:00 +00:00
|
|
|
std::vector<ROMMachine::ROM> missing_roms;
|
|
|
|
_machine.reset(Machine::MachineForTargets(_analyser.targets, CSROMFetcher(&missing_roms), error));
|
2019-07-22 02:05:22 +00:00
|
|
|
if(!_machine) {
|
|
|
|
for(const auto &missing_rom : missing_roms) {
|
|
|
|
CSMissingROM *rom = [[CSMissingROM alloc] init];
|
|
|
|
|
|
|
|
// Copy/convert the primitive fields.
|
2019-07-23 01:18:30 +00:00
|
|
|
rom.machineName = [NSString stringWithUTF8String:missing_rom.machine_name.c_str()];
|
2019-07-22 02:05:22 +00:00
|
|
|
rom.fileName = [NSString stringWithUTF8String:missing_rom.file_name.c_str()];
|
2019-07-23 01:18:30 +00:00
|
|
|
rom.descriptiveName = missing_rom.descriptive_name.empty() ? nil : [NSString stringWithUTF8String:missing_rom.descriptive_name.c_str()];
|
2019-07-22 02:05:22 +00:00
|
|
|
rom.size = missing_rom.size;
|
|
|
|
|
|
|
|
// Convert the CRC list.
|
|
|
|
NSMutableArray<NSNumber *> *crc32s = [[NSMutableArray alloc] init];
|
|
|
|
for(const auto &crc : missing_rom.crc32s) {
|
|
|
|
[crc32s addObject:@(crc)];
|
|
|
|
}
|
|
|
|
rom.crc32s = [crc32s copy];
|
|
|
|
|
|
|
|
// Add to the missing list.
|
|
|
|
[missingROMs addObject:rom];
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
2018-01-25 23:28:19 +00:00
|
|
|
|
2018-10-25 01:59:30 +00:00
|
|
|
_inputMode =
|
|
|
|
(_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive())
|
|
|
|
? CSMachineKeyboardInputModeKeyboard : CSMachineKeyboardInputModeJoystick;
|
2018-06-13 23:22:34 +00:00
|
|
|
|
2018-06-17 22:53:56 +00:00
|
|
|
_leds = [[NSMutableArray alloc] init];
|
|
|
|
Activity::Source *const activity_source = _machine->activity_source();
|
|
|
|
if(activity_source) {
|
|
|
|
_activityObserver.machine = self;
|
|
|
|
activity_source->set_activity_observer(&_activityObserver);
|
|
|
|
}
|
|
|
|
|
2017-09-01 01:22:23 +00:00
|
|
|
_delegateMachineAccessLock = [[NSLock alloc] init];
|
|
|
|
|
2016-06-21 01:47:27 +00:00
|
|
|
_speakerDelegate.machine = self;
|
2017-09-01 01:22:23 +00:00
|
|
|
_speakerDelegate.machineAccessLock = _delegateMachineAccessLock;
|
2018-07-22 20:55:47 +00:00
|
|
|
|
|
|
|
_joystickMachine = _machine->joystick_machine();
|
2016-06-21 01:47:27 +00:00
|
|
|
}
|
|
|
|
return self;
|
2016-01-05 04:12:47 +00:00
|
|
|
}
|
|
|
|
|
2017-12-18 02:26:06 +00:00
|
|
|
- (void)speaker:(Outputs::Speaker::Speaker *)speaker didCompleteSamples:(const int16_t *)samples length:(int)length {
|
2016-01-15 01:33:22 +00:00
|
|
|
[self.audioQueue enqueueAudioBuffer:samples numberOfSamples:(unsigned int)length];
|
2016-01-14 03:38:59 +00:00
|
|
|
}
|
|
|
|
|
2018-03-22 13:23:01 +00:00
|
|
|
- (void)speakerDidChangeInputClock:(Outputs::Speaker::Speaker *)speaker {
|
2018-03-22 13:48:19 +00:00
|
|
|
[self.delegate machineSpeakerDidChangeInputClock:self];
|
2018-03-22 13:23:01 +00:00
|
|
|
}
|
|
|
|
|
2016-04-25 00:34:25 +00:00
|
|
|
- (void)dealloc {
|
2017-09-01 01:22:23 +00:00
|
|
|
// The two delegate's references to this machine are nilled out here because close_output may result
|
|
|
|
// in a data flush, which might cause an audio callback, which could cause the audio queue to decide
|
|
|
|
// that it's out of data, resulting in an attempt further to run the machine while it is dealloc'ing.
|
|
|
|
//
|
|
|
|
// They are nilled inside an explicit lock because that allows the delegates to protect their entire
|
|
|
|
// call into the machine, not just the pointer access.
|
|
|
|
[_delegateMachineAccessLock lock];
|
|
|
|
_speakerDelegate.machine = nil;
|
|
|
|
[_delegateMachineAccessLock unlock];
|
|
|
|
|
2019-01-18 01:44:18 +00:00
|
|
|
[_view performWithGLContext:^{
|
|
|
|
@synchronized(self) {
|
|
|
|
self->_scanTarget.reset();
|
|
|
|
}
|
|
|
|
}];
|
2016-04-25 00:34:25 +00:00
|
|
|
}
|
|
|
|
|
2016-06-13 23:30:41 +00:00
|
|
|
- (float)idealSamplingRateFromRange:(NSRange)range {
|
2016-06-01 23:04:07 +00:00
|
|
|
@synchronized(self) {
|
2017-12-18 02:26:06 +00:00
|
|
|
Outputs::Speaker::Speaker *speaker = _machine->crt_machine()->get_speaker();
|
2017-09-01 01:22:23 +00:00
|
|
|
if(speaker) {
|
2016-06-13 23:30:41 +00:00
|
|
|
return speaker->get_ideal_clock_rate_in_range((float)range.location, (float)(range.location + range.length));
|
2016-06-01 23:04:07 +00:00
|
|
|
}
|
2016-06-05 15:20:05 +00:00
|
|
|
return 0;
|
2016-06-01 23:04:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-17 00:51:35 +00:00
|
|
|
- (void)setAudioSamplingRate:(float)samplingRate bufferSize:(NSUInteger)bufferSize {
|
2016-06-01 23:04:07 +00:00
|
|
|
@synchronized(self) {
|
2016-06-17 00:51:35 +00:00
|
|
|
[self setSpeakerDelegate:&_speakerDelegate sampleRate:samplingRate bufferSize:bufferSize];
|
2016-06-01 23:04:07 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-18 02:26:06 +00:00
|
|
|
- (BOOL)setSpeakerDelegate:(Outputs::Speaker::Speaker::Delegate *)delegate sampleRate:(float)sampleRate bufferSize:(NSUInteger)bufferSize {
|
2016-06-01 02:16:20 +00:00
|
|
|
@synchronized(self) {
|
2017-12-18 02:26:06 +00:00
|
|
|
Outputs::Speaker::Speaker *speaker = _machine->crt_machine()->get_speaker();
|
2017-09-01 01:22:23 +00:00
|
|
|
if(speaker) {
|
2016-06-17 00:51:35 +00:00
|
|
|
speaker->set_output_rate(sampleRate, (int)bufferSize);
|
2016-06-01 02:16:20 +00:00
|
|
|
speaker->set_delegate(delegate);
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
return NO;
|
|
|
|
}
|
2016-01-14 03:38:59 +00:00
|
|
|
}
|
2016-03-20 02:46:17 +00:00
|
|
|
|
2018-03-22 02:18:13 +00:00
|
|
|
- (void)runForInterval:(NSTimeInterval)interval {
|
2016-06-01 02:16:20 +00:00
|
|
|
@synchronized(self) {
|
2018-07-22 20:55:47 +00:00
|
|
|
if(_joystickMachine && _joystickManager) {
|
|
|
|
[_joystickManager update];
|
|
|
|
|
|
|
|
// TODO: configurable mapping from physical joypad inputs to machine inputs.
|
|
|
|
// Until then, apply a default mapping.
|
|
|
|
|
|
|
|
size_t c = 0;
|
2019-11-09 23:19:05 +00:00
|
|
|
auto &machine_joysticks = _joystickMachine->get_joysticks();
|
2018-07-22 20:55:47 +00:00
|
|
|
for(CSJoystick *joystick in _joystickManager.joysticks) {
|
|
|
|
size_t target = c % machine_joysticks.size();
|
2019-01-18 01:44:18 +00:00
|
|
|
++c;
|
2018-07-22 20:55:47 +00:00
|
|
|
|
|
|
|
// Post the first two analogue axes presented by the controller as horizontal and vertical inputs,
|
|
|
|
// unless the user seems to be using a hat.
|
|
|
|
// SDL will return a value in the range [-32768, 32767], so map from that to [0, 1.0]
|
|
|
|
if(!joystick.hats.count || !joystick.hats[0].direction) {
|
|
|
|
if(joystick.axes.count > 0) {
|
|
|
|
const float x_axis = joystick.axes[0].position;
|
|
|
|
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Horizontal), x_axis);
|
|
|
|
}
|
|
|
|
if(joystick.axes.count > 1) {
|
|
|
|
const float y_axis = joystick.axes[1].position;
|
|
|
|
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Vertical), y_axis);
|
|
|
|
}
|
2018-07-22 21:29:37 +00:00
|
|
|
} else {
|
|
|
|
// Forward hats as directions; hats always override analogue inputs.
|
|
|
|
for(CSJoystickHat *hat in joystick.hats) {
|
|
|
|
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Up), !!(hat.direction & CSJoystickHatDirectionUp));
|
|
|
|
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Down), !!(hat.direction & CSJoystickHatDirectionDown));
|
|
|
|
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Left), !!(hat.direction & CSJoystickHatDirectionLeft));
|
|
|
|
machine_joysticks[target]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Right), !!(hat.direction & CSJoystickHatDirectionRight));
|
|
|
|
}
|
2018-07-22 20:55:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Forward all fire buttons, mapping as a function of index.
|
|
|
|
if(machine_joysticks[target]->get_number_of_fire_buttons()) {
|
|
|
|
std::vector<bool> button_states((size_t)machine_joysticks[target]->get_number_of_fire_buttons());
|
|
|
|
for(CSJoystickButton *button in joystick.buttons) {
|
|
|
|
if(button.isPressed) button_states[(size_t)(((int)button.index - 1) % machine_joysticks[target]->get_number_of_fire_buttons())] = true;
|
|
|
|
}
|
|
|
|
for(size_t index = 0; index < button_states.size(); ++index) {
|
|
|
|
machine_joysticks[target]->set_input(
|
|
|
|
Inputs::Joystick::Input(Inputs::Joystick::Input::Type::Fire, index),
|
|
|
|
button_states[index]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-22 02:18:13 +00:00
|
|
|
_machine->crt_machine()->run_for(interval);
|
2016-06-01 02:16:20 +00:00
|
|
|
}
|
|
|
|
}
|
2016-03-20 02:46:17 +00:00
|
|
|
|
2016-04-12 03:12:56 +00:00
|
|
|
- (void)setView:(CSOpenGLView *)view aspectRatio:(float)aspectRatio {
|
2016-04-25 00:34:25 +00:00
|
|
|
_view = view;
|
2016-03-20 17:50:13 +00:00
|
|
|
[view performWithGLContext:^{
|
2016-04-12 03:12:56 +00:00
|
|
|
[self setupOutputWithAspectRatio:aspectRatio];
|
2016-03-20 17:50:13 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2016-06-01 02:16:20 +00:00
|
|
|
- (void)setupOutputWithAspectRatio:(float)aspectRatio {
|
2018-11-08 03:53:46 +00:00
|
|
|
_scanTarget.reset(new Outputs::Display::OpenGL::ScanTarget);
|
2018-11-15 02:52:57 +00:00
|
|
|
_machine->crt_machine()->set_scan_target(_scanTarget.get());
|
2016-06-01 02:16:20 +00:00
|
|
|
}
|
|
|
|
|
2019-03-03 00:33:28 +00:00
|
|
|
- (void)updateViewForPixelSize:(CGSize)pixelSize {
|
|
|
|
_scanTarget->update((int)pixelSize.width, (int)pixelSize.height);
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)drawViewForPixelSize:(CGSize)pixelSize {
|
|
|
|
_scanTarget->draw((int)pixelSize.width, (int)pixelSize.height);
|
2016-06-01 02:16:20 +00:00
|
|
|
}
|
|
|
|
|
2016-06-19 20:35:04 +00:00
|
|
|
- (void)paste:(NSString *)paste {
|
2018-02-09 21:31:05 +00:00
|
|
|
KeyboardMachine::Machine *keyboardMachine = _machine->keyboard_machine();
|
2017-12-29 23:30:46 +00:00
|
|
|
if(keyboardMachine)
|
|
|
|
keyboardMachine->type_string([paste UTF8String]);
|
2016-06-19 20:35:04 +00:00
|
|
|
}
|
|
|
|
|
2018-07-28 03:37:24 +00:00
|
|
|
- (NSBitmapImageRep *)imageRepresentation {
|
2019-02-28 02:05:02 +00:00
|
|
|
// Grab a screenshot.
|
|
|
|
Outputs::Display::OpenGL::Screenshot screenshot(4, 3);
|
|
|
|
|
|
|
|
// Generate an NSBitmapImageRep containing the screenshot's data.
|
2018-07-28 03:37:24 +00:00
|
|
|
NSBitmapImageRep *const result =
|
|
|
|
[[NSBitmapImageRep alloc]
|
|
|
|
initWithBitmapDataPlanes:NULL
|
2019-02-28 02:05:02 +00:00
|
|
|
pixelsWide:screenshot.width
|
|
|
|
pixelsHigh:screenshot.height
|
2018-07-28 03:37:24 +00:00
|
|
|
bitsPerSample:8
|
2019-02-28 02:05:02 +00:00
|
|
|
samplesPerPixel:4
|
|
|
|
hasAlpha:YES
|
2018-07-28 03:37:24 +00:00
|
|
|
isPlanar:NO
|
|
|
|
colorSpaceName:NSDeviceRGBColorSpace
|
2019-02-28 02:05:02 +00:00
|
|
|
bytesPerRow:4 * screenshot.width
|
2018-07-28 03:37:24 +00:00
|
|
|
bitsPerPixel:0];
|
|
|
|
|
2019-02-28 02:05:02 +00:00
|
|
|
memcpy(result.bitmapData, screenshot.pixel_data.data(), size_t(screenshot.width*screenshot.height*4));
|
2018-07-28 03:37:24 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2018-01-25 02:48:44 +00:00
|
|
|
- (void)applyMedia:(const Analyser::Static::Media &)media {
|
2017-08-17 15:00:08 +00:00
|
|
|
@synchronized(self) {
|
2018-07-11 01:32:28 +00:00
|
|
|
MediaTarget::Machine *const mediaTarget = _machine->media_target();
|
|
|
|
if(mediaTarget) mediaTarget->insert_media(media);
|
2017-08-17 15:00:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-22 20:55:47 +00:00
|
|
|
- (void)setJoystickManager:(CSJoystickManager *)joystickManager {
|
|
|
|
@synchronized(self) {
|
|
|
|
_joystickManager = joystickManager;
|
|
|
|
if(_joystickMachine) {
|
2019-11-09 23:19:05 +00:00
|
|
|
auto &machine_joysticks = _joystickMachine->get_joysticks();
|
2018-07-22 20:55:47 +00:00
|
|
|
for(const auto &joystick: machine_joysticks) {
|
|
|
|
joystick->reset_all_inputs();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-26 03:47:47 +00:00
|
|
|
- (void)setKey:(uint16_t)key characters:(NSString *)characters isPressed:(BOOL)isPressed {
|
2017-11-25 02:36:22 +00:00
|
|
|
auto keyboard_machine = _machine->keyboard_machine();
|
2018-10-25 01:59:30 +00:00
|
|
|
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);
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2019-11-09 23:02:14 +00:00
|
|
|
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);
|
2018-10-25 01:59:30 +00:00
|
|
|
|
|
|
|
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);
|
2019-08-02 20:15:34 +00:00
|
|
|
BIND(VK_ANSI_Backslash, Backslash); BIND(VK_ANSI_Slash, ForwardSlash);
|
2018-10-25 01:59:30 +00:00
|
|
|
BIND(VK_ANSI_Comma, Comma); BIND(VK_ANSI_Period, FullStop);
|
|
|
|
|
2019-11-09 23:02:14 +00:00
|
|
|
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);
|
2018-10-25 01:59:30 +00:00
|
|
|
|
|
|
|
BIND(VK_Return, Enter); BIND(VK_Tab, Tab);
|
2019-08-02 20:15:34 +00:00
|
|
|
BIND(VK_Space, Space); BIND(VK_Delete, Backspace);
|
2018-10-25 01:59:30 +00:00
|
|
|
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);
|
2018-11-24 03:32:32 +00:00
|
|
|
BIND(VK_DownArrow, Down); BIND(VK_UpArrow, Up);
|
2018-04-16 01:11:30 +00:00
|
|
|
}
|
2018-10-25 01:59:30 +00:00
|
|
|
#undef BIND
|
2018-04-16 01:11:30 +00:00
|
|
|
|
2018-10-25 01:59:30 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-10-16 01:25:56 +00:00
|
|
|
}
|
2018-10-25 01:59:30 +00:00
|
|
|
|
|
|
|
@synchronized(self) {
|
|
|
|
keyboard.set_key_pressed(mapped_key, pressedKey, isPressed);
|
|
|
|
}
|
|
|
|
return;
|
2017-10-16 01:25:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-25 02:36:22 +00:00
|
|
|
auto joystick_machine = _machine->joystick_machine();
|
2018-06-13 23:22:34 +00:00
|
|
|
if(self.inputMode == CSMachineKeyboardInputModeJoystick && joystick_machine) {
|
2017-10-16 01:25:56 +00:00
|
|
|
@synchronized(self) {
|
2019-11-09 23:19:05 +00:00
|
|
|
auto &joysticks = joystick_machine->get_joysticks();
|
2017-10-16 01:25:56 +00:00
|
|
|
if(!joysticks.empty()) {
|
2018-06-11 00:45:52 +00:00
|
|
|
// Convert to a C++ bool so that the following calls are resolved correctly even if overloaded.
|
|
|
|
bool is_pressed = !!isPressed;
|
2017-10-16 01:25:56 +00:00
|
|
|
switch(key) {
|
2018-06-11 00:45:52 +00:00
|
|
|
case VK_LeftArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Left, is_pressed); break;
|
|
|
|
case VK_RightArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Right, is_pressed); break;
|
|
|
|
case VK_UpArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Up, is_pressed); break;
|
|
|
|
case VK_DownArrow: joysticks[0]->set_input(Inputs::Joystick::Input::Down, is_pressed); break;
|
|
|
|
case VK_Space: joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed); break;
|
|
|
|
case VK_ANSI_A: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 0), is_pressed); break;
|
|
|
|
case VK_ANSI_S: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 1), is_pressed); break;
|
2018-06-13 23:22:34 +00:00
|
|
|
case VK_ANSI_D: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 2), is_pressed); break;
|
|
|
|
case VK_ANSI_F: joysticks[0]->set_input(Inputs::Joystick::Input(Inputs::Joystick::Input::Fire, 3), is_pressed); break;
|
2017-10-16 01:25:56 +00:00
|
|
|
default:
|
2018-08-05 01:40:26 +00:00
|
|
|
if(characters.length) {
|
2018-06-11 00:45:52 +00:00
|
|
|
joysticks[0]->set_input(Inputs::Joystick::Input([characters characterAtIndex:0]), is_pressed);
|
2018-02-26 03:47:47 +00:00
|
|
|
} else {
|
2018-06-11 00:45:52 +00:00
|
|
|
joysticks[0]->set_input(Inputs::Joystick::Input::Fire, is_pressed);
|
2018-02-26 03:47:47 +00:00
|
|
|
}
|
2017-10-16 01:25:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-13 02:25:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)clearAllKeys {
|
2019-06-11 22:41:41 +00:00
|
|
|
const auto keyboard_machine = _machine->keyboard_machine();
|
2017-10-16 01:25:56 +00:00
|
|
|
if(keyboard_machine) {
|
|
|
|
@synchronized(self) {
|
|
|
|
keyboard_machine->get_keyboard().reset_all_keys();
|
|
|
|
}
|
|
|
|
}
|
2017-10-13 02:25:02 +00:00
|
|
|
|
2019-06-11 22:41:41 +00:00
|
|
|
const auto joystick_machine = _machine->joystick_machine();
|
2017-10-16 01:25:56 +00:00
|
|
|
if(joystick_machine) {
|
|
|
|
@synchronized(self) {
|
|
|
|
for(auto &joystick : joystick_machine->get_joysticks()) {
|
|
|
|
joystick->reset_all_inputs();
|
|
|
|
}
|
|
|
|
}
|
2017-10-13 02:25:02 +00:00
|
|
|
}
|
2019-06-11 22:41:41 +00:00
|
|
|
|
|
|
|
const auto mouse_machine = _machine->mouse_machine();
|
|
|
|
if(mouse_machine) {
|
|
|
|
@synchronized(self) {
|
|
|
|
mouse_machine->get_mouse().reset_all_buttons();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setMouseButton:(int)button isPressed:(BOOL)isPressed {
|
|
|
|
auto mouse_machine = _machine->mouse_machine();
|
|
|
|
if(mouse_machine) {
|
|
|
|
@synchronized(self) {
|
|
|
|
mouse_machine->get_mouse().set_button_pressed(button % mouse_machine->get_mouse().get_number_of_buttons(), isPressed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)addMouseMotionX:(CGFloat)deltaX y:(CGFloat)deltaY {
|
|
|
|
auto mouse_machine = _machine->mouse_machine();
|
|
|
|
if(mouse_machine) {
|
|
|
|
@synchronized(self) {
|
2019-07-08 21:36:55 +00:00
|
|
|
mouse_machine->get_mouse().move(int(deltaX), int(deltaY));
|
2019-06-11 22:41:41 +00:00
|
|
|
}
|
|
|
|
}
|
2017-10-13 02:25:02 +00:00
|
|
|
}
|
|
|
|
|
2017-11-25 02:36:22 +00:00
|
|
|
#pragma mark - Options
|
|
|
|
|
|
|
|
- (void)setUseFastLoadingHack:(BOOL)useFastLoadingHack {
|
|
|
|
Configurable::Device *configurable_device = _machine->configurable_device();
|
|
|
|
if(!configurable_device) return;
|
|
|
|
|
|
|
|
@synchronized(self) {
|
|
|
|
_useFastLoadingHack = useFastLoadingHack;
|
|
|
|
|
|
|
|
Configurable::SelectionSet selection_set;
|
|
|
|
append_quick_load_tape_selection(selection_set, useFastLoadingHack ? true : false);
|
|
|
|
configurable_device->set_selections(selection_set);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-01 17:29:04 +00:00
|
|
|
- (void)setVideoSignal:(CSMachineVideoSignal)videoSignal {
|
2017-11-25 02:36:22 +00:00
|
|
|
Configurable::Device *configurable_device = _machine->configurable_device();
|
|
|
|
if(!configurable_device) return;
|
|
|
|
|
|
|
|
@synchronized(self) {
|
2018-04-01 17:29:04 +00:00
|
|
|
_videoSignal = videoSignal;
|
2017-11-25 02:36:22 +00:00
|
|
|
|
|
|
|
Configurable::SelectionSet selection_set;
|
2018-04-01 17:29:04 +00:00
|
|
|
Configurable::Display display;
|
|
|
|
switch(videoSignal) {
|
2019-02-13 00:52:32 +00:00
|
|
|
case CSMachineVideoSignalRGB: display = Configurable::Display::RGB; break;
|
|
|
|
case CSMachineVideoSignalSVideo: display = Configurable::Display::SVideo; break;
|
|
|
|
case CSMachineVideoSignalComposite: display = Configurable::Display::CompositeColour; break;
|
|
|
|
case CSMachineVideoSignalMonochromeComposite: display = Configurable::Display::CompositeMonochrome; break;
|
2018-04-01 17:29:04 +00:00
|
|
|
}
|
|
|
|
append_display_selection(selection_set, display);
|
2017-11-25 02:36:22 +00:00
|
|
|
configurable_device->set_selections(selection_set);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-01 17:29:04 +00:00
|
|
|
- (bool)supportsVideoSignal:(CSMachineVideoSignal)videoSignal {
|
|
|
|
Configurable::Device *configurable_device = _machine->configurable_device();
|
|
|
|
if(!configurable_device) return NO;
|
|
|
|
|
|
|
|
// Get the options this machine provides.
|
|
|
|
std::vector<std::unique_ptr<Configurable::Option>> options;
|
|
|
|
@synchronized(self) {
|
|
|
|
options = configurable_device->get_options();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the standard option for this video signal.
|
|
|
|
Configurable::StandardOptions option;
|
|
|
|
switch(videoSignal) {
|
2019-02-13 00:52:32 +00:00
|
|
|
case CSMachineVideoSignalRGB: option = Configurable::DisplayRGB; break;
|
|
|
|
case CSMachineVideoSignalSVideo: option = Configurable::DisplaySVideo; break;
|
|
|
|
case CSMachineVideoSignalComposite: option = Configurable::DisplayCompositeColour; break;
|
|
|
|
case CSMachineVideoSignalMonochromeComposite: option = Configurable::DisplayCompositeMonochrome; break;
|
2018-04-01 17:29:04 +00:00
|
|
|
}
|
|
|
|
std::unique_ptr<Configurable::Option> display_option = std::move(standard_options(option).front());
|
|
|
|
Configurable::ListOption *display_list_option = dynamic_cast<Configurable::ListOption *>(display_option.get());
|
|
|
|
NSAssert(display_list_option, @"Expected display option to be a list");
|
|
|
|
|
|
|
|
// See whether the video signal is included in the machine options.
|
|
|
|
for(auto &candidate: options) {
|
|
|
|
Configurable::ListOption *list_option = dynamic_cast<Configurable::ListOption *>(candidate.get());
|
|
|
|
|
|
|
|
// Both should be list options
|
|
|
|
if(!list_option) continue;
|
|
|
|
|
|
|
|
// Check for same name of option.
|
|
|
|
if(candidate->short_name != display_option->short_name) continue;
|
|
|
|
|
|
|
|
// Check that the video signal option is included.
|
|
|
|
return std::find(list_option->options.begin(), list_option->options.end(), display_list_option->options.front()) != list_option->options.end();
|
|
|
|
}
|
|
|
|
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
2017-11-25 02:36:22 +00:00
|
|
|
- (void)setUseAutomaticTapeMotorControl:(BOOL)useAutomaticTapeMotorControl {
|
|
|
|
Configurable::Device *configurable_device = _machine->configurable_device();
|
|
|
|
if(!configurable_device) return;
|
|
|
|
|
|
|
|
@synchronized(self) {
|
|
|
|
_useAutomaticTapeMotorControl = useAutomaticTapeMotorControl;
|
|
|
|
|
|
|
|
Configurable::SelectionSet selection_set;
|
|
|
|
append_automatic_tape_motor_control_selection(selection_set, useAutomaticTapeMotorControl ? true : false);
|
|
|
|
configurable_device->set_selections(selection_set);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-20 02:32:12 +00:00
|
|
|
- (void)setUseQuickBootingHack:(BOOL)useQuickBootingHack {
|
|
|
|
Configurable::Device *configurable_device = _machine->configurable_device();
|
|
|
|
if(!configurable_device) return;
|
|
|
|
|
|
|
|
@synchronized(self) {
|
|
|
|
_useQuickBootingHack = useQuickBootingHack;
|
|
|
|
|
|
|
|
Configurable::SelectionSet selection_set;
|
|
|
|
append_quick_boot_selection(selection_set, useQuickBootingHack ? true : false);
|
|
|
|
configurable_device->set_selections(selection_set);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-25 01:14:15 +00:00
|
|
|
- (NSString *)userDefaultsPrefix {
|
|
|
|
// Assumes that the first machine in the targets list is the source of user defaults.
|
2018-01-25 03:35:54 +00:00
|
|
|
std::string name = Machine::ShortNameForTargetMachine(_analyser.targets.front()->machine);
|
2018-01-25 01:14:15 +00:00
|
|
|
return [[NSString stringWithUTF8String:name.c_str()] lowercaseString];
|
|
|
|
}
|
|
|
|
|
2018-08-06 22:52:42 +00:00
|
|
|
- (BOOL)canInsertMedia {
|
|
|
|
return !!_machine->media_target();
|
|
|
|
}
|
|
|
|
|
2018-02-13 02:46:21 +00:00
|
|
|
#pragma mark - Special machines
|
|
|
|
|
|
|
|
- (CSAtari2600 *)atari2600 {
|
|
|
|
return [[CSAtari2600 alloc] initWithAtari2600:_machine->raw_pointer() owner:self];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (CSZX8081 *)zx8081 {
|
|
|
|
return [[CSZX8081 alloc] initWithZX8081:_machine->raw_pointer() owner:self];
|
|
|
|
}
|
|
|
|
|
2018-06-13 23:22:34 +00:00
|
|
|
#pragma mark - Input device queries
|
|
|
|
|
|
|
|
- (BOOL)hasJoystick {
|
|
|
|
return !!_machine->joystick_machine();
|
|
|
|
}
|
|
|
|
|
2019-06-11 22:21:56 +00:00
|
|
|
- (BOOL)hasMouse {
|
|
|
|
return !!_machine->mouse_machine();
|
|
|
|
}
|
|
|
|
|
2018-10-25 01:59:30 +00:00
|
|
|
- (BOOL)hasExclusiveKeyboard {
|
|
|
|
return !!_machine->keyboard_machine() && _machine->keyboard_machine()->get_keyboard().is_exclusive();
|
2018-06-13 23:22:34 +00:00
|
|
|
}
|
|
|
|
|
2019-09-22 17:53:38 +00:00
|
|
|
- (BOOL)shouldUsurpCommand {
|
|
|
|
if(!_machine->keyboard_machine()) return NO;
|
|
|
|
|
|
|
|
const auto essential_modifiers = _machine->keyboard_machine()->get_keyboard().get_essential_modifiers();
|
|
|
|
return essential_modifiers.find(Inputs::Keyboard::Key::LeftMeta) != essential_modifiers.end() ||
|
|
|
|
essential_modifiers.find(Inputs::Keyboard::Key::RightMeta) != essential_modifiers.end();
|
|
|
|
}
|
|
|
|
|
2018-06-17 22:53:56 +00:00
|
|
|
#pragma mark - Activity observation
|
|
|
|
|
|
|
|
- (void)addLED:(NSString *)led {
|
|
|
|
[_leds addObject:led];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray<NSString *> *)leds {
|
|
|
|
return _leds;
|
|
|
|
}
|
|
|
|
|
2016-01-05 04:12:47 +00:00
|
|
|
@end
|