From 1fa28340e8e8d8459456469c72f7156ccf1493f1 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 23 Apr 2023 14:54:29 +0100 Subject: [PATCH] Support user preferences on Windows. This is done using basically the same methods as on Unix, and just translating the system calls in save_prefs to a different API. --- puzzles.but | 8 +-- windows.c | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 4 deletions(-) diff --git a/puzzles.but b/puzzles.but index 1d87ffb..a1bc600 100644 --- a/puzzles.but +++ b/puzzles.but @@ -179,10 +179,10 @@ solving it yourself after seeing the answer, you can just press Undo. \dt \i\e{Preferences} -\dd Where supported (currently only on Unix), brings up a dialog -allowing you to configure personal preferences about a particular -game. Some of these preferences will be specific to a particular game; -others will be common to all games. +\dd Where supported (currently only on Windows and Unix), brings up a +dialog allowing you to configure personal preferences about a +particular game. Some of these preferences will be specific to a +particular game; others will be common to all games. \lcont{ diff --git a/windows.c b/windows.c index 5735850..d616a71 100644 --- a/windows.c +++ b/windows.c @@ -7,6 +7,7 @@ #ifndef NO_HTMLHELP #include #endif /* NO_HTMLHELP */ +#include #include #include @@ -35,6 +36,7 @@ #define IDM_LOAD 0x00F0 #define IDM_PRINT 0x0100 #define IDM_PRESETS 0x0110 +#define IDM_PREFS 0x0120 #define IDM_GAMES 0x0300 #define IDM_KEYEMUL 0x0400 @@ -119,6 +121,8 @@ void debug_printf(const char *fmt, ...) (WS_MAXIMIZEBOX | WS_OVERLAPPED)) static void new_game_size(frontend *fe, float scale); +static void load_prefs(midend *me); +static char *save_prefs(midend *me); struct font { HFONT font; @@ -935,6 +939,7 @@ void print(frontend *fe) game_params *params; nme = midend_new(NULL, fe->game, NULL, NULL); + load_prefs(nme); /* * Set the non-interactive mid-end to have the same @@ -1442,6 +1447,7 @@ static midend *midend_for_new_game(frontend *fe, const game *cgame, if (!arg) { if (me) midend_free(me); me = midend_new(fe, cgame, &win_drawing, fe); + load_prefs(me); midend_new_game(me); } else { FILE *fp; @@ -1482,6 +1488,7 @@ static midend *midend_for_new_game(frontend *fe, const game *cgame, if (!err_load) { if (me) midend_free(me); me = midend_new(fe, loadgame, &win_drawing, fe); + load_prefs(me); err_load = midend_deserialise(me, savefile_read, fp); } } else { @@ -1494,6 +1501,7 @@ static midend *midend_for_new_game(frontend *fe, const game *cgame, */ if (me) midend_free(me); me = midend_new(fe, cgame, &win_drawing, fe); + load_prefs(me); err_param = midend_game_id(me, arg); if (!err_param) { midend_new_game(me); @@ -1719,6 +1727,8 @@ static int fe_set_midend(frontend *fe, midend *me) AppendMenu(menu, MF_ENABLED, IDM_SOLVE, TEXT("Sol&ve")); } AppendMenu(menu, MF_SEPARATOR, 0, 0); + AppendMenu(menu, MF_ENABLED, IDM_PREFS, TEXT("Pre&ferences")); + AppendMenu(menu, MF_SEPARATOR, 0, 0); AppendMenu(menu, MF_ENABLED, IDM_QUIT, TEXT("E&xit")); menu = CreateMenu(); AppendMenu(bar, MF_ENABLED|MF_POPUP, (UINT_PTR)menu, TEXT("&Help")); @@ -2488,6 +2498,138 @@ static void update_type_menu_tick(frontend *fe) DrawMenuBar(fe->hwnd); } +static char *prefs_dir(void) +{ + const char *var; + if ((var = getenv("APPDATA")) != NULL) { + size_t size = strlen(var) + 80; + char *dir = snewn(size, char); + sprintf(dir, "%s\\Simon Tatham's Portable Puzzle Collection", var); + return dir; + } + return NULL; +} + +static char *prefs_path_general(const game *game, const char *suffix) +{ + char *dir, *path; + + dir = prefs_dir(); + if (!dir) + return NULL; + + path = make_prefs_path(dir, "\\", game, suffix); + + sfree(dir); + return path; +} + +static char *prefs_path(const game *game) +{ + return prefs_path_general(game, ".conf"); +} + +static char *prefs_tmp_path(const game *game) +{ + return prefs_path_general(game, ".tmp"); +} + +static void load_prefs(midend *me) +{ + const game *game = midend_which_game(me); + char *path = prefs_path(game); + if (!path) + return; + FILE *fp = fopen(path, "r"); + if (!fp) + return; + const char *err = midend_load_prefs(me, savefile_read, fp); + fclose(fp); + if (err) + fprintf(stderr, "Unable to load preferences file %s:\n%s\n", + path, err); + sfree(path); +} + +static char *save_prefs(midend *me) +{ + const game *game = midend_which_game(me); + char *dir_path = prefs_dir(); + char *file_path = prefs_path(game); + char *tmp_path = prefs_tmp_path(game); + HANDLE fh; + FILE *fp; + bool cleanup_dir = false, cleanup_tmpfile = false; + char *err = NULL; + + if (!dir_path || !file_path || !tmp_path) { + sprintf(err = snewn(256, char), + "Unable to save preferences:\n" + "Could not determine pathname for configuration files"); + goto out; + } + + if (!CreateDirectory(dir_path, NULL)) { + /* Ignore errors while trying to make the directory. It may + * well already exist, and even if we got some error code + * other than EEXIST, it's still worth at least _trying_ to + * make the file inside it, and see if that goes wrong. */ + } else { + cleanup_dir = true; + } + + fh = CreateFile(tmp_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + if (fh == INVALID_HANDLE_VALUE) { + char *os_err = geterrstr(); + sprintf(err = snewn(256 + strlen(tmp_path) + strlen(os_err), char), + "Unable to save preferences:\n" + "Unable to create file '%s':\n%s", tmp_path, os_err); + sfree(os_err); + goto out; + } else { + cleanup_tmpfile = true; + } + + fp = _fdopen(_open_osfhandle((intptr_t)fh, 0), "w"); + SetLastError(0); + midend_save_prefs(me, savefile_write, fp); + fclose(fp); + if (GetLastError()) { + char *os_err = geterrstr(); + sprintf(err = snewn(80 + strlen(tmp_path) + strlen(os_err), char), + "Unable to write file '%s':\n%s", tmp_path, os_err); + sfree(os_err); + goto out; + } + + if (MoveFileEx(tmp_path, file_path, MOVEFILE_REPLACE_EXISTING) < 0) { + char *os_err = geterrstr(); + sprintf(err = snewn(256 + strlen(tmp_path) + strlen(file_path) + + strlen(os_err), char), + "Unable to save preferences:\n" + "Unable to rename '%s' to '%s':\n%s", tmp_path, file_path, + os_err); + sfree(os_err); + goto out; + } else { + cleanup_dir = false; + cleanup_tmpfile = false; + } + + out: + if (cleanup_tmpfile) { + if (!DeleteFile(tmp_path)) { /* can't do anything about this */ } + } + if (cleanup_dir) { + if (!RemoveDirectory(dir_path)) { /* can't do anything about this */ } + } + sfree(dir_path); + sfree(file_path); + sfree(tmp_path); + return err; +} + static void update_copy_menu_greying(frontend *fe) { UINT enable = (midend_can_format_as_text_now(fe->me) ? @@ -2582,6 +2724,16 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, if (get_config(fe, CFG_PRINT)) print(fe); break; + case IDM_PREFS: + if (get_config(fe, CFG_PREFS)) { + char *prefs_err = save_prefs(fe->me); + if (prefs_err) { + MessageBox(fe->hwnd, prefs_err, "Error saving preferences", + MB_ICONERROR | MB_OK); + sfree(prefs_err); + } + } + break; case IDM_ABOUT: about(fe); break;