Refactor prefs into JSON publish/subscribe API

- Breaking changes currently only tested on Linux desktop build =P
    - Goal is to eventually eliminate most/many of the disparate getter/setter functions to allow better
      modularity/scaling and platform portability
This commit is contained in:
Aaron Culliney 2016-03-25 22:34:33 -07:00
parent 87ae0f08e0
commit 74a5b74ae3
21 changed files with 796 additions and 502 deletions

11
.apple2
View File

@ -1,11 +0,0 @@
speed = 1.00
altspeed = 4.00
disk path = /usr/local/games/apple2/disks
color = interpolated
video = 1X
volume = 8
caps lock = 1
joystick = joy keypad
system path = /usr/local/games/apple2/rom
pc joystick parms = 128 128 255 1 255 1
keypad joystick parms = 8 1

View File

@ -45,6 +45,7 @@ static unsigned int remainder_buffer_size = 0;
static unsigned long remainder_buffer_size_max = 0;
static unsigned int remainder_buffer_idx = 0;
static long speaker_volume = 0;
static int16_t speaker_amplitude = SPKR_DATA_INIT;
static int16_t speaker_data = 0;
@ -63,6 +64,22 @@ static AudioBuffer_s *speakerBuffer = NULL;
// --------------------------------------------------------------------------------------------------------------------
static void speaker_prefsChanged(const char *domain) {
prefs_parseLongValue(domain, PREF_SPEAKER_VOLUME, &speaker_volume, /*base:*/10); // expected range 0-10
if (speaker_volume < 0) {
speaker_volume = 0;
}
if (speaker_volume > 10) {
speaker_volume = 10;
}
float samplesScale = speaker_volume/10.f;
speaker_amplitude = (int16_t)(SPKR_DATA_INIT * samplesScale);
}
static __attribute__((constructor)) void _init_speaker(void) {
prefs_registerListener(PREF_DOMAIN_AUDIO, &speaker_prefsChanged);
}
/*
* Because disk image loading is slow (AKA close-to-original-//e-speed), we may auto-switch to "fullspeed" for faster
* loading when all the following heuristics hold true:
@ -480,11 +497,6 @@ bool speaker_isActive(void) {
return speaker_recently_active;
}
void speaker_setVolumeZeroToTen(unsigned long goesToTen) {
float samplesScale = goesToTen/10.f;
speaker_amplitude = (int16_t)(SPKR_DATA_INIT * samplesScale);
}
double speaker_cyclesPerSample(void) {
return cycles_per_sample;
}

View File

@ -20,7 +20,6 @@ void speaker_init(void);
void speaker_destroy(void);
void speaker_reset(void);
void speaker_flush(void);
void speaker_setVolumeZeroToTen(unsigned long goesToTen);
bool speaker_isActive(void);
/*

View File

@ -32,9 +32,11 @@
#endif
// custom annotations
#define INPARM
#define OUTPARM
#define INOUT
#define INPARM
#define _NONNULL
#define _NULLABLE
#define OUTPARM
#define PRIVATE
#define PUBLIC
#define READONLY
@ -65,8 +67,8 @@
#include <sys/types.h>
#include <sys/stat.h>
#include "misc.h"
#include "json_parse.h"
#include "misc.h"
#include "vm.h"
#include "timing.h"
#include "cpu.h"

View File

@ -39,6 +39,8 @@ static uint8_t video__wider_font[0x8000] = { 0 };
static uint8_t video__font[0x4000] = { 0 };
static uint8_t video__int_font[3][0x4000] = { { 0 } }; // interface font
static color_mode_t color_mode = COLOR_NONE;
// Precalculated framebuffer offsets given VM addr
unsigned int video__screen_addresses[8192] = { INT_MIN };
uint8_t video__columns[8192] = { 0 };
@ -474,6 +476,22 @@ static void _initialize_color() {
#endif
}
static void video_prefsChanged(const char *domain) {
long val = 0;
prefs_parseLongValue(domain, PREF_COLOR_MODE, &val, /*base:*/10);
if (val < 0) {
val = 0;
}
if (val >= NUM_COLOROPTS) {
val = NUM_COLOROPTS-1;
}
color_mode = (color_mode_t)val;
#if TESTING
color_mode = COLOR;
#endif
video_reset();
}
void video_reset(void) {
_initialize_hires_values();
_initialize_tables_video();
@ -1531,6 +1549,8 @@ static void _init_interface(void) {
_initialize_row_col_tables();
_initialize_dhires_values();
_initialize_color();
prefs_registerListener(PREF_DOMAIN_VIDEO, &video_prefsChanged);
}
static __attribute__((constructor)) void __init_interface(void) {

View File

@ -12,6 +12,15 @@
#ifndef _DISPLAY_H_
#define _DISPLAY_H_
typedef enum color_mode_t {
COLOR_NONE = 0,
/*LAZY_COLOR, deprecated*/
COLOR,
/*LAZY_INTERP, deprecated*/
COLOR_INTERP,
NUM_COLOROPTS
} color_mode_t;
typedef struct video_animation_s {
#if INTERFACE_TOUCH

View File

@ -25,6 +25,8 @@ void (*interface_setGlyphScale)(int glyphScale) = NULL;
void (*(*interface_getModelDataSetter)(interface_device_t device))(const char *jsonData) = NULL;
#endif
static char disk_path[PATH_MAX] = { 0 };
// 2015/04/12 : This was legacy code for rendering the menu interfaces on desktop Linux. Portions here are resurrected
// to render HUD messages on desktop and mobile. Nothing special or pretty here, but has "just worked" for 20+ years ;-)
@ -420,7 +422,7 @@ void c_interface_select_diskette( int drive )
curpos = entries - 1;
}
char temp[PATH_MAX];
char temp[PATH_MAX] = { 0 };
for (;;)
{
for (i = 0; i < 18; i++)
@ -635,6 +637,8 @@ void c_interface_select_diskette( int drive )
snprintf(disk_path + len, MIN(ent_len, PATH_MAX-(len+ent_len)), "/%s", namelist[curpos]->d_name);
}
prefs_setStringValue(PREF_DOMAIN_INTERFACE, PREF_DISK_PATH, disk_path);
nextdir = true;
break;
}
@ -754,6 +758,16 @@ void c_interface_parameters()
int ch;
static interface_enum_t option = OPT_CPU;
static int cur_y = 0, cur_off = 0, cur_x = 0, cur_pos = 0;
long val = 0;
prefs_parseLongValue(PREF_DOMAIN_VIDEO, PREF_COLOR_MODE, &val, /*base:*/10);
color_mode_t color_mode = (color_mode_t)val;
prefs_parseLongValue(PREF_DOMAIN_JOYSTICK, PREF_JOYSTICK_MODE, &val, /*base:*/10);
/* extern */joy_mode = (joystick_mode_t)val;
prefs_parseLongValue(PREF_DOMAIN_AUDIO, PREF_SPEAKER_VOLUME, &val, /*base:*/10);
long speaker_volume = val;
/* reset the x position, so we don't lose our cursor if path changes */
cur_x = 0;
@ -765,7 +779,8 @@ void c_interface_parameters()
c_interface_print_screen( screen );
#define TEMPSIZE 1024
char temp[TEMPSIZE];
char temp[TEMPSIZE] = { 0 };
bool shutdown = false;
for (;;)
{
for (i = 0; (i < PARAMS_H) && (i < NUM_OPTIONS); i++)
@ -828,13 +843,13 @@ void c_interface_parameters()
break;
case OPT_VOLUME:
if (sound_volume == 0)
if (speaker_volume == 0)
{
snprintf(temp, TEMPSIZE, "%s", "Off ");
}
else
{
snprintf(temp, TEMPSIZE, "%d", sound_volume);
snprintf(temp, TEMPSIZE, "%ld", speaker_volume);
}
break;
@ -961,6 +976,7 @@ void c_interface_parameters()
{
cpu_scale_factor = CPU_SCALE_SLOWEST;
}
prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE, cpu_scale_factor);
break;
case OPT_ALTCPU:
@ -969,6 +985,7 @@ void c_interface_parameters()
{
cpu_altscale_factor = CPU_SCALE_SLOWEST;
}
prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, cpu_altscale_factor);
break;
case OPT_PATH:
@ -983,14 +1000,8 @@ void c_interface_parameters()
break;
case OPT_COLOR:
if (color_mode == 0)
{
color_mode = NUM_COLOROPTS-1;
}
else
{
--color_mode;
}
color_mode = (color_mode == 0) ? NUM_COLOROPTS-1 : color_mode-1;
prefs_setLongValue(PREF_DOMAIN_VIDEO, PREF_COLOR_MODE, color_mode);
break;
#if !VIDEO_OPENGL
@ -1009,27 +1020,22 @@ void c_interface_parameters()
#endif
case OPT_VOLUME:
if (sound_volume > 0)
if (speaker_volume > 0)
{
--sound_volume;
--speaker_volume;
}
prefs_setLongValue(PREF_DOMAIN_AUDIO, PREF_SPEAKER_VOLUME, speaker_volume);
break;
case OPT_CAPS:
if (caps_lock) {
caps_lock = false;
}
caps_lock = false;
use_system_caps_lock = true;
prefs_setBoolValue(PREF_DOMAIN_KEYBOARD, PREF_KEYBOARD_CAPS, caps_lock);
break;
case OPT_JOYSTICK:
if (joy_mode == 0)
{
joy_mode = NUM_JOYOPTS-1;
}
else
{
--joy_mode;
}
joy_mode = (joy_mode == 0) ? NUM_JOYOPTS-1 : joy_mode-1;
prefs_setLongValue(PREF_DOMAIN_JOYSTICK, PREF_JOYSTICK_MODE, joy_mode);
break;
case OPT_CALIBRATE:
@ -1055,6 +1061,7 @@ void c_interface_parameters()
{
cpu_scale_factor = CPU_SCALE_FASTEST;
}
prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE, cpu_scale_factor);
break;
case OPT_ALTCPU:
@ -1063,6 +1070,7 @@ void c_interface_parameters()
{
cpu_altscale_factor = CPU_SCALE_FASTEST;
}
prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, cpu_altscale_factor);
break;
case OPT_PATH:
@ -1080,14 +1088,8 @@ void c_interface_parameters()
break;
case OPT_COLOR:
if (color_mode == NUM_COLOROPTS-1)
{
color_mode = 0;
}
else
{
++color_mode;
}
color_mode = (color_mode >= NUM_COLOROPTS-1) ? 0 : color_mode+1;
prefs_setLongValue(PREF_DOMAIN_VIDEO, PREF_COLOR_MODE, color_mode);
break;
#if !VIDEO_OPENGL
@ -1106,28 +1108,23 @@ void c_interface_parameters()
#endif
case OPT_VOLUME:
sound_volume++;
if (sound_volume > 10)
speaker_volume++;
if (speaker_volume > 10)
{
sound_volume = 10;
speaker_volume = 10;
}
prefs_setLongValue(PREF_DOMAIN_AUDIO, PREF_SPEAKER_VOLUME, speaker_volume);
break;
case OPT_CAPS:
if (!caps_lock) {
caps_lock = true;
}
caps_lock = true;
use_system_caps_lock = false;
prefs_setBoolValue(PREF_DOMAIN_KEYBOARD, PREF_KEYBOARD_CAPS, caps_lock);
break;
case OPT_JOYSTICK:
if (joy_mode == NUM_JOYOPTS-1)
{
joy_mode = 0;
}
else
{
++joy_mode;
}
joy_mode = (joy_mode == NUM_JOYOPTS-1) ? 0 : joy_mode+1;
prefs_setLongValue(PREF_DOMAIN_JOYSTICK, PREF_JOYSTICK_MODE, joy_mode);
break;
case OPT_CALIBRATE:
@ -1149,8 +1146,9 @@ void c_interface_parameters()
video_reset();
vm_reinitializeAudio();
c_joystick_reset();
prefs_save();
c_interface_exit(ch);
return;
break;
}
else if ((ch == '?') && (option != OPT_PATH))
{
@ -1194,14 +1192,14 @@ void c_interface_parameters()
{
int i;
strncpy(temp, disk_path, TEMPSIZE);
strncpy(temp, disk_path, TEMPSIZE-1);
for (i = strlen(temp); i >= cur_pos + cur_x; i--)
{
temp[ i + 1 ] = temp[ i ];
}
temp[ cur_pos + cur_x ] = ch;
strncpy(disk_path, temp, PATH_MAX);
strncpy(disk_path, temp, PATH_MAX-1);
if (cur_x < INTERFACE_PATH_MAX-1)
{
cur_x++;
@ -1210,6 +1208,8 @@ void c_interface_parameters()
{
cur_pos++;
}
prefs_setStringValue(PREF_DOMAIN_INTERFACE, PREF_DISK_PATH, disk_path);
}
/* Backspace or delete setting path */
@ -1230,6 +1230,8 @@ void c_interface_parameters()
{
cur_pos--;
}
prefs_setStringValue(PREF_DOMAIN_INTERFACE, PREF_DISK_PATH, disk_path);
}
/* calibrate joystick */
@ -1269,7 +1271,7 @@ void c_interface_parameters()
ch = toupper(ch);
if (ch == 'Y')
{
save_settings();
prefs_save();
disk6_eject(0);
c_interface_print_screen( screen );
disk6_eject(1);
@ -1277,9 +1279,9 @@ void c_interface_parameters()
#ifdef __linux__
LOG("Back to Linux, w00t!\n");
#endif
video_shutdown(false); // soft quit video_main_loop()
shutdown = true;
c_interface_exit(ch);
return;
break;
}
}
@ -1299,7 +1301,7 @@ void c_interface_parameters()
c_joystick_reset();
cpu65_reboot();
c_interface_exit(ch);
return;
break;
}
}
@ -1307,6 +1309,12 @@ void c_interface_parameters()
}
}
}
if (shutdown) {
video_shutdown(/*emulatorShuttingDown:*/false); // soft quit video_main_loop()
} else {
prefs_sync(NULL);
}
}
/* -------------------------------------------------------------------------
@ -1537,6 +1545,15 @@ static void *interface_thread(void *current_key)
cpu_pause();
char *path = NULL;
prefs_copyStringValue(PREF_DOMAIN_INTERFACE, PREF_DISK_PATH, &path);
if (!path) {
ASPRINTF(&path, "%s", getenv("HOME"));
}
strncpy(disk_path, path, PATH_MAX-1);
disk_path[PATH_MAX-1] = '\0';
FREE(path);
switch ((__SWORD_TYPE)current_key) {
case kF1:
c_interface_select_diskette( 0 );

View File

@ -20,6 +20,8 @@
extern void copy_and_pad_string(char *dest, const char* src, const char c, const int len, const char cap);
#endif
joystick_mode_t joy_mode = JOY_PCJOY;
/* parameters for generic and keyboard-simulated joysticks */
uint16_t joy_x = HALF_JOY_RANGE;
uint16_t joy_y = HALF_JOY_RANGE;
@ -30,10 +32,33 @@ bool joy_clip_to_radius = false;
#ifdef KEYPAD_JOYSTICK
short joy_step = 1;
uint8_t joy_auto_recenter = 0;
bool joy_auto_recenter = false;
#endif
static void joystick_prefsChanged(const char *domain) {
assert(strcmp(domain, PREF_DOMAIN_JOYSTICK) == 0);
#ifdef KEYPAD_JOYSTICK
long lVal = 0;
prefs_parseLongValue(domain, PREF_JOYSTICK_KPAD_STEP, &lVal, /*base:*/10);
joy_step = (short)lVal;
if (joy_step < 1) {
joy_step = 1;
}
if (joy_step > 255) {
joy_step = 255;
}
prefs_parseBoolValue(domain, PREF_JOYSTICK_KPAD_AUTO_RECENTER, &joy_auto_recenter);
#endif
}
static __attribute__((constructor)) void _init_joystick(void) {
prefs_registerListener(PREF_DOMAIN_JOYSTICK, &joystick_prefsChanged);
}
#ifdef INTERFACE_CLASSIC
/* -------------------------------------------------------------------------
c_calibrate_pc_joystick() - calibrates joystick. determines extreme
and center coordinates. assumes that it can write to the interface
@ -109,7 +134,7 @@ static void c_calibrate_pc_joystick()
}
}
#if defined(KEYPAD_JOYSTICK)
#ifdef KEYPAD_JOYSTICK
static void c_calibrate_keypad_joystick()
{
@ -216,6 +241,9 @@ static void c_calibrate_keypad_joystick()
static struct timespec ts = { .tv_sec=0, .tv_nsec=33333333 };
nanosleep(&ts, NULL);
}
prefs_setLongValue(PREF_DOMAIN_JOYSTICK, PREF_JOYSTICK_KPAD_STEP, joy_step);
prefs_setBoolValue(PREF_DOMAIN_JOYSTICK, PREF_JOYSTICK_KPAD_AUTO_RECENTER, joy_auto_recenter);
}
#endif // KEYPAD_JOYSTICK
@ -227,7 +255,7 @@ void c_calibrate_joystick()
}
#ifdef KEYPAD_JOYSTICK
if (joy_mode == JOY_KPAD)
else if (joy_mode == JOY_KPAD)
{
c_calibrate_keypad_joystick();
}

View File

@ -16,6 +16,16 @@
#define HALF_JOY_RANGE (JOY_RANGE>>1)
#define QUARTER_JOY_RANGE (HALF_JOY_RANGE>>1)
typedef enum joystick_mode_t {
JOY_PCJOY = 0,
#ifdef KEYPAD_JOYSTICK
JOY_KPAD,
#endif
NUM_JOYOPTS
} joystick_mode_t;
extern joystick_mode_t joy_mode;
extern uint16_t joy_x;
extern uint16_t joy_y;
extern uint8_t joy_button0;
@ -24,11 +34,12 @@ extern uint8_t joy_button2;
extern bool joy_clip_to_radius;
#ifdef KEYPAD_JOYSTICK
extern uint8_t joy_auto_recenter;
extern bool joy_auto_recenter;
extern short joy_step;
#endif
void c_joystick_reset(void);
#ifdef INTERFACE_CLASSIC
void c_calibrate_joystick(void);
#endif

View File

@ -262,6 +262,9 @@ static int _json_createFromString(const char *jsonString, INOUT JSON_ref *jsonRe
case JSMN_ERROR_PART:
ERRLOG("%s", "String is not a complete JSON packet, moar bytes expected");
break;
case JSMN_ERROR_PRIMITIVE_INVAL:
ERRLOG("%s", "Invalid character inside JSON primitive");
break;
default:
ERRLOG("UNKNOWN errCount : %d", errCount);
break;
@ -460,6 +463,7 @@ int json_mapCopyJSON(const JSON_ref jsonRef, const char *key, INOUT JSON_ref *va
}
errCount = json_createFromString(str, val);
assert(errCount >= 0);
FREE(str);
} while (0);

View File

@ -15,12 +15,10 @@
#include "common.h"
/* from misc.c */
extern uid_t user, privileged;
static int next_key = -1;
static int last_scancode = -1;
bool caps_lock = true; // default enabled because so much breaks otherwise
bool use_system_caps_lock = false;
/* ----------------------------------------------------
//e Keymap. Mapping scancodes to Apple //e US Keyboard
@ -345,7 +343,7 @@ void c_keys_handle_input(int scancode, int pressed, int is_cooked)
} while(0);
}
#if defined(KEYPAD_JOYSTICK)
#ifdef KEYPAD_JOYSTICK
// Keypad emulated joystick relies on "raw" keyboard input
if (joy_mode == JOY_KPAD)
{
@ -484,6 +482,17 @@ bool c_keys_is_interface_key(int key)
}
#endif
static void keys_prefsChanged(const char *domain) {
bool val = false;
if (prefs_parseBoolValue(domain, PREF_KEYBOARD_CAPS, &val)) {
caps_lock = val;
}
}
static __attribute__((constructor)) void __init_keys(void) {
prefs_registerListener(PREF_DOMAIN_KEYBOARD, &keys_prefsChanged);
}
#if INTERFACE_TOUCH
bool (*keydriver_isTouchKeyboardAvailable)(void) = NULL;
void (*keydriver_setTouchKeyboardEnabled)(bool enabled) = NULL;

View File

@ -136,6 +136,7 @@
// ----------------------------------------------------------------------------
extern bool caps_lock;
extern bool use_system_caps_lock;
int c_mygetch(int block);
int c_rawkey();
@ -183,3 +184,4 @@ extern void (*keydriver_loadAltKbd)(const char *kbdPath);
#endif
#endif

View File

@ -1415,9 +1415,7 @@ void c_interface_debugging() {
int ch;
int command_pos = PROMPT_X;
opcodes = (apple_mode == 0) ? opcodes_6502 :
(apple_mode == 1) ? opcodes_undoc :
opcodes_65c02;
opcodes = opcodes_65c02;
/* initialize the buffers */
for (i=0; i<BUF_Y; i++)

