blob: 4b5a205925256b9b88208ace1acf0e37dd8061fc [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "prefs.h"
#include "pref_tables.h"
#include "status_types.h"
#include "defaults.h"
#include "log.h"
#include "file.h"
#include "datafile.h"
#include "parse.h"
#define PREFS_COMMENT_CHARACTER '#'
#define PREFS_MAGIC_NUMBER 0x4005
void
setStatusFields (const unsigned char *fields) {
if (!statusFieldsSet) {
if (fields) {
unsigned int index = 0;
while (index < (ARRAY_COUNT(prefs.statusFields) - 1)) {
unsigned char field = fields[index];
if (field == sfEnd) break;
prefs.statusFields[index++] = field;
}
statusFieldsSet = 1;
}
}
}
static void
setStatusStyle (unsigned char style) {
static const unsigned char styleNone[] = {
sfEnd
};
static const unsigned char styleAlva[] = {
sfAlphabeticCursorCoordinates, sfAlphabeticWindowCoordinates, sfStateLetter, sfEnd
};
static const unsigned char styleTieman[] = {
sfCursorAndWindowColumn2, sfCursorAndWindowRow2, sfStateDots, sfEnd
};
static const unsigned char stylePB80[] = {
sfWindowRow, sfEnd
};
static const unsigned char styleConfigurable[] = {
sfGeneric, sfEnd
};
static const unsigned char styleMDV[] = {
sfWindowCoordinates2, sfEnd
};
static const unsigned char styleVoyager[] = {
sfWindowRow, sfCursorRow, sfCursorColumn, sfEnd
};
static const unsigned char styleTime[] = {
sfTime, sfEnd
};
static const unsigned char *const styleTable[] = {
styleNone, styleAlva, styleTieman, stylePB80,
styleConfigurable, styleMDV, styleVoyager, styleTime
};
static const unsigned char styleCount = ARRAY_COUNT(styleTable);
if (style < styleCount) {
const unsigned char *fields = styleTable[style];
if (*fields != sfEnd) setStatusFields(fields);
}
}
static int
comparePreferenceNames (const char *name1, const char *name2) {
return strcmp(name1, name2);
}
static int
sortPreferenceDefinitions (const void *element1, const void *element2) {
const PreferenceDefinition *const *pref1 = element1;
const PreferenceDefinition *const *pref2 = element2;
return comparePreferenceNames((*pref1)->name, (*pref2)->name);
}
static int
searchPreferenceDefinition (const void *target, const void *element) {
const char *name = target;
const PreferenceDefinition *const *pref = element;
return comparePreferenceNames(name, (*pref)->name);
}
static const PreferenceDefinition *
findPreferenceDefinition (const char *name) {
static const PreferenceDefinition **sortedDefinitions = NULL;
if (!sortedDefinitions) {
if (!(sortedDefinitions = malloc(ARRAY_SIZE(sortedDefinitions, preferenceDefinitionCount)))) {
logMallocError();
return NULL;
}
for (unsigned int index=0; index<preferenceDefinitionCount; index+=1) {
sortedDefinitions[index] = &preferenceDefinitionTable[index];
}
qsort(
sortedDefinitions, preferenceDefinitionCount,
sizeof(*sortedDefinitions), sortPreferenceDefinitions
);
}
{
const PreferenceDefinition *const *pref = bsearch(
name, sortedDefinitions, preferenceDefinitionCount,
sizeof(*sortedDefinitions), searchPreferenceDefinition
);
if (pref) return *pref;
}
return NULL;
}
static int
sortPreferenceAliases (const void *element1, const void *element2) {
const PreferenceAlias *const *alias1 = element1;
const PreferenceAlias *const *alias2 = element2;
return comparePreferenceNames((*alias1)->oldName, (*alias2)->oldName);
}
static int
searchPreferenceAlias (const void *target, const void *element) {
const char *name = target;
const PreferenceAlias *const *alias = element;
return comparePreferenceNames(name, (*alias)->oldName);
}
static const PreferenceAlias *
findPreferenceAlias (const char *name) {
static const PreferenceAlias **sortedAliases = NULL;
if (!sortedAliases) {
if (!(sortedAliases = malloc(ARRAY_SIZE(sortedAliases, preferenceAliasCount)))) {
logMallocError();
return NULL;
}
for (unsigned int index=0; index<preferenceAliasCount; index+=1) {
sortedAliases[index] = &preferenceAliasTable[index];
}
qsort(
sortedAliases, preferenceAliasCount,
sizeof(*sortedAliases), sortPreferenceAliases
);
}
{
const PreferenceAlias *const *alias = bsearch(
name, sortedAliases, preferenceAliasCount,
sizeof(*sortedAliases), searchPreferenceAlias
);
if (alias) return *alias;
}
return NULL;
}
const PreferenceDefinition *
findPreference (const char *name) {
while (name) {
{
const PreferenceDefinition *pref = findPreferenceDefinition(name);
if (pref) return pref;
}
{
const PreferenceAlias *alias = findPreferenceAlias(name);
if (!alias) break;
name = alias->newName;
}
}
if (name) logMessage(LOG_WARNING, "unknown preference: %s", name);
return NULL;
}
static void
resetPreference (const PreferenceDefinition *pref) {
if (pref->settingCount) {
memset(pref->setting, pref->defaultValue, pref->settingCount);
} else {
*pref->setting = pref->defaultValue;
}
if (pref->encountered) *pref->encountered = 0;
}
void
resetPreferences (void) {
memset(&prefs, 0, sizeof(prefs));
prefs.magic[0] = PREFS_MAGIC_NUMBER & 0XFF;
prefs.magic[1] = PREFS_MAGIC_NUMBER >> 8;
prefs.version = 6;
{
const PreferenceDefinition *pref = preferenceDefinitionTable;
const PreferenceDefinition *end = pref + preferenceDefinitionCount;
while (pref < end) resetPreference(pref++);
}
}
static const char *
getSettingName (const PreferenceDefinition *pref, unsigned char index) {
const PreferenceStringTable *names = pref->settingNames;
if (!names) return NULL;
if (index >= names->count) return NULL;
return names->table[index];
}
static int
changePreferenceSetting (
const char *name, const char *operand,
unsigned char *setting, const PreferenceStringTable *names
) {
if (names) {
for (unsigned int index=0; index<names->count; index+=1) {
const char *name = names->table[index];
if (!name) continue;
if (strcmp(operand, name) == 0) {
*setting = index;
return 1;
}
}
}
{
int value;
if (isInteger(&value, operand)) {
unsigned char maximum = names? (names->count - 1): 0XFF;
if ((value >= 0) && (value <= maximum)) {
*setting = value;
return 1;
}
}
}
logMessage(LOG_WARNING, "invalid preference setting: %s %s", name, operand);
return 0;
}
int
setPreference (char *string) {
const char *name;
{
static const char delimiters[] = {
' ', '\t', PARAMETER_ASSIGNMENT_CHARACTER, 0
};
name = strtok(string, delimiters);
}
if (name) {
const PreferenceDefinition *pref = findPreference(name);
if (pref) {
if (pref->encountered) *pref->encountered = 1;
static const char delimiters[] = " \t";
const char *operand;
if (pref->settingCount) {
unsigned char count = pref->settingCount;
unsigned char *setting = pref->setting;
while (count) {
if ((operand = strtok(NULL, delimiters))) {
if (changePreferenceSetting(name, operand, setting, pref->settingNames)) {
setting += 1;
count -= 1;
continue;
}
}
*setting = 0;
break;
}
} else if (!(operand = strtok(NULL, delimiters))) {
logMessage(LOG_WARNING, "missing preference setting: %s", name);
} else if (!changePreferenceSetting(name, operand, pref->setting, pref->settingNames)) {
}
}
} else {
logMessage(LOG_WARNING, "missing preference name");
}
return 1;
}
char *
makePreferencesFilePath (const char *name) {
if (!name) name = PREFERENCES_FILE;
return makeUpdatablePath(name);
}
static int
processPreferenceLine (const LineHandlerParameters *parameters) {
char *line = parameters->line.text;
while (isspace(*line)) line += 1;
if (!*line) return 1;
if (*line == PREFS_COMMENT_CHARACTER) return 1;
return setPreference(line);
}
int
loadPreferencesFile (const char *path) {
int ok = 0;
logMessage(LOG_DEBUG, "loading preferences file: %s", path);
FILE *file = openDataFile(path, "rb", 1);
if (file) {
PreferenceSettings newPreferences;
size_t length = fread(&newPreferences, 1, sizeof(newPreferences), file);
if (ferror(file)) {
logMessage(LOG_ERR, "%s: %s: %s",
gettext("cannot read preferences file"), path, strerror(errno));
} else if ((length < 40) ||
(newPreferences.magic[0] != (PREFS_MAGIC_NUMBER & 0XFF)) ||
(newPreferences.magic[1] != (PREFS_MAGIC_NUMBER >> 8))) {
fclose(file);
if ((file = openDataFile(path, "r", 1))) {
resetPreferences();
if (processLines(file, processPreferenceLine, NULL)) ok = 1;
}
} else {
prefs = newPreferences;
ok = 1;
{
const PreferenceDefinition *pref = preferenceDefinitionTable;
const PreferenceDefinition *end = pref + preferenceDefinitionCount;
const unsigned char *from = prefs.magic;
const unsigned char *to = from + length;
while (pref < end) {
unsigned char count = pref->settingCount;
if (!count) count = 1;
if ((pref->setting < from) || ((pref->setting + count) > to)) {
resetPreference(pref);
}
pref += 1;
}
}
if (length < (prefs.statusFields + sizeof(prefs.statusFields) - prefs.magic)) {
setStatusStyle(prefs.expandCurrentWord);
} else {
statusFieldsSet = 1;
}
if (prefs.version == 0) {
prefs.version += 1;
prefs.pcmVolume = DEFAULT_PCM_VOLUME;
prefs.midiVolume = DEFAULT_MIDI_VOLUME;
prefs.fmVolume = DEFAULT_FM_VOLUME;
}
if (prefs.version == 1) {
prefs.version += 1;
prefs.sayLineMode = DEFAULT_SAY_LINE_MODE;
prefs.autospeak = DEFAULT_AUTOSPEAK;
}
if (prefs.version == 2) {
prefs.version += 1;
prefs.autorepeatEnabled = DEFAULT_AUTOREPEAT_ENABLED;
prefs.longPressTime = DEFAULT_LONG_PRESS_TIME;
prefs.autorepeatInterval = DEFAULT_AUTOREPEAT_INTERVAL;
prefs.screenCursorVisibleTime *= 4;
prefs.screenCursorInvisibleTime *= 4;
prefs.attributesVisibleTime *= 4;
prefs.attributesInvisibleTime *= 4;
prefs.capitalsVisibleTime *= 4;
prefs.capitalsInvisibleTime *= 4;
}
if (prefs.version == 3) {
prefs.version += 1;
prefs.autorepeatPanning = DEFAULT_AUTOREPEAT_PANNING;
}
if (prefs.version == 4) {
prefs.version += 1;
prefs.touchSensitivity = DEFAULT_TOUCH_SENSITIVITY;
}
if (prefs.version == 5) {
prefs.version += 1;
prefs.expandCurrentWord = DEFAULT_EXPAND_CURRENT_WORD;
}
}
if (file) {
fclose(file);
file = NULL;
}
}
return ok;
}
static int
putPreferenceComment (FILE *file, const PreferenceDefinition *pref) {
if (fprintf(file, "\n%c %s", PREFS_COMMENT_CHARACTER, pref->name) < 0) return 0;
if (pref->settingCount) {
if (fprintf(file, "[%u]", pref->settingCount) < 0) return 0;
}
if (fputs(": ", file) == EOF) return 0;
const char *name = getSettingName(pref, pref->defaultValue);
if (name) {
if (fputs(name, file) == EOF) return 0;
} else {
if (fprintf(file, "%u", pref->defaultValue) < 0) return 0;
}
if (pref->settingNames) {
if (fputs(" {", file) == EOF) return 0;
unsigned char count = pref->settingNames->count;
int first = 1;
for (unsigned char index=0; index<count; index+=1) {
const char *name = getSettingName(pref, index);
if (name) {
if (first) {
first = 0;
} else if (fputc(' ', file) == EOF) {
return 0;
}
if (fputs(name, file) == EOF) return 0;
} else {
logMessage(LOG_WARNING,
"unnamed preference setting: %s: %u",
pref->name, index
);
}
}
if (fputc('}', file) == EOF) return 0;
}
if (fputc('\n', file) == EOF) return 0;
return 1;
}
static int
putSetting (FILE *file, const PreferenceDefinition *pref, unsigned char setting) {
if (fputc(' ', file) == EOF) return 0;
const char *name = getSettingName(pref, setting);
if (name) {
if (fputs(name, file) == EOF) return 0;
} else {
if (fprintf(file, "%u", setting) < 0) return 0;
}
return 1;
}
static int
putPreference (FILE *file, const PreferenceDefinition *pref) {
if (pref->dontSave) return 1;
if (!putPreferenceComment(file, pref)) return 0;
if (fputs(pref->name, file) == EOF) return 0;
if (pref->settingCount) {
unsigned char count = pref->settingCount;
unsigned char *setting = pref->setting;
while (count-- && *setting) {
if (!putSetting(file, pref, *setting++)) return 0;
}
} else if (!putSetting(file, pref, *pref->setting)) {
return 0;
}
if (fputs("\n", file) == EOF) return 0;
return 1;
}
static int
putPreferences (FILE *file) {
const PreferenceDefinition *pref = preferenceDefinitionTable;
const PreferenceDefinition *const end = pref + preferenceDefinitionCount;
while (pref < end) {
if (!putPreference(file, pref)) return 0;
pref += 1;
}
return 1;
}
static int
putHeader (FILE *file) {
fprintf(file,
"%c %s Preferences File\n",
PREFS_COMMENT_CHARACTER, PACKAGE_NAME
);
return !ferror(file);
}
int
savePreferencesFile (const char *path) {
int ok = 0;
FILE *file = openDataFile(path, "w", 0);
if (file) {
if (putHeader(file)) {
if (putPreferences(file)) {
ok = 1;
}
}
if (!ok) {
if (!ferror(file)) errno = EIO;
logMessage(LOG_ERR,
"%s: %s: %s",
gettext("cannot write to preferences file"),
path, strerror(errno)
);
}
fclose(file);
}
return ok;
}