2017-11-04 23:36:46 +00:00
//
// main.cpp
// Clock Signal
//
// Created by Thomas Harte on 04/11/2017.
2018-05-13 19:19:52 +00:00
// Copyright 2017 Thomas Harte. All rights reserved.
2017-11-04 23:36:46 +00:00
//
2018-07-16 02:47:50 +00:00
# include <algorithm>
2018-07-28 13:14:18 +00:00
# include <array>
2020-02-10 02:44:55 +00:00
# include <atomic>
2022-03-25 20:58:06 +00:00
# include <cstddef>
2017-11-19 00:34:38 +00:00
# include <cstdio>
2019-07-23 01:14:21 +00:00
# include <cstdlib>
2017-12-19 02:39:23 +00:00
# include <cstring>
2020-03-24 01:26:50 +00:00
# include <iomanip>
2017-11-04 23:36:46 +00:00
# include <iostream>
2020-03-15 16:54:55 +00:00
# include <map>
2017-11-05 17:49:28 +00:00
# include <memory>
2018-07-27 00:53:12 +00:00
# include <sys/stat.h>
2017-11-06 01:12:01 +00:00
2023-12-15 00:28:43 +00:00
# include <SDL.h>
2017-11-04 23:36:46 +00:00
2018-01-25 03:35:54 +00:00
# include "../../Analyser/Static/StaticAnalyser.hpp"
2017-11-05 17:49:28 +00:00
# include "../../Machines/Utility/MachineForTarget.hpp"
2020-02-10 02:44:55 +00:00
# include "../../ClockReceiver/TimeTypes.hpp"
2020-02-10 03:27:02 +00:00
# include "../../ClockReceiver/ScanSynchroniser.hpp"
2020-02-10 02:44:55 +00:00
2020-04-02 03:19:34 +00:00
# include "../../Machines/MachineTypes.hpp"
2017-11-05 17:49:28 +00:00
2018-07-16 00:19:06 +00:00
# include "../../Activity/Observer.hpp"
2019-01-18 03:28:02 +00:00
# include "../../Outputs/OpenGL/Primitives/Rectangle.hpp"
# include "../../Outputs/OpenGL/ScanTarget.hpp"
2019-02-28 02:05:02 +00:00
# include "../../Outputs/OpenGL/Screenshot.hpp"
2018-07-16 00:19:06 +00:00
2020-03-20 03:24:06 +00:00
# include "../../Reflection/Enum.hpp"
# include "../../Reflection/Struct.hpp"
2020-03-13 00:56:02 +00:00
2017-11-05 17:49:28 +00:00
namespace {
2020-02-10 02:44:55 +00:00
struct MachineRunner {
2020-02-11 04:07:09 +00:00
MachineRunner ( ) {
frame_lock_ . clear ( ) ;
}
2020-02-10 02:44:55 +00:00
~ MachineRunner ( ) {
stop ( ) ;
}
void start ( ) {
last_time_ = Time : : nanos_now ( ) ;
timer_ = SDL_AddTimer ( timer_period , & sdl_callback , reinterpret_cast < void * > ( this ) ) ;
}
void stop ( ) {
if ( timer_ ) {
2020-02-13 04:47:04 +00:00
// SDL doesn't define whether SDL_RemoveTimer will block until any pending calls
// have been completed, or will return instantly. So: do an ordered shutdown,
// then remove the timer.
state_ = State : : Stopping ;
while ( state_ = = State : : Stopping ) {
frame_lock_ . clear ( ) ;
}
2020-02-10 02:44:55 +00:00
SDL_RemoveTimer ( timer_ ) ;
timer_ = 0 ;
}
}
void signal_vsync ( ) {
2020-02-10 03:27:02 +00:00
const auto now = Time : : nanos_now ( ) ;
const auto previous_vsync_time = vsync_time_ . load ( ) ;
vsync_time_ . store ( now ) ;
// Update estimate of current frame time.
frame_time_average_ - = frame_times_ [ frame_time_pointer_ ] ;
frame_times_ [ frame_time_pointer_ ] = now - previous_vsync_time ;
frame_time_average_ + = frame_times_ [ frame_time_pointer_ ] ;
frame_time_pointer_ = ( frame_time_pointer_ + 1 ) & ( frame_times_ . size ( ) - 1 ) ;
_frame_period . store ( ( 1e9 * 32.0 ) / double ( frame_time_average_ ) ) ;
2017-11-05 17:49:28 +00:00
}
2020-02-11 04:07:09 +00:00
void signal_did_draw ( ) {
frame_lock_ . clear ( ) ;
}
2020-02-11 04:30:32 +00:00
void set_speed_multiplier ( double multiplier ) {
scan_synchroniser_ . set_base_speed_multiplier ( multiplier ) ;
}
2020-01-25 19:48:00 +00:00
std : : mutex * machine_mutex ;
2017-11-05 17:49:28 +00:00
Machine : : DynamicMachine * machine ;
2020-02-10 02:44:55 +00:00
private :
SDL_TimerID timer_ = 0 ;
Time : : Nanos last_time_ = 0 ;
std : : atomic < Time : : Nanos > vsync_time_ ;
2020-02-11 04:07:09 +00:00
std : : atomic_flag frame_lock_ ;
2020-02-10 02:44:55 +00:00
2020-02-13 04:47:04 +00:00
enum class State {
Running ,
Stopping ,
Stopped
} ;
2020-05-21 03:34:26 +00:00
std : : atomic < State > state_ { State : : Running } ;
2020-02-13 04:47:04 +00:00
2020-02-10 03:27:02 +00:00
Time : : ScanSynchroniser scan_synchroniser_ ;
// A slightly clumsy means of trying to derive frame rate from calls to
// signal_vsync(); SDL_DisplayMode provides only an integral quantity
// whereas, empirically, it's fairly common for monitors to run at the
// NTSC-esque frame rates of 59.94Hz.
std : : array < Time : : Nanos , 32 > frame_times_ ;
Time : : Nanos frame_time_average_ = 0 ;
size_t frame_time_pointer_ = 0 ;
std : : atomic < double > _frame_period ;
2020-02-10 02:44:55 +00:00
static constexpr Uint32 timer_period = 4 ;
2020-06-15 04:24:10 +00:00
static Uint32 sdl_callback ( Uint32 , void * param ) {
2020-02-10 02:44:55 +00:00
reinterpret_cast < MachineRunner * > ( param ) - > update ( ) ;
return timer_period ;
}
void update ( ) {
2020-02-13 04:47:04 +00:00
// If a shutdown is in progress, signal stoppage and do nothing.
if ( state_ ! = State : : Running ) {
state_ = State : : Stopped ;
return ;
}
// Get time now and determine how long it has been since the last time this
// function was called. If it's more than half a second then forego any activity
// now, as there's obviously been some sort of substantial time glitch.
2020-02-10 03:27:02 +00:00
const auto time_now = Time : : nanos_now ( ) ;
2020-02-13 04:47:04 +00:00
if ( time_now - last_time_ > Time : : Nanos ( 500'000'000 ) ) {
2020-02-15 04:24:51 +00:00
last_time_ = time_now - Time : : Nanos ( 500'000'000 ) ;
2020-02-13 04:47:04 +00:00
}
2020-02-10 03:27:02 +00:00
const auto vsync_time = vsync_time_ . load ( ) ;
2020-02-10 02:44:55 +00:00
2020-06-15 04:24:10 +00:00
std : : unique_lock lock_guard ( * machine_mutex ) ;
2020-04-02 03:19:34 +00:00
const auto scan_producer = machine - > scan_producer ( ) ;
const auto timed_machine = machine - > timed_machine ( ) ;
2020-02-10 03:27:02 +00:00
bool split_and_sync = false ;
if ( last_time_ < vsync_time & & time_now > = vsync_time ) {
2020-04-02 03:19:34 +00:00
split_and_sync = scan_synchroniser_ . can_synchronise ( scan_producer - > get_scan_status ( ) , _frame_period ) ;
2020-02-10 03:27:02 +00:00
}
if ( split_and_sync ) {
2020-04-02 03:19:34 +00:00
timed_machine - > run_for ( double ( vsync_time - last_time_ ) / 1e9 ) ;
2022-07-12 14:57:22 +00:00
timed_machine - > flush_output ( MachineTypes : : TimedMachine : : Output : : All ) ;
2020-04-02 03:19:34 +00:00
timed_machine - > set_speed_multiplier (
scan_synchroniser_ . next_speed_multiplier ( scan_producer - > get_scan_status ( ) )
2020-02-10 03:27:02 +00:00
) ;
2020-02-11 04:07:09 +00:00
// This is a bit of an SDL ugliness; wait here until the next frame is drawn.
// That is, unless and until I can think of a good way of running background
// updates via a share group — possibly an extra intermediate buffer is needed?
lock_guard . unlock ( ) ;
while ( frame_lock_ . test_and_set ( ) ) ;
lock_guard . lock ( ) ;
2020-04-02 03:19:34 +00:00
timed_machine - > run_for ( double ( time_now - vsync_time ) / 1e9 ) ;
2022-07-12 14:57:22 +00:00
timed_machine - > flush_output ( MachineTypes : : TimedMachine : : Output : : All ) ;
2020-02-10 03:27:02 +00:00
} else {
2020-04-02 03:19:34 +00:00
timed_machine - > set_speed_multiplier ( scan_synchroniser_ . get_base_speed_multiplier ( ) ) ;
timed_machine - > run_for ( double ( time_now - last_time_ ) / 1e9 ) ;
2022-07-12 14:57:22 +00:00
timed_machine - > flush_output ( MachineTypes : : TimedMachine : : Output : : All ) ;
2020-02-10 03:27:02 +00:00
}
2020-02-10 02:44:55 +00:00
last_time_ = time_now ;
}
2017-11-05 17:49:28 +00:00
} ;
2017-12-18 02:26:06 +00:00
struct SpeakerDelegate : public Outputs : : Speaker : : Speaker : : Delegate {
2020-02-10 02:44:55 +00:00
// This is empirically the best that I can seem to do with SDL's timer precision.
2020-02-17 00:07:13 +00:00
static constexpr size_t buffered_samples = 1024 ;
2020-02-16 17:46:25 +00:00
bool is_stereo = false ;
2017-11-23 00:36:39 +00:00
2020-06-15 04:24:10 +00:00
void speaker_did_complete_samples ( Outputs : : Speaker : : Speaker * , const std : : vector < int16_t > & buffer ) final {
std : : lock_guard lock_guard ( audio_buffer_mutex_ ) ;
2020-02-17 00:07:13 +00:00
const size_t buffer_size = buffered_samples * ( is_stereo ? 2 : 1 ) ;
2017-11-23 00:36:39 +00:00
if ( audio_buffer_ . size ( ) > buffer_size ) {
audio_buffer_ . erase ( audio_buffer_ . begin ( ) , audio_buffer_ . end ( ) - buffer_size ) ;
}
audio_buffer_ . insert ( audio_buffer_ . end ( ) , buffer . begin ( ) , buffer . end ( ) ) ;
}
void audio_callback ( Uint8 * stream , int len ) {
2020-06-15 04:24:10 +00:00
std : : lock_guard lock_guard ( audio_buffer_mutex_ ) ;
2017-11-23 00:36:39 +00:00
2020-02-16 17:46:25 +00:00
// SDL buffer length is in bytes, so there's no need to adjust for stereo/mono in here.
2020-05-10 03:00:39 +00:00
const std : : size_t sample_length = size_t ( len ) / sizeof ( int16_t ) ;
2020-02-16 17:46:25 +00:00
const std : : size_t copy_length = std : : min ( sample_length , audio_buffer_ . size ( ) ) ;
int16_t * const target = static_cast < int16_t * > ( static_cast < void * > ( stream ) ) ;
2017-11-23 00:36:39 +00:00
std : : memcpy ( stream , audio_buffer_ . data ( ) , copy_length * sizeof ( int16_t ) ) ;
if ( copy_length < sample_length ) {
std : : memset ( & target [ copy_length ] , 0 , ( sample_length - copy_length ) * sizeof ( int16_t ) ) ;
}
audio_buffer_ . erase ( audio_buffer_ . begin ( ) , audio_buffer_ . begin ( ) + copy_length ) ;
}
static void SDL_audio_callback ( void * userdata , Uint8 * stream , int len ) {
reinterpret_cast < SpeakerDelegate * > ( userdata ) - > audio_callback ( stream , len ) ;
2017-11-06 03:29:25 +00:00
}
SDL_AudioDeviceID audio_device ;
2017-11-23 00:36:39 +00:00
std : : mutex audio_buffer_mutex_ ;
std : : vector < int16_t > audio_buffer_ ;
2017-11-06 03:29:25 +00:00
} ;
2018-07-16 00:19:06 +00:00
class ActivityObserver : public Activity : : Observer {
public :
ActivityObserver ( Activity : : Source * source , float aspect_ratio ) {
// Get the suorce to supply all LEDs and drives.
source - > set_activity_observer ( this ) ;
// The objective is to display drives on one side of the screen, other LEDs on the other. Drives
// may or may not have LEDs and this code intends to display only those which do; so a quick
// comparative processing of the two lists is called for.
// Strip the list of drives to only those which have LEDs. Thwy're the ones that'll be displayed.
drives_ . resize ( std : : remove_if ( drives_ . begin ( ) , drives_ . end ( ) , [ this ] ( const std : : string & string ) {
return std : : find ( leds_ . begin ( ) , leds_ . end ( ) , string ) = = leds_ . end ( ) ;
} ) - drives_ . begin ( ) ) ;
// Remove from the list of LEDs any which are drives. Those will be represented separately.
leds_ . resize ( std : : remove_if ( leds_ . begin ( ) , leds_ . end ( ) , [ this ] ( const std : : string & string ) {
return std : : find ( drives_ . begin ( ) , drives_ . end ( ) , string ) ! = drives_ . end ( ) ;
} ) - leds_ . begin ( ) ) ;
set_aspect_ratio ( aspect_ratio ) ;
}
void set_aspect_ratio ( float aspect_ratio ) {
2020-06-15 04:24:10 +00:00
std : : lock_guard lock_guard ( mutex ) ;
2018-07-16 00:19:06 +00:00
lights_ . clear ( ) ;
// Generate a bunch of LEDs for connected drives.
2019-12-22 05:22:17 +00:00
constexpr float height = 0.05f ;
2018-07-16 00:19:06 +00:00
const float width = height / aspect_ratio ;
const float right_x = 1.0f - 2.0f * width ;
float y = 1.0f - 2.0f * height ;
for ( const auto & drive : drives_ ) {
2024-05-30 01:42:46 +00:00
lights_ . emplace ( drive , std : : make_unique < Outputs : : Display : : OpenGL : : Rectangle > ( right_x , y , width , height ) ) ;
2018-07-16 00:19:06 +00:00
y - = height * 2.0f ;
}
/*
This would generate LEDs for things other than drives ; I ' m declining for now
due to the inexpressiveness of just painting a rectangle .
const float left_x = - 1.0f + 2.0f * width ;
y = 1.0f - 2.0f * height ;
for ( const auto & led : leds_ ) {
2024-05-30 01:42:46 +00:00
lights_ . emplace ( led , std : : make_unique < OpenGL : : Rectangle > ( left_x , y , width , height ) ) ;
2018-07-16 00:19:06 +00:00
y - = height * 2.0f ;
}
*/
}
void draw ( ) {
2020-06-15 04:24:10 +00:00
std : : lock_guard lock_guard ( mutex ) ;
2018-07-16 00:19:06 +00:00
for ( const auto & lit_led : lit_leds_ ) {
if ( blinking_leds_ . find ( lit_led ) = = blinking_leds_ . end ( ) & & lights_ . find ( lit_led ) ! = lights_ . end ( ) )
lights_ [ lit_led ] - > draw ( 0.0 , 0.8 , 0.0 ) ;
}
blinking_leds_ . clear ( ) ;
}
private :
std : : vector < std : : string > leds_ ;
2021-07-16 01:26:02 +00:00
void register_led ( const std : : string & name , uint8_t ) final {
2020-06-15 04:24:10 +00:00
std : : lock_guard lock_guard ( mutex ) ;
2018-07-16 00:19:06 +00:00
leds_ . push_back ( name ) ;
}
std : : vector < std : : string > drives_ ;
2020-01-24 03:57:51 +00:00
void register_drive ( const std : : string & name ) final {
2020-06-15 04:24:10 +00:00
std : : lock_guard lock_guard ( mutex ) ;
2018-07-16 00:19:06 +00:00
drives_ . push_back ( name ) ;
}
2020-01-24 03:57:51 +00:00
void set_led_status ( const std : : string & name , bool lit ) final {
2020-06-15 04:24:10 +00:00
std : : lock_guard lock_guard ( mutex ) ;
2018-07-16 00:19:06 +00:00
if ( lit ) lit_leds_ . insert ( name ) ;
else lit_leds_ . erase ( name ) ;
}
2020-06-15 04:24:10 +00:00
void announce_drive_event ( const std : : string & name , DriveEvent ) final {
std : : lock_guard lock_guard ( mutex ) ;
2018-07-16 00:19:06 +00:00
blinking_leds_ . insert ( name ) ;
}
2019-01-18 03:28:02 +00:00
std : : map < std : : string , std : : unique_ptr < Outputs : : Display : : OpenGL : : Rectangle > > lights_ ;
2018-07-16 00:19:06 +00:00
std : : set < std : : string > lit_leds_ ;
std : : set < std : : string > blinking_leds_ ;
2020-01-25 19:48:00 +00:00
std : : mutex mutex ;
2018-07-16 00:19:06 +00:00
} ;
2020-03-04 03:58:15 +00:00
bool KeyboardKeyForSDLScancode ( SDL_Scancode scancode , Inputs : : Keyboard : : Key & key ) {
2017-11-06 02:16:14 +00:00
# define BIND(x, y) case SDL_SCANCODE_##x: key = Inputs::Keyboard::Key::y; break;
switch ( scancode ) {
default : return false ;
BIND ( F1 , F1 ) BIND ( F2 , F2 ) BIND ( F3 , F3 ) BIND ( F4 , F4 ) BIND ( F5 , F5 ) BIND ( F6 , F6 )
BIND ( F7 , F7 ) BIND ( F8 , F8 ) BIND ( F9 , F9 ) BIND ( F10 , F10 ) BIND ( F11 , F11 ) BIND ( F12 , F12 )
BIND ( 1 , k1 ) BIND ( 2 , k2 ) BIND ( 3 , k3 ) BIND ( 4 , k4 ) BIND ( 5 , k5 )
BIND ( 6 , k6 ) BIND ( 7 , k7 ) BIND ( 8 , k8 ) BIND ( 9 , k9 ) BIND ( 0 , k0 )
BIND ( Q , Q ) BIND ( W , W ) BIND ( E , E ) BIND ( R , R ) BIND ( T , T )
BIND ( Y , Y ) BIND ( U , U ) BIND ( I , I ) BIND ( O , O ) BIND ( P , P )
BIND ( A , A ) BIND ( S , S ) BIND ( D , D ) BIND ( F , F ) BIND ( G , G )
BIND ( H , H ) BIND ( J , J ) BIND ( K , K ) BIND ( L , L )
BIND ( Z , Z ) BIND ( X , X ) BIND ( C , C ) BIND ( V , V )
BIND ( B , B ) BIND ( N , N ) BIND ( M , M )
2019-11-09 23:02:14 +00:00
BIND ( KP_7 , Keypad7 ) BIND ( KP_8 , Keypad8 ) BIND ( KP_9 , Keypad9 )
BIND ( KP_4 , Keypad4 ) BIND ( KP_5 , Keypad5 ) BIND ( KP_6 , Keypad6 )
BIND ( KP_1 , Keypad1 ) BIND ( KP_2 , Keypad2 ) BIND ( KP_3 , Keypad3 )
BIND ( KP_0 , Keypad0 )
2017-11-06 02:16:14 +00:00
BIND ( ESCAPE , Escape )
BIND ( PRINTSCREEN , PrintScreen ) BIND ( SCROLLLOCK , ScrollLock ) BIND ( PAUSE , Pause )
2019-08-02 20:15:34 +00:00
BIND ( GRAVE , BackTick ) BIND ( MINUS , Hyphen ) BIND ( EQUALS , Equals ) BIND ( BACKSPACE , Backspace )
2017-11-06 02:16:14 +00:00
BIND ( TAB , Tab )
BIND ( LEFTBRACKET , OpenSquareBracket ) BIND ( RIGHTBRACKET , CloseSquareBracket )
2019-08-02 20:15:34 +00:00
BIND ( BACKSLASH , Backslash )
2017-11-08 03:54:22 +00:00
2017-11-06 02:16:14 +00:00
BIND ( CAPSLOCK , CapsLock ) BIND ( SEMICOLON , Semicolon )
BIND ( APOSTROPHE , Quote ) BIND ( RETURN , Enter )
BIND ( LSHIFT , LeftShift ) BIND ( COMMA , Comma ) BIND ( PERIOD , FullStop )
BIND ( SLASH , ForwardSlash ) BIND ( RSHIFT , RightShift )
BIND ( LCTRL , LeftControl ) BIND ( LALT , LeftOption ) BIND ( LGUI , LeftMeta )
BIND ( SPACE , Space )
BIND ( RCTRL , RightControl ) BIND ( RALT , RightOption ) BIND ( RGUI , RightMeta )
BIND ( LEFT , Left ) BIND ( RIGHT , Right ) BIND ( UP , Up ) BIND ( DOWN , Down )
BIND ( INSERT , Insert ) BIND ( HOME , Home ) BIND ( PAGEUP , PageUp )
BIND ( DELETE , Delete ) BIND ( END , End ) BIND ( PAGEDOWN , PageDown )
2019-11-09 23:02:14 +00:00
BIND ( NUMLOCKCLEAR , NumLock ) BIND ( KP_DIVIDE , KeypadSlash ) BIND ( KP_MULTIPLY , KeypadAsterisk )
BIND ( KP_PLUS , KeypadPlus ) BIND ( KP_MINUS , KeypadMinus ) BIND ( KP_ENTER , KeypadEnter )
BIND ( KP_DECIMAL , KeypadDecimalPoint )
BIND ( KP_EQUALS , KeypadEquals )
2017-11-06 02:16:14 +00:00
BIND ( HELP , Help )
2017-11-06 02:29:20 +00:00
// SDL doesn't seem to have scancodes for hash or keypad delete?
2017-11-06 02:16:14 +00:00
}
# undef BIND
return true ;
}
2017-11-19 00:34:38 +00:00
struct ParsedArguments {
2020-03-20 00:40:43 +00:00
std : : vector < std : : string > file_names ;
2020-03-16 01:50:43 +00:00
std : : map < std : : string , std : : string > selections ; // The empty string will be inserted for arguments without an = suffix.
2020-03-19 03:11:25 +00:00
void apply ( Reflection : : Struct * reflectable ) const {
for ( const auto & argument : selections ) {
// Replace any dashes with underscores in the argument name.
std : : string property ;
std : : transform ( argument . first . begin ( ) , argument . first . end ( ) , std : : back_inserter ( property ) , [ ] ( char c ) { return c = = ' - ' ? ' _ ' : c ; } ) ;
if ( argument . second . empty ( ) ) {
Reflection : : set < bool > ( * reflectable , property , true ) ;
} else {
Reflection : : fuzzy_set ( * reflectable , property , argument . second ) ;
}
}
}
2017-11-19 00:34:38 +00:00
} ;
/*! Parses an argc/argv pair to discern program arguments. */
ParsedArguments parse_arguments ( int argc , char * argv [ ] ) {
ParsedArguments arguments ;
for ( int index = 1 ; index < argc ; + + index ) {
char * arg = argv [ index ] ;
// Accepted format is:
//
// --flag sets a Boolean option to true.
// --flag=value sets the value for a list option.
// name sets the file name to load.
2018-07-28 14:45:50 +00:00
2017-11-19 00:34:38 +00:00
// Anything starting with a dash always makes a selection; otherwise it's a file name.
if ( arg [ 0 ] = = ' - ' ) {
while ( * arg = = ' - ' ) arg + + ;
// Check for an equals sign, to discern a Boolean selection from a list selection.
std : : string argument = arg ;
std : : size_t split_index = argument . find ( " = " ) ;
if ( split_index = = std : : string : : npos ) {
2020-03-16 01:50:43 +00:00
arguments . selections [ argument ] ; // To create an entry with the default empty string.
2017-11-19 00:34:38 +00:00
} else {
2020-03-19 03:11:25 +00:00
const std : : string name = argument . substr ( 0 , split_index ) ;
2017-11-19 00:34:38 +00:00
std : : string value = argument . substr ( split_index + 1 , std : : string : : npos ) ;
2020-03-15 16:54:55 +00:00
arguments . selections [ name ] = value ;
2017-11-19 00:34:38 +00:00
}
} else {
2020-03-20 00:40:43 +00:00
arguments . file_names . push_back ( arg ) ;
2017-11-19 00:34:38 +00:00
}
}
return arguments ;
}
2017-11-21 02:55:32 +00:00
std : : string final_path_component ( const std : : string & path ) {
2017-11-21 02:59:53 +00:00
// An empty path has no final component.
2017-11-21 02:55:32 +00:00
if ( path . empty ( ) ) {
return " " ;
}
2017-11-21 02:59:53 +00:00
// Find the last slash...
2017-11-21 02:55:32 +00:00
auto final_slash = path . find_last_of ( " / \\ " ) ;
2018-07-28 14:45:50 +00:00
2017-11-21 02:59:53 +00:00
// If no slash was found at all, return the whole path.
2017-11-21 02:55:32 +00:00
if ( final_slash = = std : : string : : npos ) {
return path ;
}
2017-11-21 02:59:53 +00:00
// If a slash was found in the final position, remove it and recurse.
2017-11-21 02:55:32 +00:00
if ( final_slash = = path . size ( ) - 1 ) {
return final_path_component ( path . substr ( 0 , path . size ( ) - 1 ) ) ;
}
2017-11-21 02:59:53 +00:00
// Otherwise return everything from just after the slash to the end of the path.
2017-11-21 02:55:32 +00:00
return path . substr ( final_slash + 1 , path . size ( ) - final_slash - 1 ) ;
}
2018-07-28 13:14:18 +00:00
/*!
Executes @ c command and returns its STDOUT .
*/
std : : string system_get ( const char * command ) {
2024-10-16 01:49:42 +00:00
struct pcloser {
void operator ( ) ( FILE * file ) {
2024-10-16 01:54:04 +00:00
pclose ( file ) ;
2024-10-16 01:49:42 +00:00
}
} ;
std : : unique_ptr < FILE , pcloser > pipe ( popen ( command , " r " ) ) ;
2018-09-10 00:33:56 +00:00
if ( ! pipe ) return " " ;
2018-07-28 13:14:18 +00:00
std : : string result ;
2018-09-10 00:33:56 +00:00
while ( ! feof ( pipe . get ( ) ) ) {
2018-07-28 13:14:18 +00:00
std : : array < char , 256 > buffer ;
2018-09-10 00:33:56 +00:00
if ( fgets ( buffer . data ( ) , buffer . size ( ) , pipe . get ( ) ) ! = nullptr )
result + = buffer . data ( ) ;
}
return result ;
2018-07-28 13:14:18 +00:00
}
2019-08-02 20:38:05 +00:00
/*!
Maintains a communicative window title .
*/
class DynamicWindowTitler {
public :
DynamicWindowTitler ( SDL_Window * window ) : window_ ( window ) , file_name_ ( SDL_GetWindowTitle ( window ) ) { }
std : : string window_title ( ) {
if ( ! mouse_is_captured_ ) return file_name_ ;
return file_name_ + " (press control+escape to release mouse) " ;
}
void set_mouse_is_captured ( bool is_captured ) {
mouse_is_captured_ = is_captured ;
update_window_title ( ) ;
}
2021-05-08 17:30:07 +00:00
void set_file_name ( const std : : string & name ) {
file_name_ = name ;
update_window_title ( ) ;
}
2019-08-02 20:38:05 +00:00
private :
void update_window_title ( ) {
SDL_SetWindowTitle ( window_ , window_title ( ) . c_str ( ) ) ;
}
bool mouse_is_captured_ = false ;
SDL_Window * window_ = nullptr ;
2021-05-08 17:30:07 +00:00
std : : string file_name_ ;
2019-08-02 20:38:05 +00:00
} ;
2021-05-08 17:13:43 +00:00
/*!
Provides a wrapper for SDL_Joystick pointers that can keep track
of historic hat values .
*/
class SDLJoystick {
public :
SDLJoystick ( SDL_Joystick * joystick ) : joystick_ ( joystick ) {
hat_values_ . resize ( SDL_JoystickNumHats ( joystick ) ) ;
}
~ SDLJoystick ( ) {
SDL_JoystickClose ( joystick_ ) ;
}
/// @returns The underlying SDL_Joystick.
SDL_Joystick * get ( ) {
return joystick_ ;
}
/// @returns A reference to the storage for the previous state of hat @c c.
Uint8 & last_hat_value ( int c ) {
return hat_values_ [ c ] ;
}
/// @returns The logic OR of all stored hat states.
Uint8 hat_values ( ) {
Uint8 value = 0 ;
for ( const auto hat_value : hat_values_ ) {
value | = hat_value ;
}
return value ;
}
private :
SDL_Joystick * joystick_ ;
std : : vector < Uint8 > hat_values_ ;
} ;
2017-11-21 02:55:32 +00:00
}
2017-11-04 23:36:46 +00:00
int main ( int argc , char * argv [ ] ) {
SDL_Window * window = nullptr ;
2017-11-19 00:34:38 +00:00
// Attempt to parse arguments.
2020-03-19 02:31:32 +00:00
const ParsedArguments arguments = parse_arguments ( argc , argv ) ;
2017-11-19 00:34:38 +00:00
2018-06-14 01:31:13 +00:00
// This may be printed either as
2023-05-16 20:40:09 +00:00
const std : : string usage_suffix = " [file or --new={machine}] [OPTIONS] [--rompath={path to ROMs}] [--speed={speed multiplier, e.g. 1.5}] [--logical-keyboard] [--volume={0.0 to 1.0}] " ;
2018-06-14 01:31:13 +00:00
2017-11-21 02:55:32 +00:00
// Print a help message if requested.
if ( arguments . selections . find ( " help " ) ! = arguments . selections . end ( ) | | arguments . selections . find ( " h " ) ! = arguments . selections . end ( ) ) {
2020-03-19 03:11:25 +00:00
const auto all_machines = Machine : : AllMachines ( Machine : : Type : : DoesntRequireMedia , false ) ;
2020-03-16 01:50:43 +00:00
2018-06-14 01:31:13 +00:00
std : : cout < < " Usage: " < < final_path_component ( argv [ 0 ] ) < < usage_suffix < < std : : endl ;
2017-11-23 01:03:28 +00:00
std : : cout < < " Use alt+enter to toggle full screen display. Use control+shift+V to paste text. " < < std : : endl ;
2020-03-19 03:11:25 +00:00
std : : cout < < " Required machine type **and all options** are determined from the file if specified; otherwise use: " < < std : : endl < < std : : endl ;
2020-03-16 01:50:43 +00:00
std : : cout < < " \t --new={ " ;
bool is_first = true ;
for ( const auto & name : all_machines ) {
if ( ! is_first ) std : : cout < < " | " ;
std : : cout < < name ;
is_first = false ;
}
std : : cout < < " } " < < std : : endl < < std : : endl ;
2017-11-21 02:55:32 +00:00
2020-03-19 03:11:25 +00:00
std : : cout < < " Media is required to start the: " ;
const auto other_machines = Machine : : AllMachines ( Machine : : Type : : RequiresMedia , true ) ;
is_first = true ;
for ( const auto & name : other_machines ) {
if ( ! is_first ) std : : cout < < " , " ;
std : : cout < < name ;
is_first = false ;
}
std : : cout < < " . " < < std : : endl < < std : : endl ;
2024-02-12 19:23:54 +00:00
std : : cout < < " Further machine options: " < < std : : endl < < std : : endl ;
2018-07-28 14:45:50 +00:00
2020-03-15 16:54:55 +00:00
const auto targets = Machine : : TargetsByMachineName ( false ) ;
2020-03-17 03:25:05 +00:00
const auto runtime_options = Machine : : AllOptionsByMachineName ( ) ;
2020-03-19 03:11:25 +00:00
const auto machine_names = Machine : : AllMachines ( Machine : : Type : : Any , true ) ;
2020-03-17 03:25:05 +00:00
for ( const auto & machine : machine_names ) {
const auto target = targets . find ( machine ) ;
const auto options = runtime_options . find ( machine ) ;
const auto target_reflectable = dynamic_cast < Reflection : : Struct * > ( target ! = targets . end ( ) ? target - > second . get ( ) : nullptr ) ;
const auto options_reflectable = dynamic_cast < Reflection : : Struct * > ( options ! = runtime_options . end ( ) ? options - > second . get ( ) : nullptr ) ;
// Don't print a section for this machine if it has no construction and no runtime options objects.
if ( ! target_reflectable & & ! options_reflectable ) continue ;
const auto target_keys = target_reflectable ? target_reflectable - > all_keys ( ) : std : : vector < std : : string > ( ) ;
const auto options_keys = options_reflectable ? options_reflectable - > all_keys ( ) : std : : vector < std : : string > ( ) ;
// Don't print a section for this machine if it doesn't actually have any options.
if ( target_keys . empty ( ) & & options_keys . empty ( ) ) {
continue ;
}
std : : cout < < machine < < " : " < < std : : endl ;
2020-03-18 01:44:04 +00:00
// Join the two lists of properties and sort the result.
2020-03-17 03:25:05 +00:00
std : : vector < std : : string > all_options = options_keys ;
all_options . insert ( all_options . end ( ) , target_keys . begin ( ) , target_keys . end ( ) ) ;
2020-03-18 01:44:04 +00:00
std : : sort ( all_options . begin ( ) , all_options . end ( ) ) ;
2020-03-17 03:25:05 +00:00
for ( const auto & option : all_options ) {
2020-03-19 03:11:25 +00:00
// Replace any underscores with hyphens, better to conform to command-line norms.
std : : string mapped_option ;
std : : transform ( option . begin ( ) , option . end ( ) , std : : back_inserter ( mapped_option ) , [ ] ( char c ) { return c = = ' _ ' ? ' - ' : c ; } ) ;
std : : cout < < ' \t ' < < " -- " < < mapped_option ;
2020-03-17 03:25:05 +00:00
2020-03-18 01:44:04 +00:00
auto source = target_reflectable ;
2020-03-18 04:06:52 +00:00
auto type = target_reflectable ? target_reflectable - > type_of ( option ) : nullptr ;
2020-03-17 03:25:05 +00:00
if ( ! type ) {
2020-03-18 01:44:04 +00:00
source = options_reflectable ;
2020-03-17 03:25:05 +00:00
type = options_reflectable - > type_of ( option ) ;
}
2020-03-13 00:56:02 +00:00
// Is this a registered enum? If so, list options.
if ( ! Reflection : : Enum : : name ( * type ) . empty ( ) ) {
std : : cout < < " ={ " ;
bool is_first = true ;
2020-03-18 01:44:04 +00:00
for ( const auto & value : source - > values_for ( option ) ) {
2020-03-13 00:56:02 +00:00
if ( ! is_first ) std : : cout < < ' | ' ;
is_first = false ;
2020-03-19 03:11:25 +00:00
2020-03-13 00:56:02 +00:00
std : : cout < < value ;
}
std : : cout < < " } " ;
}
2020-03-18 01:44:04 +00:00
// The above effectively assumes that every field is either a
// Boolean or an enum. This may need to be revisted. It also
// assumes no name collisions, but that's kind of unavoidable.
2020-03-17 03:25:05 +00:00
std : : cout < < std : : endl ;
2020-03-13 00:56:02 +00:00
}
std : : cout < < std : : endl ;
}
2019-07-23 01:14:21 +00:00
return EXIT_SUCCESS ;
2017-11-21 02:55:32 +00:00
}
2020-03-16 01:50:43 +00:00
// Determine the machine for the supplied file, if any, or from --new.
Analyser : : Static : : TargetList targets ;
const auto new_argument = arguments . selections . find ( " new " ) ;
2020-03-20 00:40:43 +00:00
std : : string long_machine_name ;
2020-03-16 01:50:43 +00:00
if ( new_argument ! = arguments . selections . end ( ) & & ! new_argument - > second . empty ( ) ) {
// Perform for a case-insensitive search against short names.
2020-03-19 03:11:25 +00:00
const auto short_names = Machine : : AllMachines ( Machine : : Type : : DoesntRequireMedia , false ) ;
2020-03-16 01:50:43 +00:00
auto short_name = short_names . begin ( ) ;
while ( short_name ! = short_names . end ( ) ) {
if ( std : : equal (
short_name - > begin ( ) , short_name - > end ( ) ,
new_argument - > second . begin ( ) , new_argument - > second . end ( ) ,
[ ] ( char a , char b ) { return tolower ( b ) = = tolower ( a ) ; } ) ) {
break ;
}
+ + short_name ;
}
// If a match was found, use the corresponding long name to look up a suitable
// Analyser::Statuc::Target and move that to the targets list.
if ( short_name ! = short_names . end ( ) ) {
2020-03-20 00:40:43 +00:00
long_machine_name = Machine : : AllMachines ( Machine : : Type : : DoesntRequireMedia , true ) [ short_name - short_names . begin ( ) ] ;
2020-03-16 01:50:43 +00:00
auto targets_by_machine = Machine : : TargetsByMachineName ( false ) ;
2020-03-20 00:40:43 +00:00
std : : unique_ptr < Analyser : : Static : : Target > tgt = std : : move ( targets_by_machine [ long_machine_name ] ) ;
2020-03-16 01:50:43 +00:00
targets . push_back ( std : : move ( tgt ) ) ;
}
2020-03-20 01:15:38 +00:00
} else if ( ! arguments . file_names . empty ( ) ) {
// Take the first file name that actually implies a machine.
auto file_name = arguments . file_names . begin ( ) ;
while ( file_name ! = arguments . file_names . end ( ) & & targets . empty ( ) ) {
targets = Analyser : : Static : : GetTargets ( * file_name ) ;
+ + file_name ;
}
2017-11-04 23:36:46 +00:00
}
2017-11-05 17:49:28 +00:00
2017-11-04 23:36:46 +00:00
if ( targets . empty ( ) ) {
2020-03-20 00:40:43 +00:00
if ( ! arguments . file_names . empty ( ) ) {
std : : cerr < < " Cannot open " ;
bool is_first = true ;
for ( const auto & name : arguments . file_names ) {
if ( ! is_first ) std : : cerr < < " , " ;
is_first = false ;
std : : cerr < < name ;
}
std : : cerr < < " ; no target machine found " < < std : : endl ;
2020-03-16 01:50:43 +00:00
return EXIT_FAILURE ;
}
if ( ! new_argument - > second . empty ( ) ) {
std : : cerr < < " Unknown machine: " < < new_argument - > second < < std : : endl ;
return EXIT_FAILURE ;
}
std : : cerr < < " Usage: " < < final_path_component ( argv [ 0 ] ) < < usage_suffix < < std : : endl ;
std : : cerr < < " Use --help to learn more about available options. " < < std : : endl ;
2019-07-23 01:14:21 +00:00
return EXIT_FAILURE ;
2017-11-04 23:36:46 +00:00
}
2020-02-10 02:44:55 +00:00
MachineRunner machine_runner ;
2017-11-06 03:29:25 +00:00
SpeakerDelegate speaker_delegate ;
2017-11-05 17:49:28 +00:00
2017-11-06 01:12:01 +00:00
// For vanilla SDL purposes, assume system ROMs can be found in one of:
//
2018-06-14 01:21:52 +00:00
// /usr/local/share/CLK/[system];
// /usr/share/CLK/[system]; or
// [user-supplied path]/[system]
2021-06-06 01:09:35 +00:00
ROM : : Request missing_roms ;
2021-05-30 23:40:29 +00:00
std : : vector < std : : string > checked_paths ;
2021-06-06 01:09:35 +00:00
ROMMachine : : ROMFetcher rom_fetcher = [ & missing_roms , & arguments , & checked_paths ]
2021-06-04 02:22:56 +00:00
( const ROM : : Request & roms ) - > ROM : : Map {
2018-06-14 01:21:52 +00:00
std : : vector < std : : string > paths = {
" /usr/local/share/CLK/ " ,
" /usr/share/CLK/ "
} ;
2020-03-19 02:31:32 +00:00
const auto rompath = arguments . selections . find ( " rompath " ) ;
if ( rompath ! = arguments . selections . end ( ) ) {
2021-06-05 01:53:56 +00:00
std : : string path = rompath - > second ;
// Ensure the path ends in a slash.
if ( path . back ( ) ! = ' / ' ) {
path + = ' / ' ;
}
// If ~ is present, expand it to %HOME%.
const size_t tilde_position = path . find ( " ~ " ) ;
if ( tilde_position ! = std : : string : : npos ) {
path . replace ( tilde_position , 1 , getenv ( " HOME " ) ) ;
2020-03-16 01:50:43 +00:00
}
2021-06-05 01:53:56 +00:00
paths . push_back ( path ) ;
2020-03-16 01:50:43 +00:00
}
2018-06-14 01:21:52 +00:00
2021-06-04 02:22:56 +00:00
ROM : : Map results ;
for ( const auto & description : roms . all_descriptions ( ) ) {
for ( const auto & file_name : description . file_names ) {
FILE * file = nullptr ;
std : : vector < std : : string > rom_checked_paths ;
for ( const auto & path : paths ) {
std : : string local_path = path + description . machine_name + " / " + file_name ;
file = std : : fopen ( local_path . c_str ( ) , " rb " ) ;
rom_checked_paths . push_back ( local_path ) ;
if ( file ) break ;
}
2017-11-08 02:19:51 +00:00
2021-06-04 02:22:56 +00:00
if ( ! file ) {
std : : copy ( rom_checked_paths . begin ( ) , rom_checked_paths . end ( ) , std : : back_inserter ( checked_paths ) ) ;
continue ;
}
2017-11-08 03:54:22 +00:00
2021-06-04 02:22:56 +00:00
std : : vector < uint8_t > data ;
2017-11-08 02:19:51 +00:00
2021-06-04 02:22:56 +00:00
std : : fseek ( file , 0 , SEEK_END ) ;
data . resize ( std : : ftell ( file ) ) ;
std : : fseek ( file , 0 , SEEK_SET ) ;
std : : size_t read = fread ( data . data ( ) , 1 , data . size ( ) , file ) ;
std : : fclose ( file ) ;
2017-11-08 02:19:51 +00:00
2021-06-04 02:22:56 +00:00
if ( read = = data . size ( ) ) {
results [ description . name ] = std : : move ( data ) ;
} else {
std : : copy ( rom_checked_paths . begin ( ) , rom_checked_paths . end ( ) , std : : back_inserter ( checked_paths ) ) ;
}
2021-05-30 23:40:29 +00:00
}
2017-11-22 00:22:33 +00:00
}
2017-11-06 01:12:01 +00:00
2021-06-06 01:09:35 +00:00
missing_roms = roms . subtract ( results ) ;
2017-11-22 00:22:33 +00:00
return results ;
2018-01-25 23:28:19 +00:00
} ;
2017-11-08 03:54:22 +00:00
2020-03-19 02:31:32 +00:00
// Apply all command-line options to the targets.
for ( auto & target : targets ) {
auto reflectable_target = dynamic_cast < Reflection : : Struct * > ( target . get ( ) ) ;
if ( ! reflectable_target ) continue ;
2020-03-19 03:11:25 +00:00
arguments . apply ( reflectable_target ) ;
2020-03-19 02:31:32 +00:00
}
2018-01-25 23:28:19 +00:00
// Create and configure a machine.
: : Machine : : Error error ;
2020-01-25 19:48:00 +00:00
std : : mutex machine_mutex ;
2018-01-25 23:28:19 +00:00
std : : unique_ptr < : : Machine : : DynamicMachine > machine ( : : Machine : : MachineForTargets ( targets , rom_fetcher , error ) ) ;
if ( ! machine ) {
switch ( error ) {
default : break ;
2021-06-05 01:53:56 +00:00
case : : Machine : : Error : : MissingROM : {
std : : cerr < < " Could not find system ROMs; please install to /usr/local/share/CLK/ or /usr/share/CLK/, or provide a --rompath, e.g. --rompath=~/ROMs. " < < std : : endl ;
2021-06-13 23:28:05 +00:00
std : : cerr < < " Needed but didn't find " ;
2021-06-05 01:53:56 +00:00
2021-06-07 00:02:13 +00:00
using DescriptionFlag = ROM : : Description : : DescriptionFlag ;
std : : wcerr < < missing_roms . description ( DescriptionFlag : : Filename | DescriptionFlag : : CRC , L ' * ' ) ;
2021-06-05 01:53:56 +00:00
std : : cerr < < std : : endl < < std : : endl < < " Searched unsuccessfully: " ;
2021-05-30 23:40:29 +00:00
bool is_first = true ;
for ( const auto & path : checked_paths ) {
if ( ! is_first ) std : : cerr < < " ; " ;
std : : cerr < < path ;
is_first = false ;
}
2021-06-05 01:53:56 +00:00
std : : cerr < < std : : endl ;
} break ;
2017-11-22 00:22:33 +00:00
}
2018-01-25 23:28:19 +00:00
2019-07-23 01:14:21 +00:00
return EXIT_FAILURE ;
2017-11-08 03:32:59 +00:00
}
2017-11-06 01:12:01 +00:00
2020-03-19 02:31:32 +00:00
// Apply all command-line options to the machines.
auto configurable = machine - > configurable_device ( ) ;
if ( configurable ) {
const auto options = configurable - > get_options ( ) ;
2020-03-19 03:11:25 +00:00
arguments . apply ( options . get ( ) ) ;
2020-03-19 02:31:32 +00:00
configurable - > set_options ( options ) ;
}
2020-01-26 18:25:23 +00:00
// Apply the speed multiplier, if one was requested.
2020-03-22 02:24:31 +00:00
{
const auto speed_argument = arguments . selections . find ( " speed " ) ;
if ( speed_argument ! = arguments . selections . end ( ) ) {
const char * speed_string = speed_argument - > second . c_str ( ) ;
char * end ;
const double speed = strtod ( speed_string , & end ) ;
if ( size_t ( end - speed_string ) ! = strlen ( speed_string ) ) {
std : : cerr < < " Unable to parse speed: " < < speed_string < < std : : endl ;
} else if ( speed < = 0.0 ) {
std : : cerr < < " Cannot run at speed " < < speed_string < < " ; speeds must be positive. " < < std : : endl ;
} else {
machine_runner . set_speed_multiplier ( speed ) ;
}
}
}
// Apply the desired output volume, if requested.
{
const auto volume_argument = arguments . selections . find ( " volume " ) ;
if ( volume_argument ! = arguments . selections . end ( ) ) {
const char * volume_string = volume_argument - > second . c_str ( ) ;
char * end ;
const double volume = strtod ( volume_string , & end ) ;
if ( size_t ( end - volume_string ) ! = strlen ( volume_string ) ) {
std : : cerr < < " Unable to parse volume: " < < volume_string < < std : : endl ;
} else if ( volume < 0.0 | | volume > 1.0 ) {
std : : cerr < < " Cannot run with volume " < < volume_string < < " ; volumes must be between 0.0 and 1.0. " < < std : : endl ;
} else {
2021-06-15 01:02:55 +00:00
const auto audio_producer = machine - > audio_producer ( ) ;
if ( audio_producer ) {
const auto speaker = machine - > audio_producer ( ) - > get_speaker ( ) ;
if ( speaker ) speaker - > set_output_volume ( volume ) ;
}
2020-03-22 02:24:31 +00:00
}
2020-03-19 02:31:32 +00:00
}
2020-01-26 18:25:23 +00:00
}
2020-11-28 02:00:48 +00:00
// Check whether a 'logical' keyboard has been requested, or the machine would prefer one anyway.
const bool logical_keyboard =
( arguments . selections . find ( " logical-keyboard " ) ! = arguments . selections . end ( ) ) | |
( machine - > keyboard_machine ( ) & & machine - > keyboard_machine ( ) - > prefers_logical_input ( ) ) ;
2020-03-10 03:10:39 +00:00
if ( logical_keyboard ) {
SDL_StartTextInput ( ) ;
}
2020-03-01 04:07:14 +00:00
2020-03-20 01:15:38 +00:00
// Ensure all media is inserted, if this machine accepts it.
{
auto media_target = machine - > media_target ( ) ;
if ( media_target ) {
Analyser : : Static : : Media media ;
for ( const auto & file_name : arguments . file_names ) {
media + = Analyser : : Static : : GetMedia ( file_name ) ;
}
media_target - > insert_media ( media ) ;
}
2020-03-20 00:40:43 +00:00
}
2018-01-25 23:28:19 +00:00
// Attempt to set up video and audio.
if ( SDL_Init ( SDL_INIT_VIDEO | SDL_INIT_AUDIO ) < 0 ) {
std : : cerr < < " SDL could not initialize! SDL_Error: " < < SDL_GetError ( ) < < std : : endl ;
2019-07-23 01:14:21 +00:00
return EXIT_FAILURE ;
2018-01-25 23:28:19 +00:00
}
// Ask for no depth buffer, a core profile and vsync-aligned rendering.
SDL_GL_SetAttribute ( SDL_GL_DEPTH_SIZE , 0 ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_PROFILE_MASK , SDL_GL_CONTEXT_PROFILE_CORE ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_MAJOR_VERSION , 3 ) ;
SDL_GL_SetAttribute ( SDL_GL_CONTEXT_MINOR_VERSION , 2 ) ;
SDL_GL_SetSwapInterval ( 1 ) ;
2020-03-20 00:40:43 +00:00
window = SDL_CreateWindow ( long_machine_name . empty ( ) ? final_path_component ( arguments . file_names . front ( ) ) . c_str ( ) : long_machine_name . c_str ( ) ,
2018-01-25 23:28:19 +00:00
SDL_WINDOWPOS_UNDEFINED , SDL_WINDOWPOS_UNDEFINED ,
400 , 300 ,
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE ) ;
2019-08-02 20:38:05 +00:00
DynamicWindowTitler window_titler ( window ) ;
2019-03-02 23:07:05 +00:00
SDL_GLContext gl_context = nullptr ;
2019-02-25 03:31:59 +00:00
if ( window ) {
gl_context = SDL_GL_CreateContext ( window ) ;
}
2019-02-21 03:32:42 +00:00
if ( ! window | | ! gl_context ) {
2019-02-25 03:31:59 +00:00
std : : cerr < < " Could not create " < < ( window ? " OpenGL context " : " window " ) ;
std : : cerr < < " ; reported error: \" " < < SDL_GetError ( ) < < " \" " < < std : : endl ;
2019-07-23 01:14:21 +00:00
return EXIT_FAILURE ;
2018-01-25 23:28:19 +00:00
}
SDL_GL_MakeCurrent ( window , gl_context ) ;
GLint target_framebuffer = 0 ;
glGetIntegerv ( GL_FRAMEBUFFER_BINDING , & target_framebuffer ) ;
2017-11-07 03:13:38 +00:00
// Setup output, assuming a CRT machine for now, and prepare a best-effort updater.
2019-01-18 03:28:02 +00:00
Outputs : : Display : : OpenGL : : ScanTarget scan_target ( target_framebuffer ) ;
2021-05-08 17:13:43 +00:00
std : : unique_ptr < ActivityObserver > activity_observer ;
bool uses_mouse ;
std : : vector < SDLJoystick > joysticks ;
2018-06-17 02:25:46 +00:00
2021-05-08 17:30:07 +00:00
machine_runner . machine_mutex = & machine_mutex ;
const auto setup_machine_input_output = [ & scan_target , & machine , & speaker_delegate , & activity_observer , & joysticks , & uses_mouse , & machine_runner ] {
// Wire up the best-effort updater, its delegate, and the speaker delegate.
machine_runner . machine = machine . get ( ) ;
2021-05-08 17:13:43 +00:00
machine - > scan_producer ( ) - > set_scan_target ( & scan_target ) ;
// For now, lie about audio output intentions.
2021-06-15 01:02:55 +00:00
const auto audio_producer = machine - > audio_producer ( ) ;
if ( audio_producer ) {
auto speaker = audio_producer - > get_speaker ( ) ;
if ( speaker ) {
// Create an audio pipe.
SDL_AudioSpec desired_audio_spec ;
SDL_AudioSpec obtained_audio_spec ;
SDL_zero ( desired_audio_spec ) ;
desired_audio_spec . freq = 48000 ; // TODO: how can I get SDL to reveal the output rate of this machine?
desired_audio_spec . format = AUDIO_S16 ;
desired_audio_spec . channels = 1 + int ( speaker - > get_is_stereo ( ) ) ;
desired_audio_spec . samples = Uint16 ( SpeakerDelegate : : buffered_samples ) ;
desired_audio_spec . callback = SpeakerDelegate : : SDL_audio_callback ;
desired_audio_spec . userdata = & speaker_delegate ;
speaker_delegate . audio_device = SDL_OpenAudioDevice ( nullptr , 0 , & desired_audio_spec , & obtained_audio_spec , SDL_AUDIO_ALLOW_FREQUENCY_CHANGE ) ;
speaker - > set_output_rate ( obtained_audio_spec . freq , desired_audio_spec . samples , obtained_audio_spec . channels = = 2 ) ;
speaker_delegate . is_stereo = obtained_audio_spec . channels = = 2 ;
speaker - > set_delegate ( & speaker_delegate ) ;
SDL_PauseAudioDevice ( speaker_delegate . audio_device , 0 ) ;
}
2021-05-08 17:13:43 +00:00
}
2018-06-17 02:25:46 +00:00
2021-05-08 17:13:43 +00:00
/*
If the machine offers anything for activity observation ,
create and register an activity observer .
*/
Activity : : Source * const activity_source = machine - > activity_source ( ) ;
if ( activity_source ) {
activity_observer = std : : make_unique < ActivityObserver > ( activity_source , 4.0f / 3.0f ) ;
} else {
activity_observer = nullptr ;
}
2018-06-17 02:25:46 +00:00
2021-05-08 17:13:43 +00:00
// If this is a joystick machine, check for and open attached joysticks.
const auto joystick_machine = machine - > joystick_machine ( ) ;
if ( joystick_machine ) {
SDL_InitSubSystem ( SDL_INIT_JOYSTICK ) ;
for ( int c = 0 ; c < SDL_NumJoysticks ( ) ; + + c ) {
joysticks . emplace_back ( SDL_JoystickOpen ( c ) ) ;
2018-06-17 02:25:46 +00:00
}
2021-05-08 17:13:43 +00:00
} else {
joysticks . clear ( ) ;
}
2018-06-17 02:25:46 +00:00
2021-05-08 17:13:43 +00:00
// Keep a record of whether mouse events can be forwarded.
uses_mouse = ! ! machine - > mouse_machine ( ) ;
2018-06-17 02:25:46 +00:00
} ;
2021-05-08 17:13:43 +00:00
setup_machine_input_output ( ) ;
2018-06-15 02:37:44 +00:00
2021-05-08 17:13:43 +00:00
int window_width , window_height ;
SDL_GetWindowSize ( window , & window_width , & window_height ) ;
2018-07-16 00:19:06 +00:00
2020-03-04 02:30:30 +00:00
// SDL 2.x delivers key up/down events and text inputs separately even when they're correlated;
// this struct and map is used to correlate them by time.
struct KeyPress {
2020-03-10 03:10:39 +00:00
uint32_t timestamp = 0 ;
2020-03-04 02:30:30 +00:00
std : : string input ;
2020-03-04 03:58:15 +00:00
SDL_Scancode scancode = SDL_SCANCODE_UNKNOWN ;
2020-03-10 03:10:39 +00:00
SDL_Keycode keycode = SDLK_UNKNOWN ;
bool is_down = true ;
2023-12-28 20:12:06 +00:00
bool repeat = false ;
2020-03-10 03:10:39 +00:00
KeyPress ( uint32_t timestamp , const char * text ) : timestamp ( timestamp ) , input ( text ) { }
2023-12-28 20:12:06 +00:00
KeyPress ( uint32_t timestamp , SDL_Scancode scancode , SDL_Keycode keycode , bool is_down , bool repeat ) :
timestamp ( timestamp ) , scancode ( scancode ) , keycode ( keycode ) , is_down ( is_down ) , repeat ( repeat ) { }
2024-02-17 02:47:23 +00:00
KeyPress ( ) = default ;
2020-03-04 02:30:30 +00:00
} ;
2020-03-10 03:10:39 +00:00
std : : vector < KeyPress > keypresses ;
2020-03-04 02:30:30 +00:00
2017-11-05 17:49:28 +00:00
// Run the main event loop until the OS tells us to quit.
2017-11-04 23:36:46 +00:00
bool should_quit = false ;
2017-11-22 02:44:29 +00:00
Uint32 fullscreen_mode = 0 ;
2020-02-10 02:44:55 +00:00
machine_runner . start ( ) ;
2017-11-04 23:36:46 +00:00
while ( ! should_quit ) {
2020-02-11 04:07:09 +00:00
// Draw a new frame, indicating completion of the draw to the machine runner.
2020-01-25 19:48:00 +00:00
scan_target . update ( int ( window_width ) , int ( window_height ) ) ;
scan_target . draw ( int ( window_width ) , int ( window_height ) ) ;
if ( activity_observer ) activity_observer - > draw ( ) ;
2020-02-11 04:07:09 +00:00
machine_runner . signal_did_draw ( ) ;
// Wait for presentation of that frame, posting a vsync.
SDL_GL_SwapWindow ( window ) ;
machine_runner . signal_vsync ( ) ;
// NB: machine_mutex is *not* currently locked, therefore it shouldn't
// be 'most' of the time — assuming most of the time is spent waiting
// on vsync, anyway.
2020-01-25 19:48:00 +00:00
// Grab the machine lock and process all pending events.
2020-06-15 04:24:10 +00:00
std : : lock_guard lock_guard ( machine_mutex ) ;
2020-03-04 03:58:15 +00:00
const auto keyboard_machine = machine - > keyboard_machine ( ) ;
2017-11-04 23:36:46 +00:00
SDL_Event event ;
2017-11-05 17:49:28 +00:00
while ( SDL_PollEvent ( & event ) ) {
2017-11-04 23:36:46 +00:00
switch ( event . type ) {
case SDL_QUIT : should_quit = true ; break ;
2017-11-05 17:49:28 +00:00
2017-11-13 00:47:18 +00:00
case SDL_WINDOWEVENT :
switch ( event . window . event ) {
case SDL_WINDOWEVENT_RESIZED : {
GLint target_framebuffer = 0 ;
glGetIntegerv ( GL_FRAMEBUFFER_BINDING , & target_framebuffer ) ;
2019-01-18 03:28:02 +00:00
scan_target . set_target_framebuffer ( target_framebuffer ) ;
2017-11-13 00:47:18 +00:00
SDL_GetWindowSize ( window , & window_width , & window_height ) ;
2020-05-10 03:00:39 +00:00
if ( activity_observer ) activity_observer - > set_aspect_ratio ( float ( window_width ) / float ( window_height ) ) ;
2017-11-13 00:47:18 +00:00
} break ;
default : break ;
}
break ;
2017-11-19 23:05:31 +00:00
case SDL_DROPFILE : {
2021-05-01 02:56:13 +00:00
const Analyser : : Static : : Media media = Analyser : : Static : : GetMedia ( event . drop . file ) ;
2021-05-08 17:30:07 +00:00
// If the new file is only media, insert it; if it is a state snapshot then
// tear down the entire machine and replace it.
2021-05-01 02:56:13 +00:00
if ( ! media . empty ( ) ) {
machine - > media_target ( ) - > insert_media ( media ) ;
break ;
}
2021-05-08 17:30:07 +00:00
targets = Analyser : : Static : : GetTargets ( event . drop . file ) ;
if ( targets . empty ( ) ) break ;
: : Machine : : Error error ;
std : : unique_ptr < : : Machine : : DynamicMachine > new_machine ( : : Machine : : MachineForTargets ( targets , rom_fetcher , error ) ) ;
if ( error ! = Machine : : Error : : None ) break ;
machine = std : : move ( new_machine ) ;
static_cast < Outputs : : Display : : ScanTarget * > ( & scan_target ) - > will_change_owner ( ) ;
setup_machine_input_output ( ) ;
window_titler . set_file_name ( final_path_component ( event . drop . file ) ) ;
2017-11-19 23:05:31 +00:00
} break ;
2018-01-25 23:28:19 +00:00
2020-03-04 03:58:15 +00:00
case SDL_TEXTINPUT :
2023-12-29 19:54:47 +00:00
keypresses . emplace_back ( event . text . timestamp , event . text . text ) ;
2020-03-04 03:58:15 +00:00
break ;
2020-03-04 02:30:30 +00:00
2017-11-04 23:36:46 +00:00
case SDL_KEYDOWN :
2020-01-25 19:48:00 +00:00
case SDL_KEYUP : {
if ( event . type = = 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 ) ) {
if ( keyboard_machine ) {
keyboard_machine - > type_string ( SDL_GetClipboardText ( ) ) ;
break ;
}
2017-11-22 02:44:29 +00:00
}
2020-01-25 19:48:00 +00:00
// Use ctrl+escape to release the mouse (if captured).
if ( event . key . keysym . sym = = SDLK_ESCAPE & & ( SDL_GetModState ( ) & KMOD_CTRL ) ) {
SDL_SetRelativeMouseMode ( SDL_FALSE ) ;
window_titler . set_mouse_is_captured ( false ) ;
}
2019-07-08 21:36:55 +00:00
2020-01-25 19:48:00 +00:00
// Capture ctrl+shift+d as a take-a-screenshot command.
if ( event . key . keysym . sym = = SDLK_d & & ( SDL_GetModState ( ) & KMOD_CTRL ) & & ( SDL_GetModState ( ) & KMOD_SHIFT ) ) {
// Grab the screen buffer.
Outputs : : Display : : OpenGL : : Screenshot screenshot ( 4 , 3 ) ;
2018-07-27 00:53:12 +00:00
2020-01-25 19:48:00 +00:00
// Pick the directory for images. Try `xdg-user-dir PICTURES` first.
std : : string target_directory = system_get ( " xdg-user-dir PICTURES " ) ;
2018-07-28 14:45:50 +00:00
2020-01-25 19:48:00 +00:00
// Make sure there are no newlines.
target_directory . erase ( std : : remove ( target_directory . begin ( ) , target_directory . end ( ) , ' \n ' ) , target_directory . end ( ) ) ;
target_directory . erase ( std : : remove ( target_directory . begin ( ) , target_directory . end ( ) , ' \r ' ) , target_directory . end ( ) ) ;
2018-07-28 14:45:50 +00:00
2020-01-25 19:48:00 +00:00
// Fall back on the HOME directory if necessary.
if ( target_directory . empty ( ) ) target_directory = getenv ( " HOME " ) ;
2018-07-28 13:14:18 +00:00
2020-01-25 19:48:00 +00:00
// Find the first available name of the form ~/clk-screenshot-<number>.bmp.
size_t index = 0 ;
std : : string target ;
while ( true ) {
target = target_directory + " /clk-screenshot- " + std : : to_string ( index ) + " .bmp " ;
2018-07-27 00:53:12 +00:00
2020-01-25 19:48:00 +00:00
struct stat file_stats ;
if ( stat ( target . c_str ( ) , & file_stats ) )
break ;
+ + index ;
}
2018-07-27 00:53:12 +00:00
2020-01-25 19:48:00 +00:00
// Create a suitable SDL surface and save the thing.
const bool is_big_endian = SDL_BYTEORDER = = SDL_BIG_ENDIAN ;
SDL_Surface * const surface = SDL_CreateRGBSurfaceFrom (
screenshot . pixel_data . data ( ) ,
screenshot . width , screenshot . height ,
8 * 4 ,
screenshot . width * 4 ,
is_big_endian ? 0xff000000 : 0x000000ff ,
is_big_endian ? 0x00ff0000 : 0x0000ff00 ,
is_big_endian ? 0x0000ff00 : 0x00ff0000 ,
0 ) ;
SDL_SaveBMP ( surface , target . c_str ( ) ) ;
SDL_FreeSurface ( surface ) ;
break ;
2018-07-27 00:53:12 +00:00
}
2020-02-17 00:07:13 +00:00
}
2018-07-27 00:53:12 +00:00
2020-02-17 00:07:13 +00:00
// Syphon off alt+enter (toggle full-screen) upon key up only; this was previously a key down action,
// but the SDL_KEYDOWN announcement was found to be reposted after changing graphics mode on some
// systems, causing a loop of changes, so key up is safer.
if ( event . type = = SDL_KEYUP & & event . key . keysym . sym = = SDLK_RETURN & & ( SDL_GetModState ( ) & KMOD_ALT ) ) {
fullscreen_mode ^ = SDL_WINDOW_FULLSCREEN_DESKTOP ;
SDL_SetWindowFullscreen ( window , fullscreen_mode ) ;
SDL_ShowCursor ( ( fullscreen_mode & SDL_WINDOW_FULLSCREEN_DESKTOP ) ? SDL_DISABLE : SDL_ENABLE ) ;
// Announce a potential discontinuity in keyboard input.
const auto keyboard_machine = machine - > keyboard_machine ( ) ;
if ( keyboard_machine ) {
keyboard_machine - > get_keyboard ( ) . reset_all_keys ( ) ;
2018-06-14 21:24:16 +00:00
}
2020-02-17 00:07:13 +00:00
break ;
2017-11-22 02:44:29 +00:00
}
2023-12-28 20:12:06 +00:00
keypresses . emplace_back (
event . text . timestamp ,
event . key . keysym . scancode ,
event . key . keysym . sym ,
event . type = = SDL_KEYDOWN ,
2023-12-29 19:54:47 +00:00
event . key . repeat ) ;
2017-11-13 00:47:18 +00:00
} break ;
2019-07-08 21:36:55 +00:00
case SDL_MOUSEBUTTONDOWN :
2020-01-25 19:48:00 +00:00
case SDL_MOUSEBUTTONUP : {
if ( uses_mouse & & event . type = = SDL_MOUSEBUTTONDOWN & & ! SDL_GetRelativeMouseMode ( ) ) {
2019-07-08 21:36:55 +00:00
SDL_SetRelativeMouseMode ( SDL_TRUE ) ;
2019-08-02 20:38:05 +00:00
window_titler . set_mouse_is_captured ( true ) ;
2019-07-08 21:36:55 +00:00
break ;
}
2020-01-25 19:48:00 +00:00
2019-07-08 21:36:55 +00:00
const auto mouse_machine = machine - > mouse_machine ( ) ;
if ( mouse_machine ) {
mouse_machine - > get_mouse ( ) . set_button_pressed (
event . button . button % mouse_machine - > get_mouse ( ) . get_number_of_buttons ( ) ,
2019-07-09 20:32:38 +00:00
event . type = = SDL_MOUSEBUTTONDOWN ) ;
2019-07-08 21:36:55 +00:00
}
} break ;
case SDL_MOUSEMOTION : {
if ( SDL_GetRelativeMouseMode ( ) ) {
const auto mouse_machine = machine - > mouse_machine ( ) ;
if ( mouse_machine ) {
mouse_machine - > get_mouse ( ) . move ( event . motion . xrel , event . motion . yrel ) ;
}
}
} break ;
2017-11-13 00:47:18 +00:00
default : break ;
2017-11-04 23:36:46 +00:00
}
}
2017-11-05 17:49:28 +00:00
2020-03-06 02:56:26 +00:00
std : : vector < KeyPress > matched_keypresses ;
2020-03-10 03:10:39 +00:00
if ( logical_keyboard ) {
// Look for potential keypress merges; SDL doesn't in any capacity guarantee that keypresses that produce
// symbols will be delivered with the same timestamp. So look for any pairs of recorded kepresses that are
// close together temporally and otherwise seem to match.
if ( keypresses . size ( ) ) {
auto next_keypress = keypresses . begin ( ) ;
while ( next_keypress ! = keypresses . end ( ) ) {
auto keypress = next_keypress ;
2020-03-06 02:56:26 +00:00
+ + next_keypress ;
2020-03-10 03:10:39 +00:00
// If the two appear to pair off, push a combination and advance twice.
// Otherwise, keep just the first and advance once.
if (
next_keypress ! = keypresses . end ( ) & &
keypress - > timestamp > = next_keypress - > timestamp - 5 & &
keypress - > is_down & & next_keypress - > is_down & &
! keypress - > input . size ( ) ! = ! next_keypress - > input . size ( ) & &
( keypress - > scancode ! = SDL_SCANCODE_UNKNOWN ) ! = ( next_keypress - > scancode ! = SDL_SCANCODE_UNKNOWN ) ) {
KeyPress combined_keypress ;
if ( keypress - > scancode ! = SDL_SCANCODE_UNKNOWN ) {
combined_keypress . scancode = keypress - > scancode ;
combined_keypress . keycode = keypress - > keycode ;
combined_keypress . input = std : : move ( next_keypress - > input ) ;
} else {
combined_keypress . scancode = next_keypress - > scancode ;
combined_keypress . keycode = next_keypress - > keycode ;
combined_keypress . input = std : : move ( keypress - > input ) ;
} ;
+ + next_keypress ;
matched_keypresses . push_back ( combined_keypress ) ;
} else {
matched_keypresses . push_back ( * keypress ) ;
}
2020-03-06 02:56:26 +00:00
}
}
}
2020-03-04 02:30:30 +00:00
// Handle accumulated key states.
2020-04-02 03:19:34 +00:00
const auto joystick_machine = machine - > joystick_machine ( ) ;
2020-03-10 03:10:39 +00:00
for ( const auto & keypress : logical_keyboard ? matched_keypresses : keypresses ) {
2020-03-04 03:58:15 +00:00
// Try to set this key on the keyboard first, if there is one.
if ( keyboard_machine ) {
Inputs : : Keyboard : : Key key = Inputs : : Keyboard : : Key : : Space ;
2020-03-10 03:10:39 +00:00
if ( KeyboardKeyForSDLScancode ( keypress . scancode , key ) ) {
// In principle there's no need for a conditional here; in practice logical_keyboard mode
// is sufficiently untested on SDL, and somewhat too reliant on empirical timestamp behaviour,
// for it to be trustworthy enough otherwise to expose.
if ( logical_keyboard ) {
2023-12-28 20:05:55 +00:00
if ( keyboard_machine - > apply_key ( key , keypress . input . size ( ) ? keypress . input [ 0 ] : 0 , keypress . is_down , keypress . repeat , logical_keyboard ) ) {
2020-03-10 03:10:39 +00:00
continue ;
}
} else {
// This is a slightly terrible way of obtaining a symbol for the key, e.g. for letters it will always return
// the capital letter version, at least empirically. But it'll have to do for now.
const char * key_name = SDL_GetKeyName ( keypress . keycode ) ;
2023-12-28 20:05:55 +00:00
if ( keyboard_machine - > get_keyboard ( ) . set_key_pressed ( key , ( strlen ( key_name ) = = 1 ) ? key_name [ 0 ] : 0 , keypress . is_down , keypress . repeat ) ) {
2020-03-10 03:10:39 +00:00
continue ;
}
}
2020-03-04 03:58:15 +00:00
}
}
// Having failed that, try converting it into a joystick action.
if ( joystick_machine ) {
auto & joysticks = joystick_machine - > get_joysticks ( ) ;
if ( ! joysticks . empty ( ) ) {
2020-03-06 02:56:26 +00:00
const bool is_pressed = keypress . is_down ;
switch ( keypress . scancode ) {
2020-03-04 03:58:15 +00:00
case SDL_SCANCODE_LEFT : joysticks [ 0 ] - > set_input ( Inputs : : Joystick : : Input : : Left , is_pressed ) ; break ;
case SDL_SCANCODE_RIGHT : joysticks [ 0 ] - > set_input ( Inputs : : Joystick : : Input : : Right , is_pressed ) ; break ;
case SDL_SCANCODE_UP : joysticks [ 0 ] - > set_input ( Inputs : : Joystick : : Input : : Up , is_pressed ) ; break ;
case SDL_SCANCODE_DOWN : joysticks [ 0 ] - > set_input ( Inputs : : Joystick : : Input : : Down , is_pressed ) ; break ;
case SDL_SCANCODE_SPACE : joysticks [ 0 ] - > set_input ( Inputs : : Joystick : : Input : : Fire , is_pressed ) ; break ;
case SDL_SCANCODE_A : joysticks [ 0 ] - > set_input ( Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Fire , 0 ) , is_pressed ) ; break ;
case SDL_SCANCODE_S : joysticks [ 0 ] - > set_input ( Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Fire , 1 ) , is_pressed ) ; break ;
case SDL_SCANCODE_D : joysticks [ 0 ] - > set_input ( Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Fire , 2 ) , is_pressed ) ; break ;
case SDL_SCANCODE_F : joysticks [ 0 ] - > set_input ( Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Fire , 3 ) , is_pressed ) ; break ;
default : {
2020-03-06 02:56:26 +00:00
if ( keypress . input . size ( ) ) {
joysticks [ 0 ] - > set_input ( Inputs : : Joystick : : Input ( keypress . input [ 0 ] ) , is_pressed ) ;
2020-03-04 03:58:15 +00:00
}
} break ;
}
}
}
2020-03-04 02:30:30 +00:00
}
keypresses . clear ( ) ;
2018-06-15 02:37:44 +00:00
// Push new joystick state, if any.
if ( joystick_machine ) {
2019-11-09 23:19:05 +00:00
auto & machine_joysticks = joystick_machine - > get_joysticks ( ) ;
2018-06-15 02:37:44 +00:00
for ( size_t c = 0 ; c < joysticks . size ( ) ; + + c ) {
size_t target = c % machine_joysticks . size ( ) ;
2018-06-17 02:25:46 +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 ( ! joysticks [ c ] . hat_values ( ) ) {
2020-05-10 03:00:39 +00:00
const float x_axis = float ( SDL_JoystickGetAxis ( joysticks [ c ] . get ( ) , 0 ) + 32768 ) / 65535.0f ;
const float y_axis = float ( SDL_JoystickGetAxis ( joysticks [ c ] . get ( ) , 1 ) + 32768 ) / 65535.0f ;
2018-06-17 02:25:46 +00:00
machine_joysticks [ target ] - > set_input ( Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Type : : Horizontal ) , x_axis ) ;
machine_joysticks [ target ] - > set_input ( Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Type : : Vertical ) , y_axis ) ;
}
// Forward hats as directions; hats always override analogue inputs.
const int number_of_hats = SDL_JoystickNumHats ( joysticks [ c ] . get ( ) ) ;
for ( int hat = 0 ; hat < number_of_hats ; + + hat ) {
const Uint8 hat_value = SDL_JoystickGetHat ( joysticks [ c ] . get ( ) , hat ) ;
const Uint8 changes = hat_value ^ joysticks [ c ] . last_hat_value ( hat ) ;
joysticks [ c ] . last_hat_value ( hat ) = hat_value ;
if ( changes & SDL_HAT_UP ) {
machine_joysticks [ target ] - > set_input ( Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Type : : Up ) , ! ! ( hat_value & SDL_HAT_UP ) ) ;
}
if ( changes & SDL_HAT_DOWN ) {
machine_joysticks [ target ] - > set_input ( Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Type : : Down ) , ! ! ( hat_value & SDL_HAT_DOWN ) ) ;
}
if ( changes & SDL_HAT_LEFT ) {
machine_joysticks [ target ] - > set_input ( Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Type : : Left ) , ! ! ( hat_value & SDL_HAT_LEFT ) ) ;
}
if ( changes & SDL_HAT_RIGHT ) {
machine_joysticks [ target ] - > set_input ( Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Type : : Right ) , ! ! ( hat_value & SDL_HAT_RIGHT ) ) ;
}
}
2018-06-15 02:37:44 +00:00
2018-06-17 02:25:46 +00:00
// Forward all fire buttons, retaining their original indices.
2018-06-15 02:37:44 +00:00
const int number_of_buttons = SDL_JoystickNumButtons ( joysticks [ c ] . get ( ) ) ;
for ( int button = 0 ; button < number_of_buttons ; + + button ) {
machine_joysticks [ target ] - > set_input (
Inputs : : Joystick : : Input ( Inputs : : Joystick : : Input : : Type : : Fire , button ) ,
SDL_JoystickGetButton ( joysticks [ c ] . get ( ) , button ) ? true : false ) ;
}
}
}
2017-11-04 23:36:46 +00:00
}
2017-11-05 17:49:28 +00:00
// Clean up.
2020-02-10 02:44:55 +00:00
machine_runner . stop ( ) ; // Ensure no further updates will occur.
2018-06-15 02:37:44 +00:00
joysticks . clear ( ) ;
2017-11-04 23:36:46 +00:00
SDL_DestroyWindow ( window ) ;
SDL_Quit ( ) ;
2019-07-23 01:14:21 +00:00
return EXIT_SUCCESS ;
2017-11-04 23:36:46 +00:00
}