View File

@ -28,8 +28,6 @@ static module_ctor_node_s *head = NULL;
bool do_logging = true; // also controlled by NDEBUG
FILE *error_log = NULL;
int sound_volume = 2;
color_mode_t color_mode = COLOR;
const char *data_dir = NULL;
char **argv = NULL;
int argc = 0;
@ -278,7 +276,7 @@ void emulator_start(void) {
head = NULL;
#ifdef INTERFACE_CLASSIC
load_settings(); // user prefs
prefs_load(); // user prefs
c_keys_set_key(kF8); // show credits before emulation start
#endif
@ -293,6 +291,7 @@ void emulator_shutdown(void) {
disk6_eject(0);
disk6_eject(1);
video_shutdown(/*emulatorShuttingDown:*/true);
prefs_shutdown(/*emulatorShuttingDown:*/true);
timing_stopCPU();
_shutdown_threads();
}

View File

@ -13,371 +13,426 @@
*
*/
#define _GNU_SOURCE
#include "prefs.h"
#include "json_parse_private.h"
#include "common.h"
typedef struct prefs_listener_s {
prefs_change_callback_f prefsChanged;
struct prefs_listener_s *nextListener;
} prefs_listener_s;
#define PRM_NONE 0
#define PRM_SPEED 1
#define PRM_ALTSPEED 101
#define PRM_MODE 2
#define PRM_DISK_PATH 3
#define PRM_HIRES_COLOR 4
#define PRM_VOLUME 5
#define PRM_JOY_INPUT 6
#define PRM_VIDEO_MODE 7
#define PRM_JOY_KPAD_CALIBRATE 11
#define PRM_ROM_PATH 12
#define PRM_CAPSLOCK 102
typedef struct prefs_domain_s {
char *domain;
struct prefs_listener_s *listeners;
struct prefs_domain_s *nextDomain;
} prefs_domain_s;
static JSON_ref jsonPrefs = NULL;
static prefs_domain_s *domains = NULL;
static char *prefsFile = NULL;
char system_path[PATH_MAX] = { 0 };
char disk_path[PATH_MAX] = { 0 };
static pthread_mutex_t prefsLock = PTHREAD_MUTEX_INITIALIZER;
#warning FIXME TODO : completely excise deprecated apple_mode stuff
int apple_mode = 2/*IIE_MODE*/;
a2_video_mode_t a2_video_mode = VIDEO_1X;
joystick_mode_t joy_mode = JOY_PCJOY;
// ----------------------------------------------------------------------------
static char *config_filename = NULL;
void prefs_load(void) {
FREE(prefsFile);
struct match_table
{
const char *tag;
int value;
};
static const struct match_table prefs_table[] =
{
{ "speed", PRM_SPEED },
{ "altspeed", PRM_ALTSPEED },
{ "mode", PRM_MODE },
{ "path", PRM_DISK_PATH },
{ "disk path", PRM_DISK_PATH },
{ "disk_path", PRM_DISK_PATH },
{ "path", PRM_DISK_PATH },
{ "color", PRM_HIRES_COLOR },
{ "video", PRM_VIDEO_MODE },
{ "volume", PRM_VOLUME },
{ "caps_lock", PRM_CAPSLOCK },
{ "caps lock", PRM_CAPSLOCK },
{ "joystick", PRM_JOY_INPUT },
{ "keypad joystick parms", PRM_JOY_KPAD_CALIBRATE },
{ "keypad_joystick_parms", PRM_JOY_KPAD_CALIBRATE },
{ "system path", PRM_ROM_PATH },
{ "system_path", PRM_ROM_PATH },
{ 0, PRM_NONE }
};
static const struct match_table color_table[] =
{
{ "black/white", COLOR_NONE },
/*{ "lazy color", LAZY_COLOR }, deprecated*/
{ "color", COLOR },
/*{ "lazy interpolated", LAZY_INTERP }, deprecated*/
{ "interpolated", COLOR_INTERP },
{ "off", 0 },
{ "on", COLOR },
{ 0, COLOR }
};
static const struct match_table video_table[] =
{
{ "1X", VIDEO_1X },
{ "2X", VIDEO_2X },
{ "Fullscreen", VIDEO_FULLSCREEN },
{ 0, VIDEO_1X }
};
static const struct match_table volume_table[] =
{
{ "0", 0 },
{ "1", 1 },
{ "2", 2 },
{ "3", 3 },
{ "4", 4 },
{ "5", 5 },
{ "6", 6 },
{ "7", 7 },
{ "8", 8 },
{ "9", 9 },
{ "10", 10 },
{ 0, 10 },
};
static const struct match_table capslock_table[] =
{
{ "0", 0 },
{ "1", 1 },
};
static const struct match_table joy_input_table[] =
{
{ "pc joystick", JOY_PCJOY },
#ifdef KEYPAD_JOYSTICK
{ "joy keypad", JOY_KPAD },
{ "joy_keypad", JOY_KPAD },
#endif
{ 0, JOY_PCJOY }
};
/* Find the number assigned to KEYWORD in a match table PARADIGM. If no match,
* then the value associated with the terminating entry is used as a
* default. */
static int match(const struct match_table *paradigm, const char *keyword)
{
while (paradigm->tag && strcasecmp(paradigm->tag, keyword))
{
paradigm++;
const char *apple2JSON = getenv("APPLE2IX_JSON");
if (apple2JSON) {
ASPRINTF(&prefsFile, "%s", apple2JSON);
}
return paradigm->value;
if (!prefsFile) {
ASPRINTF(&prefsFile, "%s/.apple2.json", getenv("HOME"));
}
assert(prefsFile);
json_destroy(&jsonPrefs);
int tokCount = json_createFromFile(prefsFile, &jsonPrefs);
if (tokCount < 0) {
tokCount = json_createFromString("{}", &jsonPrefs);
assert(tokCount > 0);
}
prefs_sync(NULL);
}
/* Reverse match -- find a keyword associated with number KEY in
* PARADIGM. The first match is used -- synonym keywords appearing later
* in the table are not chosen.
*
* A null is returned for no match.
*/
static const char *reverse_match(const struct match_table *paradigm, int key)
{
while (paradigm->tag && key != paradigm->value)
{
paradigm++;
void prefs_loadString(const char *jsonString) {
json_destroy(&jsonPrefs);
int tokCount = json_createFromString(jsonString, &jsonPrefs);
if (tokCount < 0) {
tokCount = json_createFromString("{}", &jsonPrefs);
assert(tokCount > 0);
}
return paradigm->tag;
prefs_sync(NULL);
}
/* Eat leading and trailing whitespace of string X. The old string is
* overwritten and a new pointer is returned.
*/
static char * clean_string(char *x)
{
size_t y;
bool prefs_save(void) {
/* Leading white space */
while (isspace(*x))
{
x++;
}
/* Trailing white space */
y = strlen(x);
while (y && x[y--] == ' ')
{
}
x[y] = 0;
return x;
}
/* Load the configuration. Must be called *once* at start. */
void load_settings(void)
{
/* set system defaults before user defaults. */
strcpy(disk_path, "./disks");
strcpy(system_path, "./rom");
const char *apple2cfg = getenv("APPLE2IXCFG");
if (apple2cfg) {
config_filename = strdup(apple2cfg);
} else {
const char *homedir;
homedir = getenv("HOME");
config_filename = malloc(strlen(homedir) + 9);
strcpy(config_filename, homedir);
strcat(config_filename, "/.apple2");
/* config_filename is left allocated for convinence in
* save_settings */
}
{
char *buffer = 0;
size_t size = 0;
FILE *config_file = fopen(config_filename, "r");
if (config_file == NULL)
{
ERRLOG(
"Warning. Cannot open the .apple2 system defaults file.\n"
"Make sure it's readable in your home directory.");
return;
int fd = -1;
bool success = false;
do {
if (!prefsFile) {
ERRLOG("Not saving preferences, no file loaded...");
break;
}
while (getline(&buffer, &size, config_file) != -1)
{
char *parameter;
char *argument;
/* Split line between parameter and argument */
parameter = buffer;
argument = strchr(buffer, '=');
argument[0] = 0;
argument++;
parameter = clean_string(parameter);
argument = clean_string(argument);
int main_match = match(prefs_table, parameter);
switch (main_match)
{
case PRM_NONE:
ERRLOG("Unrecognized config parameter `%s'", parameter);
break;
case PRM_SPEED:
case PRM_ALTSPEED:
{
double x = strtod(argument, NULL);
if (x > CPU_SCALE_FASTEST)
{
x = CPU_SCALE_FASTEST;
}
else if (x < CPU_SCALE_SLOWEST)
{
x = CPU_SCALE_SLOWEST;
}
if (main_match == PRM_SPEED)
{
cpu_scale_factor = x;
}
else
{
cpu_altscale_factor = x;
}
break;
}
case PRM_DISK_PATH:
strncpy(disk_path, argument, PATH_MAX-1);
disk_path[PATH_MAX-1] = '\0';
break;
case PRM_HIRES_COLOR:
color_mode = match(color_table, argument);
break;
case PRM_VIDEO_MODE:
a2_video_mode = match(video_table, argument);
break;
case PRM_VOLUME:
sound_volume = match(volume_table, argument);
break;
case PRM_CAPSLOCK:
caps_lock = match(capslock_table, argument) ? true : false;
break;
case PRM_JOY_INPUT:
joy_mode = match(joy_input_table, argument);
break;
#ifdef KEYPAD_JOYSTICK
case PRM_JOY_KPAD_CALIBRATE:
joy_step = strtol(argument, &argument, 10);
if (joy_step < 1)
{
joy_step = 1;
}
else if (joy_step > 255)
{
joy_step = 255;
}
joy_auto_recenter = strtol(argument, &argument, 10);
break;
#endif
case PRM_ROM_PATH:
strncpy(system_path, argument, PATH_MAX-1);
system_path[PATH_MAX-1] = '\0';
break;
}
if (!jsonPrefs) {
ERRLOG("Not saving preferences, none loaded...");
break;
}
free(buffer);
if (((JSON_s *)jsonPrefs)->numTokens <= 0) {
ERRLOG("Not saving preferences, no preferences loaded...");
break;
}
fclose(config_file);
}
}
/* Save the configuration */
bool save_settings(void)
{
FILE *config_file = NULL;
LOG("Saving preferences...");
assert(((JSON_s *)jsonPrefs)->jsonString && "string should be valid");
#define PREFS_ERRPRINT() \
ERRLOG( \
"Cannot open the .apple2/apple2.cfg system defaults file for writing.\n" \
"Make sure it has rw permission in your home directory.")
"Cannot open the .apple2.json preferences file for writing.\n" \
"Make sure it has R/W permission in your home directory.")
#define ERROR_SUBMENU_H 9
#define ERROR_SUBMENU_H 8
#define ERROR_SUBMENU_W 40
#ifdef INTERFACE_CLASSIC
int ch = -1;
char submenu[ERROR_SUBMENU_H][ERROR_SUBMENU_W+1] =
//1. 5. 10. 15. 20. 25. 30. 35. 40.
{ "||||||||||||||||||||||||||||||||||||||||",
"| |",
"| |",
"| OOPS, could not open or write to the |",
"| .apple2/apple2.cfg file in your HOME |",
"| directory ... |",
"| |",
"| |",
"||||||||||||||||||||||||||||||||||||||||" };
#if defined(INTERFACE_CLASSIC) && !TESTING
int ch = -1;
char submenu[ERROR_SUBMENU_H][ERROR_SUBMENU_W+1] =
//1. 5. 10. 15. 20. 25. 30. 35. 40.
{ "||||||||||||||||||||||||||||||||||||||||",
"| |",
"| |",
"| OOPS, could not open or write to the |",
"| .apple2.json preferences file |",
"| |",
"| |",
"||||||||||||||||||||||||||||||||||||||||" };
#endif
config_file = fopen(config_filename, "w");
if (config_file == NULL)
{
PREFS_ERRPRINT();
#ifdef INTERFACE_CLASSIC
c_interface_print_submenu_centered(submenu[0], ERROR_SUBMENU_W, ERROR_SUBMENU_H);
while ((ch = c_mygetch(1)) == -1)
{
TEMP_FAILURE_RETRY(fd = open(prefsFile, O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR));
if (fd == -1) {
PREFS_ERRPRINT();
#if defined(INTERFACE_CLASSIC) && !TESTING
c_interface_print_submenu_centered(submenu[0], ERROR_SUBMENU_W, ERROR_SUBMENU_H);
while ((ch = c_mygetch(1)) == -1) {
// ...
}
#endif
break;
}
#endif
return false;
success = json_serialize(jsonPrefs, fd, /*pretty:*/true);
} while (0);
if (fd != -1) {
TEMP_FAILURE_RETRY(fsync(fd));
TEMP_FAILURE_RETRY(close(fd));
}
int err = 0;
err |= fprintf(config_file, "speed = %0.2lf\n", cpu_scale_factor);
err |= fprintf(config_file, "altspeed = %0.2lf\n", cpu_altscale_factor);
err |= fprintf(config_file, "disk path = %s\n", disk_path);
err |= fprintf(config_file, "color = %s\n", reverse_match(color_table, color_mode));
err |= fprintf(config_file, "video = %s\n", reverse_match(video_table, a2_video_mode));
err |= fprintf(config_file, "volume = %s\n", reverse_match(volume_table, sound_volume));
err |= fprintf(config_file, "caps lock = %s\n", reverse_match(capslock_table, (int)caps_lock));
err |= fprintf(config_file, "joystick = %s\n", reverse_match(joy_input_table, joy_mode));
err |= fprintf(config_file, "system path = %s\n", system_path);
#ifdef KEYPAD_JOYSTICK
err |= fprintf(config_file, "keypad joystick parms = %d %u\n", joy_step, (joy_auto_recenter ? 1 : 0));
#endif
if (err < 0) {
PREFS_ERRPRINT();
#ifdef INTERFACE_CLASSIC
c_interface_print_submenu_centered(submenu[0], ERROR_SUBMENU_W, ERROR_SUBMENU_H);
while ((ch = c_mygetch(1)) == -1) { }
#endif
return false;
}
fclose(config_file);
return true;
return success;
}
bool prefs_copyStringValue(const char *domain, const char *key, INOUT char **val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount < 0) {
break;
}
ret = json_mapCopyStringValue(jsonRef, key, val);
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_parseLongValue(const char *domain, const char *key, INOUT long *val, const long base) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount < 0) {
break;
}
ret = json_mapParseLongValue(jsonRef, key, val, base);
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_parseBoolValue(const char *domain, const char *key, INOUT bool *val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount < 0) {
break;
}
ret = json_mapParseBoolValue(jsonRef, key, val);
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_parseFloatValue(const char *domain, const char *key, INOUT float *val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount < 0) {
break;
}
ret = json_mapParseFloatValue(jsonRef, key, val);
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_setStringValue(const char *domain, const char * _NONNULL key, const char * _NONNULL val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount <= 0) {
errCount = json_createFromString("{}", &jsonRef);
assert(errCount > 0);
assert(jsonRef);
}
ret = json_mapSetStringValue(jsonRef, key, val);
if (!ret) {
break;
}
ret = json_mapSetJSONValue(jsonPrefs, domain, jsonRef);
if (!ret) {
break;
}
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_setLongValue(const char * _NONNULL domain, const char * _NONNULL key, long val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount <= 0) {
errCount = json_createFromString("{}", &jsonRef);
assert(errCount > 0);
assert(jsonRef);
}
ret = json_mapSetLongValue(jsonRef, key, val);
if (!ret) {
break;
}
ret = json_mapSetJSONValue(jsonPrefs, domain, jsonRef);
if (!ret) {
break;
}
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_setBoolValue(const char * _NONNULL domain, const char * _NONNULL key, bool val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount <= 0) {
errCount = json_createFromString("{}", &jsonRef);
assert(errCount > 0);
assert(jsonRef);
}
ret = json_mapSetBoolValue(jsonRef, key, val);
if (!ret) {
break;
}
ret = json_mapSetJSONValue(jsonPrefs, domain, jsonRef);
if (!ret) {
break;
}
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
bool prefs_setFloatValue(const char * _NONNULL domain, const char * _NONNULL key, float val) {
pthread_mutex_lock(&prefsLock);
bool ret = false;
JSON_ref jsonRef = NULL;
do {
int errCount = json_mapCopyJSON(jsonPrefs, domain, &jsonRef);
if (errCount <= 0) {
errCount = json_createFromString("{}", &jsonRef);
assert(errCount > 0);
assert(jsonRef);
}
ret = json_mapSetFloatValue(jsonRef, key, val);
if (!ret) {
break;
}
ret = json_mapSetJSONValue(jsonPrefs, domain, jsonRef);
if (!ret) {
break;
}
} while (0);
pthread_mutex_unlock(&prefsLock);
json_destroy(&jsonRef);
return ret;
}
void prefs_registerListener(const char *domain, prefs_change_callback_f callback) {
pthread_mutex_lock(&prefsLock);
assert(domain && "listener needs to specify non-NULL domain");
prefs_domain_s *dom = domains;
while (dom) {
if (strcmp(domain, dom->domain) == 0) {
break;
}
dom = dom->nextDomain;
}
if (!dom) {
dom = MALLOC(sizeof(*dom));
dom->domain = STRDUP(domain);
dom->nextDomain = domains;
dom->listeners = NULL;
domains = dom;
}
prefs_listener_s *newL = MALLOC(sizeof(*newL));
prefs_listener_s *oldL = dom->listeners;
dom->listeners = newL;
newL->nextListener = oldL;
newL->prefsChanged = callback;
pthread_mutex_unlock(&prefsLock);
}
void prefs_sync(const char *domain) {
static int syncCount = 0;
pthread_mutex_lock(&prefsLock);
++syncCount;
if (syncCount > 1) {
pthread_mutex_unlock(&prefsLock);
return;
}
pthread_mutex_unlock(&prefsLock);
prefs_domain_s *dom = domains;
do {
while (dom) {
if (domain && (strcmp(domain, dom->domain) != 0)) {
continue;
}
prefs_listener_s *listener = dom->listeners;
while (listener) {
listener->prefsChanged(dom->domain);
listener = listener->nextListener;
}
if (domain) {
break;
}
dom = dom->nextDomain;
}
pthread_mutex_lock(&prefsLock);
--syncCount;
if (syncCount == 0) {
pthread_mutex_unlock(&prefsLock);
break;
}
pthread_mutex_unlock(&prefsLock);
} while (1);
}
void prefs_shutdown(bool emulatorShuttingDown) {
if (!emulatorShuttingDown) {
return;
}
FREE(prefsFile);
pthread_mutex_lock(&prefsLock);
prefs_domain_s *dom = domains;
domains = NULL;
while (dom) {
prefs_listener_s *listener = dom->listeners;
while (listener) {
prefs_listener_s *dead = listener;
listener = listener->nextListener;
FREE(dead);
}
prefs_domain_s *dead = dom;
dom = dom->nextDomain;
FREE(dead->domain);
FREE(dead);
}
pthread_mutex_unlock(&prefsLock);
}

View File

@ -9,52 +9,88 @@
* Copyright 1995 Stephen Lee
* Copyright 1997, 1998 Aaron Culliney
* Copyright 1998, 1999, 2000 Michael Deutschmann
* Copyright 2013-2015 Aaron Culliney
* Copyright 2013-2016 Aaron Culliney
*
*/
#ifndef PREFS_H
#define PREFS_H
#ifndef _PREFS_H_
#define _PREFS_H_
#include "common.h"
typedef enum joystick_mode_t {
JOY_PCJOY = 0,
#ifdef KEYPAD_JOYSTICK
JOY_KPAD,
// pref domains
#define PREF_DOMAIN_AUDIO "audio"
#define PREF_DOMAIN_INTERFACE "interface"
#define PREF_DOMAIN_JOYSTICK "joystick"
#define PREF_DOMAIN_KEYBOARD "keyboard"
#define PREF_DOMAIN_VIDEO "video"
#define PREF_DOMAIN_VM "vm"
// audio
#define PREF_SPEAKER_VOLUME "speakerVolume"
#define PREF_MOCKINGBOARD_VOLUME "mbVolume"
// video
#define PREF_COLOR_MODE "colorMode"
// joystick
#define PREF_JOYSTICK_MODE "joystickMode"
#define PREF_JOYSTICK_KPAD_AUTO_RECENTER "kpAutoRecenter"
#define PREF_JOYSTICK_KPAD_STEP "kpStep"
// keyboard
#define PREF_KEYBOARD_CAPS "caps"
// interface
#define PREF_DISK_PATH "diskPath"
// vm
#define PREF_CPU_SCALE "cpuScale"
#define PREF_CPU_SCALE_ALT "cpuScaleAlt"
typedef void (*prefs_change_callback_f)(const char * _NONNULL domain);
// load preferences from persistent store
extern void prefs_load(void);
// load preferences from JSON string
extern void prefs_loadString(const char * _NONNULL jsonString);
// save preferences to persistent store
extern bool prefs_save(void);
// copy string value for key in prefs domain, returns true upon success and strdup()'d value in *val
extern bool prefs_copyStringValue(const char * _NONNULL domain, const char * _NONNULL key, INOUT char ** _NONNULL val);
// get long value for key in prefs domain, returns true upon success
extern bool prefs_parseLongValue(const char * _NONNULL domain, const char * _NONNULL key, INOUT long *val, const long base);
// get long value for key in prefs domain, returns true upon success
extern bool prefs_parseBoolValue(const char * _NONNULL domain, const char * _NONNULL key, INOUT bool *val);
// get float value for key in prefs domain, returns true upon success
extern bool prefs_parseFloatValue(const char * _NONNULL domain, const char *key, INOUT float * _NONNULL val);
// set string value for key in prefs domain
extern bool prefs_setStringValue(const char *domain, const char * _NONNULL key, const char * _NONNULL val);
// set long value for key in prefs domain
extern bool prefs_setLongValue(const char * _NONNULL domain, const char * _NONNULL key, long val);
// set bool value for key in map JSON, returns true upon success
extern bool prefs_setBoolValue(const char * _NONNULL domain, const char * _NONNULL key, bool val);
// set float value for key in prefs domain
extern bool prefs_setFloatValue(const char * _NONNULL domain, const char * _NONNULL key, float val);
// register a preferences listener for a particular domain
extern void prefs_registerListener(const char * _NONNULL domain, _NONNULL prefs_change_callback_f callback);
// send change notification to all domain listeners
extern void prefs_sync(const char * _NULLABLE domain);
// cleans up and removes listener in preparation for app shutdown
extern void prefs_shutdown(bool emulatorShuttingDown);
#endif
NUM_JOYOPTS
} joystick_mode_t;
typedef enum color_mode_t {
COLOR_NONE = 0,
/*LAZY_COLOR, deprecated*/
COLOR,
/*LAZY_INTERP, deprecated*/
COLOR_INTERP,
NUM_COLOROPTS
} color_mode_t;
typedef enum a2_video_mode_t {
VIDEO_FULLSCREEN = 0,
VIDEO_1X,
VIDEO_2X,
NUM_VIDOPTS
} a2_video_mode_t;
extern char system_path[PATH_MAX];
extern char disk_path[PATH_MAX];
extern int apple_mode; /* undocumented instructions or //e mode */
extern int sound_volume;
extern color_mode_t color_mode;
extern a2_video_mode_t a2_video_mode;
/* generic joystick settings */
extern joystick_mode_t joy_mode;
/* functions in prefs.c */
extern void load_settings(void);
extern bool save_settings(void);
#endif /* PREFS_H */

View File

@ -33,7 +33,7 @@ static const char *get_default_preferences(void) {
" \"diskPath\" : \"/usr/local/games/apple2/disks\""
" },"
" \"joystick\" : {"
" \"variant\" : \"keypad\","
" \"joystickMode\" : 1,"
" \"pcJoystickParms\" : \"128 128 255 1 255 1\","
" \"kpJoystickParms\" : \"8 1\""
" },"
@ -41,11 +41,11 @@ static const char *get_default_preferences(void) {
" \"caps\" : true"
" },"
" \"video\" : {"
" \"color\" : \"interpolated\""
" \"colorMode\" : \"2\""
" },"
" \"vm\" : {"
" \"speed\" : 1.0,"
" \"altSpeed\" : 4.0"
" \"cpuScale\" : 1.0,"
" \"cpuScaleAlt\" : 4.0"
" }"
"}"
;
@ -1292,7 +1292,6 @@ TEST test_json_map_mutation_1() {
PASS();
}
#if 0
TEST test_prefs_loadString_1() {
const char *prefsJSON = get_default_preferences();
prefs_loadString(prefsJSON);
@ -1302,22 +1301,22 @@ TEST test_prefs_loadString_1() {
long lVal = 0;
float fVal = 0.f;
bool ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, "speakerVolume", &lVal, /*base:*/10);
bool ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, PREF_SPEAKER_VOLUME, &lVal, /*base:*/10);
ASSERT(ok);
ASSERT(lVal == 4);
ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, "mbVolume", &lVal, /*base:*/10);
ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, PREF_MOCKINGBOARD_VOLUME, &lVal, /*base:*/10);
ASSERT(ok);
ASSERT(lVal == 2);
ok = prefs_copyStringValue(PREF_DOMAIN_INTERFACE, "diskPath", &val);
ok = prefs_copyStringValue(PREF_DOMAIN_INTERFACE, PREF_DISK_PATH, &val);
ASSERT(ok);
ASSERT(strcmp(val, "/usr/local/games/apple2/disks") == 0);
FREE(val);
ok = prefs_copyStringValue(PREF_DOMAIN_JOYSTICK, "variant", &val);
ok = prefs_copyStringValue(PREF_DOMAIN_JOYSTICK, PREF_JOYSTICK_MODE, &val);
ASSERT(ok);
ASSERT(strcmp(val, "keypad") == 0);
ASSERT(strcmp(val, "1") == 0);
FREE(val);
ok = prefs_copyStringValue(PREF_DOMAIN_JOYSTICK, "pcJoystickParms", &val);
@ -1330,20 +1329,20 @@ TEST test_prefs_loadString_1() {
ASSERT(strcmp(val, "8 1") == 0);
FREE(val);
ok = prefs_parseBoolValue(PREF_DOMAIN_KEYBOARD, "caps", &bVal);
ok = prefs_parseBoolValue(PREF_DOMAIN_KEYBOARD, PREF_KEYBOARD_CAPS, &bVal);
ASSERT(ok);
ASSERT(bVal == true);
ok = prefs_copyStringValue(PREF_DOMAIN_VIDEO, "color", &val);
ok = prefs_copyStringValue(PREF_DOMAIN_VIDEO, PREF_COLOR_MODE, &val);
ASSERT(ok);
ASSERT(strcmp(val, "interpolated") == 0);
ASSERT(strcmp(val, "2") == 0);
FREE(val);
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, "speed", &fVal);
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE, &fVal);
ASSERT(ok);
ASSERT(fVal == 1.f);
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, "altSpeed", &fVal);
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, &fVal);
ASSERT(ok);
ASSERT(fVal == 4.f);
@ -1359,34 +1358,117 @@ TEST test_prefs_set_props() {
long lVal = 0;
float fVal = 0.f;
bool ok = prefs_setLongValue(PREF_DOMAIN_AUDIO, "speakerVolume", 8);
bool ok = prefs_setLongValue(PREF_DOMAIN_AUDIO, PREF_SPEAKER_VOLUME, 8);
ASSERT(ok);
ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, "speakerVolume", &lVal, /*base:*/10);
ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, PREF_SPEAKER_VOLUME, &lVal, /*base:*/10);
ASSERT(ok);
ASSERT(lVal == 8);
ok = prefs_setStringValue(PREF_DOMAIN_INTERFACE, "diskPath", "/home/apple2ix/disks");
ok = prefs_setStringValue(PREF_DOMAIN_INTERFACE, PREF_DISK_PATH, "/home/apple2ix/disks");
ASSERT(ok);
ok = prefs_copyStringValue(PREF_DOMAIN_INTERFACE, "diskPath", &val);
ok = prefs_copyStringValue(PREF_DOMAIN_INTERFACE, PREF_DISK_PATH, &val);
ASSERT(ok);
ASSERT(strcmp(val, "/home/apple2ix/disks") == 0);
FREE(val);
ok = prefs_setBoolValue(PREF_DOMAIN_KEYBOARD, "caps", false);
ok = prefs_setBoolValue(PREF_DOMAIN_KEYBOARD, PREF_KEYBOARD_CAPS, false);
ASSERT(ok);
ok = prefs_parseBoolValue(PREF_DOMAIN_KEYBOARD, "caps", &bVal);
ok = prefs_parseBoolValue(PREF_DOMAIN_KEYBOARD, PREF_KEYBOARD_CAPS, &bVal);
ASSERT(ok);
ASSERT(bVal == false);
ok = prefs_setFloatValue(PREF_DOMAIN_VM, "altSpeed", 0.25f);
ok = prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, 0.25f);
ASSERT(ok);
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, "altSpeed", &fVal);
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, &fVal);
ASSERT(ok);
ASSERT(fVal == 0.25f);
PASS();
}
#endif
#define TEST_JSON "test-apple2ix.json"
#define EXPECTED_TEST_PREFS_FILE_SIZE 178
#define EXPECTED_TEST_PREFS_SHA "263844f0177a9229eece7907cd9f6f72aef535f5"
TEST test_prefs_load_and_save() {
unlink(TEST_JSON);
putenv("APPLE2IX_JSON=" TEST_JSON);
prefs_load();
prefs_save();
bool ok = false;
char *val = (char *)0xdeadc0de;
bool bVal = false;
long lVal = 42;
float fVal = 0.125;
ok = prefs_copyStringValue(PREF_DOMAIN_INTERFACE, PREF_DISK_PATH, &val);
ASSERT(!ok);
ASSERT(val == (char *)0xdeadc0de);
//FREE(val);
ok = prefs_setStringValue(PREF_DOMAIN_INTERFACE, PREF_DISK_PATH, "/home/apple2ix/disks");
ASSERT(ok);
ok = prefs_copyStringValue(PREF_DOMAIN_INTERFACE, PREF_DISK_PATH, &val);
ASSERT(ok);
ASSERT(val);
ASSERT(strcmp(val, "/home/apple2ix/disks") == 0);
FREE(val);
ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, PREF_SPEAKER_VOLUME, &lVal, /*base:*/10);
ASSERT(!ok);
ASSERT(lVal == 42);
ok = prefs_setLongValue(PREF_DOMAIN_AUDIO, PREF_SPEAKER_VOLUME, 8);
ASSERT(ok);
ok = prefs_parseLongValue(PREF_DOMAIN_AUDIO, PREF_SPEAKER_VOLUME, &lVal, /*base:*/10);
ASSERT(ok);
ASSERT(lVal == 8);
ok = prefs_parseBoolValue(PREF_DOMAIN_KEYBOARD, PREF_KEYBOARD_CAPS, &bVal);
ASSERT(!ok);
ASSERT(bVal == false);
ok = prefs_setBoolValue(PREF_DOMAIN_KEYBOARD, PREF_KEYBOARD_CAPS, true);
ASSERT(ok);
ok = prefs_parseBoolValue(PREF_DOMAIN_KEYBOARD, PREF_KEYBOARD_CAPS, &bVal);
ASSERT(ok);
ASSERT(bVal == true);
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, &fVal);
ASSERT(!ok);
ASSERT(fVal == 0.125f);
ok = prefs_setFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, 0.25f);
ASSERT(ok);
ok = prefs_parseFloatValue(PREF_DOMAIN_VM, PREF_CPU_SCALE_ALT, &fVal);
ASSERT(ok);
ASSERT(fVal == 0.25f);
prefs_save();
do {
uint8_t md[SHA_DIGEST_LENGTH];
char mdstr0[(SHA_DIGEST_LENGTH*2)+1];
FILE *fp = fopen(TEST_JSON, "r");
fseek(fp, 0, SEEK_END);
long expectedSize = ftell(fp);
ASSERT(expectedSize == EXPECTED_TEST_PREFS_FILE_SIZE);
fseek(fp, 0, SEEK_SET);
unsigned char *buf = MALLOC(EXPECTED_TEST_PREFS_FILE_SIZE);
if (fread(buf, 1, EXPECTED_TEST_PREFS_FILE_SIZE, fp) != EXPECTED_TEST_PREFS_FILE_SIZE) {
ASSERT(false);
}
fclose(fp); fp = NULL;
SHA1(buf, EXPECTED_TEST_PREFS_FILE_SIZE, md);
FREE(buf);
sha1_to_str(md, mdstr0);
ASSERT(strcasecmp(mdstr0, EXPECTED_TEST_PREFS_SHA) == 0);
} while(0);
unlink(TEST_JSON);
PASS();
}
// ----------------------------------------------------------------------------
// Test Suite
@ -1436,8 +1518,9 @@ GREATEST_SUITE(test_suite_prefs) {
RUN_TESTp(test_json_map_mutation_1);
//RUN_TESTp(test_prefs_loadString_1);
//RUN_TESTp(test_prefs_set_props);
RUN_TESTp(test_prefs_loadString_1);
RUN_TESTp(test_prefs_set_props);
RUN_TESTp(test_prefs_load_and_save);
// --------------------------------
pthread_mutex_unlock(&interface_mutex);

View File

@ -586,3 +586,19 @@ void timing_testCyclesCountOverflow(void) {
}
#endif
// ----------------------------------------------------------------------------
static void vm_prefsChanged(const char *domain) {
assert(strcmp(domain, PREF_DOMAIN_VM) == 0);
float fVal = 1.0;
prefs_parseFloatValue(domain, PREF_CPU_SCALE, &fVal);
cpu_scale_factor = fVal;
prefs_parseFloatValue(domain, PREF_CPU_SCALE_ALT, &fVal);
cpu_altscale_factor = fVal;
}
static __attribute__((constructor)) void _init_vm(void) {
prefs_registerListener(PREF_DOMAIN_VM, &vm_prefsChanged);
}

View File

@ -23,13 +23,12 @@
static inline void _capslock_hackaround(void) {
// NOTE : Unfortunately it appears that we cannot get a raw key down/up notification for CAPSlock, so hack that here
// ALSO : Emulator initially sets CAPS state based on a user preference, but sync to system state if user changes it
static bool modified_caps_lock = false;
int modifiers = glutGetModifiers();
if (!c_keys_is_shifted()) {
if (modifiers & GLUT_ACTIVE_SHIFT) {
modified_caps_lock = true;
use_system_caps_lock = true;
caps_lock = true;
} else if (modified_caps_lock) {
} else if (use_system_caps_lock) {
caps_lock = false;
}
}

View File

@ -43,5 +43,17 @@ typedef struct A2Color_s {
*/
extern A2Color_s colormap[];
#if !VIDEO_OPENGL
// X11 scaling ...
typedef enum a2_video_mode_t {
VIDEO_FULLSCREEN = 0,
VIDEO_1X,
VIDEO_2X,
NUM_VIDOPTS
} a2_video_mode_t;
extern a2_video_mode_t a2_video_mode;
#endif
#endif /* !A2_VIDEO_H */

View File

@ -1212,19 +1212,13 @@ void vm_initialize(void) {
}
void vm_reinitializeAudio(void) {
#ifdef AUDIO_ENABLED
speaker_setVolumeZeroToTen(sound_volume);
#if !MOBILE_DEVICE
#warning TODO FIXME : disentangle Mockingboard volume from speaker volume on Desktop ...
MB_SetVolumeZeroToTen(sound_volume);
#endif
#endif
for (unsigned int i = 0xC030; i < 0xC040; i++) {
cpu65_vmem_r[i] = cpu65_vmem_w[i] =
#ifdef AUDIO_ENABLED
(sound_volume > 0) ? speaker_toggle :
#endif
speaker_toggle;
#else
ram_nop;
#endif
}
#warning TODO FIXME ... should unset MB/Phasor hooks if volume is zero ...
}