/* * Copyright (c) 2021-2024 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include "amend.h" #include "settings.h" #include "util.h" enum { SETTING_STRING, SETTING_SHORT, SETTING_USHORT, SETTING_LONG, SETTING_PASSWORD, SETTING_BOOL }; enum { SETTING_FLAG_ALLOW_BLANK = (1 << 0) }; struct setting { char name[32]; short type; long flags; long min; long max; char sdefault[32]; short ditl_id; unsigned long offset; unsigned long size; char password_storage[256]; } settings_defs[] = { { "Author Name", SETTING_STRING, 0, 1, 0, "unknown", SETTINGS_AUTHOR_NAME_ID, offsetof(struct settings, author), member_size(struct settings, author) }, { "Author E-Mail", SETTING_STRING, 0, 1, 0, "unknown@example.com", SETTINGS_AUTHOR_EMAIL_ID, offsetof(struct settings, author_email), member_size(struct settings, author_email) }, { "Tab Stop Width", SETTING_USHORT, 0, 1, 32, "4", SETTINGS_TABSTOP_ID, offsetof(struct settings, tabstop), member_size(struct settings, tabstop) }, { "Max Amendments to Load", SETTING_USHORT, 0, 1, 65535, "100", SETTINGS_MAX_AMENDMENTS_ID, offsetof(struct settings, max_amendments), member_size(struct settings, max_amendments) }, }; void settings_find_prefs_folder(short *vrefnum, long *dirid); void settings_load(void); void settings_load(void) { HParamBlockRec pb = { 0 }; struct settings tsettings = { 0 }; char fn[] = SETTINGS_FILENAME; char *res; short error, vrefnum, prefrefnum; long len, rlen, dirid; Handle h; bool use_defaults = true, show_edit = false; GetSystemSubfolder(kPreferencesFolderType, true, &vrefnum, &dirid); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.ioParam.ioPermssn = fsRdPerm; pb.fileParam.ioDirID = dirid; error = PBHOpen(&pb, false); if (error) { if (error != fnfErr) panic("Failed reading preferences file: %d", error); settings_edit(use_defaults); return; } rlen = len = sizeof(tsettings); FSRead(pb.ioParam.ioRefNum, &rlen, &tsettings); while (tsettings.version < SETTINGS_VERSION) { switch (tsettings.version) { case 1: { /* extended author, added author_email */ struct settings_v1 s1 = { 0 }; /* re-read prefs file into settings_v1 struct */ rlen = len = sizeof(s1); SetFPos(pb.ioParam.ioRefNum, fsFromStart, 0); FSRead(pb.ioParam.ioRefNum, &rlen, &s1); strlcpy(tsettings.author, s1.author, sizeof(tsettings.author)); tsettings.tabstop = s1.tabstop; tsettings.max_amendments = s1.max_amendments; /* save but also let the user edit the new e-mail field */ settings_save(&tsettings); show_edit = true; break; } case 2: break; default: FSClose(pb.ioParam.ioRefNum); if (ask("Unknown preferences version (%d), reset?", tsettings.version)) { settings_edit(true); return; } else ExitToShell(); } tsettings.version++; } FSClose(pb.ioParam.ioRefNum); settings = tsettings; if (show_edit) settings_edit(false); } void settings_save(struct settings *tsettings) { HParamBlockRec pb = { 0 }; char fn[] = SETTINGS_FILENAME; short error, vrefnum, prefrefnum; long dirid, len; Handle h; GetSystemSubfolder(kPreferencesFolderType, true, &vrefnum, &dirid); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.fileParam.ioDirID = dirid; error = PBHCreate(&pb, false); if (error && error != dupFNErr) goto create_failed; if (!error) { memset(&pb, 0, sizeof(pb)); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.fileParam.ioDirID = dirid; if ((error = PBHGetFInfo(&pb, false))) goto create_failed; pb.fileParam.ioDirID = dirid; /* required after PBHGetFInfo */ pb.fileParam.ioFlFndrInfo.fdType = 'pref'; pb.fileParam.ioFlFndrInfo.fdCreator = AMEND_CREATOR; if ((error = PBHSetFInfo(&pb, false))) goto create_failed; memset(&pb, 0, sizeof(pb)); pb.ioParam.ioVRefNum = vrefnum; PBFlushVol(&pb, false); } memset(&pb, 0, sizeof(pb)); pb.ioParam.ioNamePtr = (StringPtr)&fn; pb.ioParam.ioVRefNum = vrefnum; pb.ioParam.ioPermssn = fsWrPerm; pb.fileParam.ioDirID = dirid; error = PBHOpen(&pb, false); if (error) goto create_failed; len = sizeof(struct settings); tsettings->version = SETTINGS_VERSION; FSWrite(pb.ioParam.ioRefNum, &len, tsettings); FSClose(pb.ioParam.ioRefNum); return; create_failed: panic("Failed creating preferences file: %d", error); } void settings_edit(bool use_defaults) { struct settings tsettings = settings; struct setting *s; DialogTHndl dlgh; DialogPtr dlg; Handle ihandle; Rect irect; size_t size, n, m, slen; long lval; bool bval, save = false; short hit, itype, ret; char *sdata, *strval; Str255 stmp; /* center dialog in screen */ dlgh = (DialogTHndl)xGetResource('DLOG', SETTINGS_DLOG_ID); HLock(dlgh); center_in_screen((**dlgh).boundsRect.right - (**dlgh).boundsRect.left, (**dlgh).boundsRect.bottom - (**dlgh).boundsRect.top, true, &(**dlgh).boundsRect); HUnlock(dlgh); if ((dlg = GetNewDialog(SETTINGS_DLOG_ID, nil, (WindowPtr)-1)) == NULL) panic("Can't find settings DLOG %d", SETTINGS_DLOG_ID); for (n = 0; n < nitems(settings_defs); n++) { s = &settings_defs[n]; sdata = (char *)&tsettings + s->offset; GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect); switch (s->type) { case SETTING_PASSWORD: PasswordDialogFieldFilterSetup(s->ditl_id, (char *)s->password_storage, sizeof(s->password_storage)); strval = (use_defaults ? (char *)s->sdefault : sdata); size = strlen(strval); if (size >= sizeof(s->password_storage) - 1) size = 0; /* show a masked value */ stmp[0] = size; for (m = 1; m <= size; m++) stmp[m] = '¥'; SetIText(ihandle, stmp); /* but store the actual password */ memcpy(s->password_storage, strval, size); s->password_storage[size] = '\0'; break; case SETTING_STRING: strval = (use_defaults ? s->sdefault : sdata); memcpy(stmp, strval, sizeof(stmp)); stmp[sizeof(stmp) - 1] = '\0'; CtoPstr(stmp); SetIText(ihandle, stmp); break; case SETTING_SHORT: case SETTING_USHORT: case SETTING_LONG: if (use_defaults) { memcpy(stmp, s->sdefault, sizeof(stmp)); stmp[sizeof(stmp) - 1] = '\0'; } else if (s->type == SETTING_SHORT) { snprintf((char *)stmp, sizeof(stmp), "%d", BYTES_TO_SHORT(sdata[0], sdata[1])); } else if (s->type == SETTING_USHORT) { snprintf((char *)stmp, sizeof(stmp), "%u", BYTES_TO_SHORT(sdata[0], sdata[1])); } else if (s->type == SETTING_LONG) { snprintf((char *)stmp, sizeof(stmp), "%ld", BYTES_TO_LONG(sdata[0], sdata[1], sdata[2], sdata[3])); } CtoPstr(stmp); SetIText(ihandle, stmp); break; case SETTING_BOOL: SetCtlValue(ihandle, (use_defaults ? s->sdefault[0] : sdata[0]) == 1); break; default: panic("unknown setting type %d", s->type); } } ShowWindow(dlg); GetDItem(dlg, ok, &itype, &ihandle, &irect); get_input: outline_button((ControlHandle)ihandle); ModalDialog(PasswordDialogFieldFilter, &hit); switch (hit) { case -1: case cancel: goto settings_done; case OK: goto verify; default: goto get_input; } verify: for (n = 0; n < nitems(settings_defs); n++) { s = &settings_defs[n]; sdata = (char *)&tsettings + s->offset; GetDItem(dlg, s->ditl_id, &itype, &ihandle, &irect); if (s->type == SETTING_PASSWORD) { memcpy((char *)&stmp, s->password_storage, sizeof(stmp)); } else if (s->type == SETTING_BOOL) { snprintf((char *)&stmp, sizeof(stmp), "%d", GetCtlValue(ihandle)); } else { GetIText(ihandle, stmp); PtoCstr(stmp); } switch (s->type) { case SETTING_STRING: case SETTING_PASSWORD: slen = strlen((char *)stmp); if (slen == 0 && (s->flags & SETTING_FLAG_ALLOW_BLANK)) { /* ok */ } else { if (s->min && s->max && s->min == s->max && slen != s->min) { warn("%s must be %ld character%s", s->name, s->min, s->min == 1 ? "" : "s"); goto get_input; } if (s->min && slen < s->min) { warn("%s is too short (minimum %ld)", s->name, s->min); goto get_input; } if (s->max && slen > s->max) { warn("%s is too long (maximum %ld)", s->name, s->max); goto get_input; } } if (save) { memset(sdata, 0, s->size); strlcpy(sdata, (char *)stmp, s->size); } break; case SETTING_SHORT: case SETTING_USHORT: case SETTING_LONG: lval = atol((char *)stmp); if (lval < s->min) { if (s->type == SETTING_USHORT) warn("%s must be at least %lu", s->name, s->min); else warn("%s must be at least %ld", s->name, s->min); goto get_input; } if (lval > s->max) { if (s->type == SETTING_USHORT) warn("%s must be less than %lu", s->name, s->max); else warn("%s must be less than %ld", s->name, s->max); goto get_input; } if (save) { if (s->type == SETTING_LONG) { sdata[0] = (lval >> 24) & 0xff; sdata[1] = (lval >> 16) & 0xff; sdata[2] = (lval >> 8) & 0xff; sdata[3] = lval & 0xff; } else { sdata[0] = (lval >> 8) & 0xff; sdata[1] = lval & 0xff; } } break; case SETTING_BOOL: if (save) sdata[0] = (stmp[0] == '1'); break; } } if (!save) { save = true; goto verify; } /* all validated ok and fields written */ settings_save(&tsettings); settings = tsettings; settings_done: PasswordDialogFieldFinish(); DisposeDialog(dlg); ReleaseResource(dlgh); }