diff --git a/fuse.c b/fuse.c index 9db40d68..933e2434 100644 --- a/fuse.c +++ b/fuse.c @@ -171,9 +171,15 @@ static int parse_nonoption_args( int argc, char **argv, int first_arg, static int do_start_files( start_files_t *start_files ); #ifdef UI_WIN32 +/* The Win32 UI supplies WinMain(), which calls this */ int fuse_main(int argc, char **argv) -#else +#elif defined UI_COCOA +/* fusepb/main.m supplies main() and Emulator.m calls fuse_init() itself, so + this entry point goes unused; it is renamed only to avoid colliding with + the one in main.m */ int old_main(int argc, char **argv) +#else +int main(int argc, char **argv) #endif { int r = 0; diff --git a/fusepb/Makefile b/fusepb/Makefile index 60323610..1086688f 100644 --- a/fusepb/Makefile +++ b/fusepb/Makefile @@ -9,7 +9,7 @@ settings.h: ../settings.dat settings-header.pl cd .. && perl -Iperl fusepb/settings-header.pl settings.dat > fusepb/settings.h settings.m: ../settings.dat ../settings.pl - cd .. && perl -Iperl settings.pl settings.dat > fusepb/settings.m + cd .. && perl -Iperl settings.pl --cocoa settings.dat > fusepb/settings.m options_cocoa.h: ../perl/cpp-perl.pl config.h ../ui/cocoa/options-cocoa-header.pl ../ui/options.dat ../perl/Fuse.pm ../perl/Fuse/Dialog.pm cd .. && perl perl/cpp-perl.pl fusepb/config.h ui/options.dat | perl -Iperl ui/cocoa/options-cocoa-header.pl - public > fusepb/$@.tmp && mv fusepb/$@.tmp fusepb/$@ diff --git a/fusepb/config.h b/fusepb/config.h index 483aa62f..799fc1e2 100644 --- a/fusepb/config.h +++ b/fusepb/config.h @@ -151,6 +151,9 @@ /* Defined if svgalib UI in use */ /* #undef UI_SVGA */ +/* Defined if the Cocoa (macOS) UI is in use */ +#define UI_COCOA 1 + /* Defined if Win32 UI in use */ /* #undef UI_WIN32 */ diff --git a/machine.c b/machine.c index 11175768..135f6c5c 100644 --- a/machine.c +++ b/machine.c @@ -493,10 +493,12 @@ machine_register_startup( void ) NULL, machine_end ); } -#ifndef __APPLE__ +/* The Cocoa app implements this in fusepb/FuseMenus.m; every other build, + macOS autotools included, needs the stub. */ +#ifndef UI_COCOA void SetEmulationHz( float hz ) { (void)hz; } -#endif +#endif /* #ifndef UI_COCOA */ diff --git a/menu.c b/menu.c index e13e7980..9ed635ff 100644 --- a/menu.c +++ b/menu.c @@ -454,7 +454,6 @@ MENU_CALLBACK( menu_machine_profiler_stop ) #if !defined( UI_WIN32 ) MENU_CALLBACK( menu_machine_debuglog ) { - (void)action; } #endif /* !defined( UI_WIN32 ) */ diff --git a/menu_data.dat b/menu_data.dat index b9a88e5b..4b57bb08 100644 --- a/menu_data.dat +++ b/menu_data.dat @@ -520,6 +520,8 @@ Media/IDE/ZXMMC/_Commit, Item,, menu_media_ide_commit,, 9 Media/IDE/ZXMMC/_Eject, Item,, menu_media_ide_eject,, 9 _Help, Branch +#ifdef UI_WIN32 Help/_Check for Updates..., Item,, menu_help_check_for_updates +#endif /* #ifdef UI_WIN32 */ Help/_Keyboard..., Item Help/_About..., Item diff --git a/peripherals/Makefile.am b/peripherals/Makefile.am index 7428ba9e..e2ab6a8d 100644 --- a/peripherals/Makefile.am +++ b/peripherals/Makefile.am @@ -65,6 +65,14 @@ if BUILD_SPECCYBOOT fuse_SOURCES += peripherals/nic/enc28j60.c endif +# The xfs core (RAM-backed filesystem engine) is always built: the GDB vfile +# debugger feature, which is compiled unconditionally, depends on it. Only the +# network engines (xfs_https.c) and their HTTP/TLS/SSH stack are Spectranet-only. +fuse_SOURCES += \ + peripherals/fs/xfs.c \ + peripherals/fs/xfs_worker.c \ + peripherals/fs/xfs_fs.c + if BUILD_SPECTRANET fuse_SOURCES += \ peripherals/flash/am29f010.c \ @@ -84,9 +92,6 @@ fuse_SOURCES += \ peripherals/nic/engines/parson.c \ peripherals/nic/spectranext_controller.c \ peripherals/nic/spectranext_stdout.c \ - peripherals/fs/xfs.c \ - peripherals/fs/xfs_worker.c \ - peripherals/fs/xfs_fs.c \ peripherals/fs/xfs_https.c \ peripherals/http/httpc.c \ peripherals/http/http_sck.c \ diff --git a/peripherals/fs/xfs.c b/peripherals/fs/xfs.c index 44ec9150..126b233f 100644 --- a/peripherals/fs/xfs.c +++ b/peripherals/fs/xfs.c @@ -105,6 +105,9 @@ void xfs_handle_mount(volatile struct xfs_registers_t* registers) return; } } +#ifdef BUILD_SPECTRANET + /* The http/https engines live in xfs_https.c, which is built only with + Spectranet as it needs the HTTP client and the mbedTLS stack. */ else if (strcmp(protocol, "https") == 0) { XFS_DEBUG("xfs: mount https hostname='%s' path='%s' mount_point=%d\n", hostname, path, mount_point); @@ -115,6 +118,7 @@ void xfs_handle_mount(volatile struct xfs_registers_t* registers) XFS_DEBUG("xfs: mount http hostname='%s' path='%s' mount_point=%d\n", hostname, path, mount_point); engine = &http_engine; } +#endif /* #ifdef BUILD_SPECTRANET */ else { XFS_DEBUG("xfs: mount failed: unknown protocol '%s' mount_point=%d\n", protocol, mount_point); diff --git a/rzx.h b/rzx.h index 0bfa5326..fad1976f 100644 --- a/rzx.h +++ b/rzx.h @@ -24,6 +24,10 @@ #ifndef FUSE_RZX_H #define FUSE_RZX_H +#ifdef HAVE_LIB_GLIB +#include +#endif /* #ifdef HAVE_LIB_GLIB */ + #include "libspectrum.h" /* The offset used to get the count of instructions from the R register */ diff --git a/settings.pl b/settings.pl index 63b16406..73cb0f8d 100644 --- a/settings.pl +++ b/settings.pl @@ -1,6 +1,7 @@ #!/usr/bin/perl -w -# settings.pl: generate settings.c from settings.dat +# settings.pl: generate the configuration-settings implementation from +# settings.dat # Copyright (c) 2002-2018 Philip Kendall, BogDan Vatra, Alistair Cree # This program is free software; you can redistribute it and/or modify @@ -21,12 +22,30 @@ # E-mail: philip-fuse@shadowmagic.org.uk +# This generator has two modes. With --cocoa it emits the Objective-C +# implementation used by the Cocoa (macOS) app, backed by NSUserDefaults. +# Without it, it emits portable C backed by libxml2 (with an INI fallback), +# used by the autotools builds (SDL, GTK, Xlib, framebuffer, ...). +# +# Everything that depends on settings.dat alone rather than on the storage +# backend is emitted once and shared: the settings_default table, settings_init, +# the getopt_long option set, settings_copy, settings_get_rom_setting and +# settings_set_string. Only the config file I/O, the string ownership rules +# and the Cocoa-only ROM array bridging are emitted per mode. + use strict; use Fuse; sub hashline ($) { '#line ', $_[0] + 1, '"', __FILE__, "\"\n" } +my $cocoa = 0; +my @files; +foreach( @ARGV ) { + if( $_ eq '--cocoa' ) { $cocoa = 1; } else { push @files, $_; } +} +@ARGV = @files; + my %options; my %fileAssoc = ( 'snapshot' => 1, 'tape_file' => 1, 'record_file' => 1, @@ -63,10 +82,19 @@ configfile => $configfile, funcn => $funcn }; } -print Fuse::GPL( 'settings.m: Handling configuration settings', - '2002 Philip Kendall, Fredrick Meunier' ); +# Each emitter below writes one chunk of the generated implementation. Those +# whose output depends on the storage backend dispatch on $cocoa internally so +# that the driver at the foot of this file stays a flat sequence. -print << 'CODE'; +sub emit_gpl { + my $ext = $cocoa ? 'm' : 'c'; + print Fuse::GPL( "settings.$ext: Handling configuration settings", + '2002 Philip Kendall, Fredrick Meunier' ); +} + +sub emit_prologue { + if( $cocoa ) { + print << 'CODE'; /* This file is autogenerated from settings.dat by settings.pl. Do not edit unless you know what will happen! */ @@ -109,28 +137,113 @@ /* The default settings of options, etc */ settings_info settings_default = { CODE + } else { + print hashline( __LINE__ ), << 'CODE'; + +/* This file is autogenerated from settings.dat by settings.pl. + Do not edit unless you know what will happen! */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_GETOPT_LONG /* Did our libc include getopt_long? */ +#include +#elif defined AMIGA || defined __MORPHOS__ /* #ifdef HAVE_GETOPT_LONG */ +/* The platform uses GNU getopt, but not getopt_long, so we get + symbol clashes on this platform. Just use getopt */ +#else /* #ifdef HAVE_GETOPT_LONG */ +#include "compat.h" /* If not, use ours */ +#endif /* #ifdef HAVE_GETOPT_LONG */ + +#ifdef HAVE_LIB_XML2 +#include +#include +#endif /* #ifdef HAVE_LIB_XML2 */ + +#include "fuse.h" +#include "infrastructure/startup_manager.h" +#include "machine.h" +#include "settings.h" +#include "spectrum.h" +#include "ui/ui.h" +#include "utils.h" + +/* The name of our configuration file */ +#ifdef WIN32 +#define CONFIG_FILE_NAME "fuse.cfg" +#define FALLBACK_CONFIG_FILE_NAME CONFIG_FILE_NAME +#else /* #ifdef WIN32 */ +#define CONFIG_FILE_NAME "fuserc" +#define FALLBACK_CONFIG_FILE_NAME ".fuserc" +#endif /* #ifdef WIN32 */ + +/* The current settings of options, etc */ +settings_info settings_current; + +/* The default settings of options, etc */ +settings_info settings_default = { +CODE + } +} +# The settings_default initialiser. Field order must match the struct emitted +# by settings-header.pl, so the trailing three members are spelled out here. +sub emit_defaults_table { foreach my $name ( sort keys %options ) { - next if $options{$name}->{type} eq 'null'; - next if $options{$name}->{type} eq 'nsarray'; - if( $options{$name}->{type} eq 'string' ) { + my $type = $options{$name}->{type}; + next if $type eq 'null'; + next if $type eq 'nsarray'; + next if $type eq 'nsdictionary'; + if( $type eq 'string' ) { print " /* $name */ (char *)$options{$name}->{default},\n"; - } else { + } else { print " /* $name */ $options{$name}->{default},\n"; } } - print " /* cocoa */ NULL,\n"; -print << 'CODE'; + print " /* cocoa */ NULL,\n" if $cocoa; + + print << 'CODE'; /* show_help */ 0, /* show_version */ 0, /* no_banner */ 0, }; +CODE +} + +sub emit_forward_declarations { + print "\n"; + + unless( $cocoa ) { + print << 'CODE'; +static int read_config_file( settings_info *settings ); + +#ifdef HAVE_LIB_XML2 +static int parse_xml( xmlDocPtr doc, settings_info *settings ); +#else /* #ifdef HAVE_LIB_XML2 */ +static int parse_ini( utils_file *file, settings_info *settings ); +#endif /* #ifdef HAVE_LIB_XML2 */ +CODE + print "\n"; + } + print << 'CODE'; static int settings_command_line( settings_info *settings, int *first_arg, int argc, char **argv ); static void settings_copy_internal( settings_info *dest, settings_info *src ); +CODE +} + +sub emit_settings_init { + print << 'CODE'; /* Called on emulator startup */ int @@ -138,6 +251,11 @@ { int error; +CODE + + print " settings_defaults( &settings_current );\n\n" unless $cocoa; + + print << 'CODE'; error = read_config_file( &settings_current ); if( error ) return error; @@ -146,7 +264,24 @@ return 0; } +CODE +} +sub emit_settings_defaults { + print "\n"; + + unless( $cocoa ) { + print << 'CODE'; +/* Fill the settings structure with sensible defaults */ +void settings_defaults( settings_info *settings ) +{ + settings_copy_internal( settings, &settings_default ); +} +CODE + return; + } + + print << 'CODE'; /* Fill the settings structure with sensible defaults */ void settings_defaults( settings_info *settings ) { @@ -202,6 +337,15 @@ [[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:defaultValues]; } +CODE +} + +sub emit_config_io { + $cocoa ? emit_config_io_cocoa() : emit_config_io_portable(); +} + +sub emit_config_io_cocoa { + print << 'CODE'; /* Read options from the config file */ @@ -283,7 +427,7 @@ } } -print << 'CODE'; + print << 'CODE'; return 0; } @@ -342,6 +486,463 @@ return 0; } +CODE +} + +sub emit_config_io_portable { + print hashline( __LINE__ ), << 'CODE'; + +#ifdef HAVE_LIB_XML2 + +/* Read options from the config file (if libxml2 is available) */ + +static int +read_config_file( settings_info *settings ) +{ + const char *cfgdir; char path[ PATH_MAX ]; + settings_info new_settings; + + xmlDocPtr doc; + + cfgdir = compat_get_config_path(); if( !cfgdir ) return 1; + + snprintf( path, PATH_MAX, "%s/%s", cfgdir, CONFIG_FILE_NAME ); + + /* See if the file exists; if doesn't, it's not a problem */ + if( !compat_file_exists( path ) ) { + /* Look for the config file in the fallback location, if there is one */ + const char *fallbackdir = compat_get_fallback_config_path(); + if( !fallbackdir ) return 0; + snprintf( path, PATH_MAX, "%s/%s", fallbackdir, FALLBACK_CONFIG_FILE_NAME ); + if( !compat_file_exists( path ) ) return 0; + } + + memset( &new_settings, 0, sizeof( new_settings ) ); + settings_copy_internal( &new_settings, settings ); + + doc = xmlReadFile( path, NULL, 0 ); + if( !doc ) { + settings_free( &new_settings ); + ui_error( UI_ERROR_WARNING, + "ignoring unrecognised config file '%s'; using defaults", path ); + return 0; + } + + if( parse_xml( doc, &new_settings ) ) { + xmlFreeDoc( doc ); + settings_free( &new_settings ); + ui_error( UI_ERROR_WARNING, + "ignoring unrecognised config file '%s'; using defaults", path ); + return 0; + } + + xmlFreeDoc( doc ); + + settings_copy_internal( settings, &new_settings ); + settings_free( &new_settings ); + + return 0; +} + +static int +parse_xml( xmlDocPtr doc, settings_info *settings ) +{ + xmlNodePtr node; + xmlChar *xmlstring; + + node = xmlDocGetRootElement( doc ); + if( xmlStrcmp( node->name, (const xmlChar*)"settings" ) ) { + ui_error( UI_ERROR_ERROR, "config file's root node is not 'settings'" ); + return 1; + } + + node = node->xmlChildrenNode; + while( node ) { + +CODE + +foreach my $name ( sort keys %options ) { + + my $type = $options{$name}->{type}; + + next if $command_line_only{$name}; + + if( $type eq 'boolean' or $type eq 'numeric' ) { + + print << "CODE"; + if( !strcmp( (const char*)node->name, "$options{$name}->{configfile}" ) ) { + xmlstring = xmlNodeListGetString( doc, node->xmlChildrenNode, 1 ); + if( xmlstring ) { + settings->$name = atoi( (char*)xmlstring ); + xmlFree( xmlstring ); + } + } else +CODE + + } elsif( $type eq 'string' ) { + + print << "CODE"; + if( !strcmp( (const char*)node->name, "$options{$name}->{configfile}" ) ) { + xmlstring = xmlNodeListGetString( doc, node->xmlChildrenNode, 1 ); + if( xmlstring ) { + libspectrum_free( settings->$name ); + settings->$name = utils_safe_strdup( (char*)xmlstring ); + xmlFree( xmlstring ); + } + } else +CODE + + } elsif( $type eq 'null' or $type eq 'nsarray' or $type eq 'nsdictionary' ) { + + print << "CODE"; + if( !strcmp( (const char*)node->name, "$options{$name}->{configfile}" ) ) { + /* Do nothing */ + } else +CODE + + } else { + die "Unknown setting type `$type'"; + } +} + +print hashline( __LINE__ ), << 'CODE'; + if( !strcmp( (const char*)node->name, "text" ) ) { + /* Do nothing */ + } else { + ui_error( UI_ERROR_WARNING, "Unknown setting '%s' in config file", + node->name ); + } + + node = node->next; + } + + return 0; +} + +int +settings_write_config( settings_info *settings ) +{ + const char *cfgdir; char path[ PATH_MAX ], buffer[80]; + + xmlDocPtr doc; xmlNodePtr root; + + cfgdir = compat_get_config_path(); if( !cfgdir ) return 1; + + snprintf( path, PATH_MAX, "%s/%s", cfgdir, CONFIG_FILE_NAME ); + + /* Create the XML document */ + doc = xmlNewDoc( (const xmlChar*)"1.0" ); + + root = xmlNewNode( NULL, (const xmlChar*)"settings" ); + xmlDocSetRootElement( doc, root ); +CODE + +foreach my $name ( sort keys %options ) { + + my $type = $options{$name}->{type}; + + next if $command_line_only{$name}; + + if( $type eq 'boolean' ) { + + print " xmlNewTextChild( root, NULL, (const xmlChar*)\"$options{$name}->{configfile}\", (const xmlChar*)(settings->$name ? \"1\" : \"0\") );\n"; + + } elsif( $type eq 'string' ) { + print << "CODE"; + if( settings->$name ) + xmlNewTextChild( root, NULL, (const xmlChar*)"$options{$name}->{configfile}", (const xmlChar*)settings->$name ); +CODE + } elsif( $type eq 'numeric' ) { + print << "CODE"; + snprintf( buffer, 80, "%d", settings->$name ); + xmlNewTextChild( root, NULL, (const xmlChar*)"$options{$name}->{configfile}", (const xmlChar*)buffer ); +CODE + } elsif( $type eq 'null' or $type eq 'nsarray' or $type eq 'nsdictionary' ) { + # Do nothing + } else { + die "Unknown setting type `$type'"; + } +} + + print hashline( __LINE__ ), << 'CODE'; + + xmlSaveFormatFile( path, doc, 1 ); + + xmlFreeDoc( doc ); + + return 0; +} + +#else /* #ifdef HAVE_LIB_XML2 */ + +/* Read options from the config file as ini file (if libxml2 is not available) */ + +static int +read_config_file( settings_info *settings ) +{ + const char *cfgdir; char path[ PATH_MAX ]; + int error; + settings_info new_settings; + + utils_file file; + + cfgdir = compat_get_config_path(); if( !cfgdir ) return 1; + + snprintf( path, PATH_MAX, "%s/%s", cfgdir, CONFIG_FILE_NAME ); + + /* See if the file exists; if doesn't, it's not a problem */ + if( !compat_file_exists( path ) ) { + /* Look for the config file in the fallback location, if there is one */ + const char *fallbackdir = compat_get_fallback_config_path(); + if( !fallbackdir ) return 0; + snprintf( path, PATH_MAX, "%s/%s", fallbackdir, FALLBACK_CONFIG_FILE_NAME ); + if( !compat_file_exists( path ) ) return 0; + } + + memset( &new_settings, 0, sizeof( new_settings ) ); + settings_copy_internal( &new_settings, settings ); + + error = utils_read_file( path, &file ); + if( error ) { + settings_free( &new_settings ); + ui_error( UI_ERROR_WARNING, + "ignoring unrecognised config file '%s'; using defaults", path ); + return 0; + } + + if( parse_ini( &file, &new_settings ) ) { + utils_close_file( &file ); + settings_free( &new_settings ); + ui_error( UI_ERROR_WARNING, + "ignoring unrecognised config file '%s'; using defaults", path ); + return 0; + } + + utils_close_file( &file ); + + settings_copy_internal( settings, &new_settings ); + settings_free( &new_settings ); + + return 0; +} + +static int +settings_var( settings_info *settings, unsigned char *name, unsigned char *last, + int **val_int, char ***val_char, unsigned char **next ) +{ + unsigned char* cpos; + size_t n; + + *val_int = NULL; + *val_char = NULL; + + *next = name; + while( name < last && ( *name == ' ' || *name == '\t' || *name == '\r' || + *name == '\n' ) ) { + *next = ++name; /* seek to first char */ + } + cpos = name; + + while( cpos < last && ( *cpos != '=' && *cpos != ' ' && *cpos != '\t' && + *cpos != '\r' && *cpos != '\n' ) ) cpos++; + *next = cpos; + n = cpos - name; /* length of name */ + + while( *next < last && **next != '=' ) { /* search for '=' */ + if( **next != ' ' && **next != '\t' && **next != '\r' && **next != '\n' ) + return 1; /* error in value */ + (*next)++; + } + if( *next < last) (*next)++; /* set after '=' */ +/* ui_error( UI_ERROR_WARNING, "Config: (%5s): ", name ); */ + +CODE +foreach my $name ( sort keys %options ) { + next if $command_line_only{$name}; + my $type = $options{$name}->{type}; + my $len = length $options{$name}->{configfile}; + + print << "CODE"; + if( n == $len && !strncmp( (const char *)name, "$options{$name}->{configfile}", n ) ) { +CODE + print " *val_int = \&settings->$name;\n" if( $type eq 'boolean' or $type eq 'numeric' ); + print " *val_char = \&settings->$name;\n" if( $type eq 'string' ); + print " /* Recognised but not stored */\n" + if( $type eq 'null' or $type eq 'nsarray' or $type eq 'nsdictionary' ); + print << "CODE"; + return 0; + } +CODE +} + print << "CODE"; + return 1; +} + +static int +parse_ini( utils_file *file, settings_info *settings ) +{ + unsigned char *cpos, *cpos_new; + int *val_int; + char **val_char; + + cpos = file->buffer; + + /* Read until the end of file */ + while( cpos < file->buffer + file->length ) { + if( settings_var( settings, cpos, file->buffer + file->length, &val_int, + &val_char, &cpos_new ) ) { + /* error in name or something else ... The buffer is not NUL terminated, + so bound the report to the offending line. */ + unsigned char *eol = cpos; + while( eol < file->buffer + file->length && + *eol != '\\r' && *eol != '\\n' ) eol++; + ui_error( UI_ERROR_WARNING, + "Unknown and/or invalid setting '%.*s' in config file", + (int)( eol - cpos ), (const char *)cpos ); + cpos = cpos_new + 1; + continue; + } + cpos = cpos_new; + if( val_int ) { + *val_int = atoi( (char *)cpos ); + while( cpos < file->buffer + file->length && + ( *cpos != '\\0' && *cpos != '\\r' && *cpos != '\\n' ) ) cpos++; + } else if( val_char ) { + char *value = (char *)cpos; + size_t n = 0; + while( cpos < file->buffer + file->length && + ( *cpos != '\\0' && *cpos != '\\r' && *cpos != '\\n' ) ) cpos++; + n = (char *)cpos - value; + if( n > 0 ) { + if( *val_char != NULL ) { + libspectrum_free( *val_char ); + *val_char = NULL; + } + *val_char = libspectrum_new( char, n + 1 ); + (*val_char)[n] = '\\0'; + memcpy( *val_char, value, n ); + } + } else { + /* Recognised, but this build does not store it: skip the value, or + the parser resumes mid-value and loses the next setting. */ + while( cpos < file->buffer + file->length && + ( *cpos != '\\0' && *cpos != '\\r' && *cpos != '\\n' ) ) cpos++; + } + /* skip 'new line' like chars */ + while( ( cpos < ( file->buffer + file->length ) ) && + ( *cpos == '\\r' || *cpos == '\\n' ) ) cpos++; + +CODE +print hashline( __LINE__ ), << 'CODE'; + } + + return 0; +} + +static int +settings_file_write( compat_fd fd, const char *buffer, size_t length ) +{ + return compat_file_write( fd, (const unsigned char *)buffer, length ); +} + +static int +settings_string_write( compat_fd doc, const char* name, const char* config ) +{ + if( config != NULL && + ( settings_file_write( doc, name, strlen( name ) ) || + settings_file_write( doc, "=", 1 ) || + settings_file_write( doc, config, strlen( config ) ) || + settings_file_write( doc, FUSE_EOL, strlen( FUSE_EOL ) ) ) ) + return 1; + return 0; +} + +static int +settings_boolean_write( compat_fd doc, const char* name, int config ) +{ + return settings_string_write( doc, name, config ? "1" : "0" ); +} + +static int +settings_numeric_write( compat_fd doc, const char* name, int config ) +{ + char buffer[80]; + snprintf( buffer, sizeof( buffer ), "%d", config ); + return settings_string_write( doc, name, buffer ); +} + +int +settings_write_config( settings_info *settings ) +{ + const char *cfgdir; char path[ PATH_MAX ]; + + compat_fd doc; + + cfgdir = compat_get_config_path(); if( !cfgdir ) return 1; + + snprintf( path, PATH_MAX, "%s/%s", cfgdir, CONFIG_FILE_NAME ); + + doc = compat_file_open( path, 1 ); + if( doc == COMPAT_FILE_OPEN_FAILED ) { + ui_error( UI_ERROR_ERROR, "couldn't open `%s' for writing: %s\n", + path, strerror( errno ) ); + return 1; + } + +CODE + +foreach my $name ( sort keys %options ) { + + my $type = $options{$name}->{type}; + + next if $command_line_only{$name}; + + if( $type eq 'boolean' ) { + + print << "CODE"; + if( settings_boolean_write( doc, "$options{$name}->{configfile}", + settings->$name ) ) + goto error; +CODE + + } elsif( $type eq 'string' ) { + print << "CODE"; + if( settings_string_write( doc, "$options{$name}->{configfile}", + settings->$name ) ) + goto error; +CODE + + } elsif( $type eq 'numeric' ) { + print << "CODE"; + if( settings_numeric_write( doc, "$options{$name}->{configfile}", + settings->$name ) ) + goto error; +CODE + + } elsif( $type eq 'null' or $type eq 'nsarray' or $type eq 'nsdictionary' ) { + # Do nothing + } else { + die "Unknown setting type `$type'"; + } +} + + print hashline( __LINE__ ), << 'CODE'; + + compat_file_close( doc ); + + return 0; +error: + compat_file_close( doc ); + + return 1; +} + +#endif /* #ifdef HAVE_LIB_XML2 */ +CODE +} + +sub emit_command_line { + print hashline( __LINE__ ), << 'CODE'; /* Read options from the command line */ static int @@ -359,7 +960,10 @@ CODE -my $fake_short_option = 256; +# getopt_long values above 255 cannot clash with a short option character. +# 256 is reserved for --no-banner; generated options take 257 upwards. +my $no_banner_short = 256; +my $fake_short_option = 257; foreach my $name ( sort keys %options ) { @@ -389,11 +993,15 @@ } } -print << 'CODE'; +print hashline( __LINE__ ), << 'CODE'; { "help", 0, NULL, 'h' }, { "version", 0, NULL, 'V' }, - { "no-banner", 0, NULL, 444 }, +CODE + +print " { \"no-banner\", 0, NULL, $no_banner_short },\n"; + +print hashline( __LINE__ ), << 'CODE'; { 0, 0, 0, 0 } /* End marker: DO NOT REMOVE */ }; @@ -418,7 +1026,7 @@ CODE -$fake_short_option = 256; +$fake_short_option = 257; foreach my $name ( sort keys %options ) { @@ -444,11 +1052,15 @@ } } -print << 'CODE'; +print hashline( __LINE__ ), << 'CODE'; case 'h': settings->show_help = 1; break; case 'V': settings->show_version = 1; break; - case 444: settings->no_banner = 1; break; +CODE + +print " case $no_banner_short: settings->no_banner = 1; break;\n"; + +print hashline( __LINE__ ), << 'CODE'; case ':': case '?': @@ -467,6 +1079,12 @@ return 0; } +CODE +} + +sub emit_copy_internal { + if( $cocoa ) { + print << 'CODE'; /* Copy one settings object to another */ static void @@ -502,15 +1120,49 @@ CODE } } + } else { + print hashline( __LINE__ ), << 'CODE'; + +/* Copy one settings object to another */ +static void +settings_copy_internal( settings_info *dest, settings_info *src ) +{ + settings_free( dest ); + +CODE + +foreach my $name ( sort keys %options ) { + + my $type = $options{$name}->{type}; + + if( $type eq 'boolean' or $type eq 'numeric' ) { + print " dest->$name = src->$name;\n"; + } elsif( $type eq 'string' ) { + print << "CODE"; + dest->$name = NULL; + if( src->$name ) { + dest->$name = utils_safe_strdup( src->$name ); + } +CODE + } +} + } -print << 'CODE'; + print << 'CODE'; +} +CODE } +sub emit_free { + print << 'CODE'; + int settings_free( settings_info *settings ) { CODE + if( $cocoa ) { + foreach my $name ( sort keys %options ) { if( $options{$name}->{type} eq 'string' ) { print " if( settings->$name ) {\n"; @@ -525,13 +1177,32 @@ } } -print << 'CODE'; + print << 'CODE'; if( settings->cocoa ) free( settings->cocoa ); settings->cocoa = NULL; return 0; } +CODE + } else { + +foreach my $name ( sort keys %options ) { + if( $options{$name}->{type} eq 'string' ) { + print " if( settings->$name ) libspectrum_free( settings->$name );\n"; + } +} + + print hashline( __LINE__ ), << 'CODE'; + + return 0; +} +CODE + } +} + +sub emit_copy { + print << 'CODE'; /* Copy one settings object to another */ void settings_copy( settings_info *dest, settings_info *src ) @@ -539,6 +1210,11 @@ settings_defaults( dest ); settings_copy_internal( dest, src ); } +CODE +} + +sub emit_rom_setting { + print << 'CODE'; char ** settings_get_rom_setting( settings_info *settings, size_t which, @@ -608,6 +1284,11 @@ } } } +CODE +} + +sub emit_set_string { + print << 'CODE'; void settings_set_string( char **string_setting, const char *value ) @@ -620,8 +1301,12 @@ *string_setting = utils_safe_strdup( value ); } CODE +} -print << 'CODE'; +# Cocoa bindings expect the per-machine ROM settings as an NSArray of +# dictionaries; these two functions bridge between that and settings_info. +sub emit_rom_arrays { + print << 'CODE'; /* Comparison function to sort the machineroms array */ NSInteger @@ -673,7 +1358,7 @@ print "\n"; } -print << 'CODE'; + print << 'CODE'; /* We assume that we got all machines in the array, this should always be true*/ [machineroms sortUsingFunction:machineroms_compare context:nil]; @@ -682,7 +1367,7 @@ } CODE -print << 'CODE'; + print << 'CODE'; /* Fill the settings structure from the supplied NSMutableArray as above */ void @@ -712,9 +1397,12 @@ print " }\n"; print " }\n"; print "}\n"; -print "\n"; +} + +sub emit_end { + if( $cocoa ) { + print << 'CODE'; -print << 'CODE'; static void settings_end( void ) { @@ -739,6 +1427,28 @@ settings_free( &settings_current ); } +CODE + } else { + print hashline( __LINE__ ), << 'CODE'; + +static void +settings_end( void ) +{ + if( settings_current.autosave_settings ) + settings_write_config( &settings_current ); + + settings_free( &settings_current ); + +#ifdef HAVE_LIB_XML2 + xmlCleanupParser(); +#endif /* #ifdef HAVE_LIB_XML2 */ +} +CODE + } +} + +sub emit_register_startup { + print << 'CODE'; void settings_register_startup( void ) @@ -746,10 +1456,20 @@ /* settings_init not yet managed by the startup manager */ startup_manager_module dependencies[] = { +CODE + + if( $cocoa ) { + print << 'CODE'; /* Fuse for OS X requires that settings_end is called before memory is deallocated as settings need to look up machine names etc */ STARTUP_MANAGER_MODULE_MEMORY, /* STARTUP_MANAGER_MODULE_SETUID, */ +CODE + } else { + print " STARTUP_MANAGER_MODULE_SETUID,\n"; + } + + print << 'CODE'; }; startup_manager_register( STARTUP_MANAGER_MODULE_SETTINGS_END, dependencies, ARRAY_SIZE( dependencies ), NULL, NULL, @@ -757,3 +1477,21 @@ } CODE +} + +emit_gpl(); +emit_prologue(); +emit_defaults_table(); +emit_forward_declarations(); +emit_settings_init(); +emit_settings_defaults(); +emit_config_io(); +emit_command_line(); +emit_copy_internal(); +emit_free(); +emit_copy(); +emit_rom_setting(); +emit_set_string(); +emit_rom_arrays() if $cocoa; +emit_end(); +emit_register_startup(); diff --git a/ui/scaler/scaler.c b/ui/scaler/scaler.c index bd7603f8..6c6daa18 100644 --- a/ui/scaler/scaler.c +++ b/ui/scaler/scaler.c @@ -54,10 +54,14 @@ struct scaler_info { }; -#ifdef _WIN32 -#define SCALER32( name ) name##_32 -#else +/* Every build links scalers32.o (the 32-bit scaler routines) except the Cocoa + app, whose Xcode project compiles only the 16-bit variant. Wire the 32-bit + routines into the table everywhere they exist; the Cocoa build renders at + 16bpp and never uses them. */ +#ifdef UI_COCOA #define SCALER32( name ) NULL +#else +#define SCALER32( name ) name##_32 #endif /* The expander functions */ diff --git a/ui/sdl/sdljoystick.c b/ui/sdl/sdljoystick.c index db2a0592..8fccf5bd 100644 --- a/ui/sdl/sdljoystick.c +++ b/ui/sdl/sdljoystick.c @@ -32,7 +32,7 @@ #include "input.h" #include "sdljoystick.h" #include "settings.h" -#include "ui.h" +#include "ui/ui.h" #include "ui/uijoystick.h" static SDL_Joystick *joystick1 = NULL; diff --git a/unittests/scalerexpandtest.c b/unittests/scalerexpandtest.c index 8197256e..039786ea 100644 --- a/unittests/scalerexpandtest.c +++ b/unittests/scalerexpandtest.c @@ -14,6 +14,7 @@ #include "settings.h" #include "ui/scaler/scaler.h" #include "ui/ui.h" +#include "ui/uidisplay.h" #include "utils.h" settings_info settings_current; @@ -30,6 +31,11 @@ uidisplay_hotswap_gfx_mode( void ) return 0; } +void +uidisplay_set_next_hotswap_reason( uidisplay_hotswap_reason reason GCC_UNUSED ) +{ +} + char* utils_safe_strdup( const char *src GCC_UNUSED ) {