diff --git a/config.c b/config.c index f2272068..a94a4cca 100644 --- a/config.c +++ b/config.c @@ -1987,7 +1987,7 @@ void setup_config_box(struct controlbox *b, bool midsession, sshrawlogname, 'r', I(LGTYP_SSHRAW)); } ctrl_filesel(s, "Log file name:", 'f', - NULL, true, "Select session log file name", + FILTER_ALL_FILES, true, "Select session log file name", HELPCTX(logging_filename), conf_filesel_handler, I(CONF_logfilename)); ctrl_text(s, "(Log file name can contain &Y, &M, &D for date," @@ -2932,7 +2932,7 @@ void setup_config_box(struct controlbox *b, bool midsession, conf_filesel_handler, I(CONF_keyfile)); ctrl_filesel(s, "Certificate to use with the private key " "(optional):", 'e', - NULL, false, "Select certificate file", + FILTER_ALL_FILES, false, "Select certificate file", HELPCTX(ssh_auth_cert), conf_filesel_handler, I(CONF_detached_cert)); diff --git a/dialog.c b/dialog.c index 511331d3..0ad34d25 100644 --- a/dialog.c +++ b/dialog.c @@ -392,7 +392,7 @@ dlgcontrol *ctrl_draglist(struct controlset *s, const char *label, } dlgcontrol *ctrl_filesel(struct controlset *s, const char *label, - char shortcut, FILESELECT_FILTER_TYPE filter, + char shortcut, FilereqFilter filter, bool write, const char *title, HelpCtx helpctx, handler_fn handler, intorptr context) { diff --git a/dialog.h b/dialog.h index 19e0695d..4e8e2ae1 100644 --- a/dialog.h +++ b/dialog.h @@ -348,21 +348,8 @@ struct dlgcontrol { * files the file selector would do well to only show .PPK * files (on those systems where this is the chosen * extension). - * - * The precise contents of `filter' are platform-defined, - * unfortunately. The special value NULL means `all files' - * and is always a valid fallback. - * - * Unlike almost all strings in this structure, this value - * is NOT expected to require freeing (although of course - * you can always use ctrl_alloc if you do need to create - * one on the fly). This is because the likely mode of use - * is to define string constants in a platform-specific - * header file, and directly reference those. Or worse, a - * particular platform might choose to cast integers into - * this pointer type... */ - FILESELECT_FILTER_TYPE filter; + FilereqFilter filter; /* * Some systems like to know whether a file selector is * choosing a file to read or one to write (and possibly @@ -552,7 +539,7 @@ dlgcontrol *ctrl_draglist(struct controlset *, const char *label, char shortcut, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_filesel(struct controlset *, const char *label, - char shortcut, FILESELECT_FILTER_TYPE filter, + char shortcut, FilereqFilter filter, bool write, const char *title, HelpCtx helpctx, handler_fn handler, intorptr context); dlgcontrol *ctrl_fontsel(struct controlset *, const char *label, diff --git a/putty.h b/putty.h index bd0160fe..fdcd6ca3 100644 --- a/putty.h +++ b/putty.h @@ -4,6 +4,21 @@ #include /* for wchar_t */ #include /* for INT_MAX */ +/* + * Declared before including platform.h, because that will refer to it + * + * An enum for different types of file that a GUI file requester might + * focus on. (Our requesters never _insist_ on a particular file type + * or extension - there's always an escape hatch to select any file + * you want - but the default can be configured.) + */ +typedef enum { + FILTER_ALL_FILES, /* no particular focus */ + FILTER_KEY_FILES, /* .ppk */ + FILTER_DYNLIB_FILES, /* whatever the host platform uses as shared libs */ + FILTER_SOUND_FILES, /* whatever kind of sound file we can use as bell */ +} FilereqFilter; + #include "defs.h" #include "platform.h" #include "network.h" diff --git a/ssh/ca-config.c b/ssh/ca-config.c index 8c180b36..ff4c66dc 100644 --- a/ssh/ca-config.c +++ b/ssh/ca-config.c @@ -453,9 +453,10 @@ void setup_ca_config_box(struct controlbox *b) P(st), P(NULL)); c->column = 0; st->ca_pubkey_edit = c; - c = ctrl_filesel(s, "Read from file", NO_SHORTCUT, NULL, false, - "Select public key file of certification authority", - HELPCTX(ssh_kex_cert), ca_pubkey_file_handler, P(st)); + c = ctrl_filesel( + s, "Read from file", NO_SHORTCUT, FILTER_ALL_FILES, false, + "Select public key file of certification authority", + HELPCTX(ssh_kex_cert), ca_pubkey_file_handler, P(st)); c->fileselect.just_button = true; c->align_next_to = st->ca_pubkey_edit; c->column = 1; diff --git a/unix/dialog.c b/unix/dialog.c index fa645b3a..0838ff03 100644 --- a/unix/dialog.c +++ b/unix/dialog.c @@ -1739,6 +1739,9 @@ static void filefont_clicked(GtkButton *button, gpointer data) struct uctrl *uc = dlg_find_bywidget(dp, GTK_WIDGET(button)); if (uc->ctrl->type == CTRL_FILESELECT) { + /* + * FIXME: do something about uc->ctrl->fileselect.filter + */ #ifdef USE_GTK_FILE_CHOOSER_DIALOG GtkWidget *filechoose = gtk_file_chooser_dialog_new( uc->ctrl->fileselect.title, GTK_WINDOW(dp->window), diff --git a/unix/platform.h b/unix/platform.h index 44c7986c..670febd7 100644 --- a/unix/platform.h +++ b/unix/platform.h @@ -84,10 +84,6 @@ typedef void *HelpCtx; #define NULL_HELPCTX ((HelpCtx)NULL) #define HELPCTX(x) NULL -typedef const char *FILESELECT_FILTER_TYPE; -#define FILTER_KEY_FILES NULL /* FIXME */ -#define FILTER_DYNLIB_FILES NULL /* FIXME */ - /* * Under X, selection data must not be NUL-terminated. */ diff --git a/windows/config.c b/windows/config.c index fc9070bf..732f3d40 100644 --- a/windows/config.c +++ b/windows/config.c @@ -159,7 +159,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, } } ctrl_filesel(s, "Custom sound file to play as a bell:", NO_SHORTCUT, - FILTER_WAVE_FILES, false, "Select bell sound file", + FILTER_SOUND_FILES, false, "Select bell sound file", HELPCTX(bell_style), conf_filesel_handler, I(CONF_bell_wavefile)); @@ -377,7 +377,7 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, bool has_help, if (!midsession && backend_vt_from_proto(PROT_SSH)) { s = ctrl_getset(b, "Connection/SSH/X11", "x11", "X11 forwarding"); ctrl_filesel(s, "X authority file for local display", 't', - NULL, false, "Select X authority file", + FILTER_ALL_FILES, false, "Select X authority file", HELPCTX(ssh_tunnels_xauthority), conf_filesel_handler, I(CONF_xauthfile)); } diff --git a/windows/controls.c b/windows/controls.c index b4c838b4..e1fcc589 100644 --- a/windows/controls.c +++ b/windows/controls.c @@ -1988,46 +1988,33 @@ bool winctrl_handle_command(struct dlgparam *dp, UINT msg, (msg == WM_COMMAND && (HIWORD(wParam) == BN_CLICKED || HIWORD(wParam) == BN_DOUBLECLICKED))) { - OPENFILENAMEW of; - wchar_t filename[FILENAME_MAX]; - - wchar_t *title_to_free = NULL; - - memset(&of, 0, sizeof(of)); - of.hwndOwner = dp->hwnd; - if (ctrl->fileselect.filter) - of.lpstrFilter = ctrl->fileselect.filter; - else - of.lpstrFilter = L"All Files (*.*)\0*\0\0\0"; - of.lpstrCustomFilter = NULL; - of.nFilterIndex = 1; - of.lpstrFile = filename; + Filename *fn_prev = NULL; if (!ctrl->fileselect.just_button) { - GetDlgItemTextW(dp->hwnd, c->base_id+1, - filename, lenof(filename)); - filename[lenof(filename)-1] = L'\0'; - } else { - *filename = L'\0'; + wchar_t *text = GetDlgItemTextW_alloc(dp->hwnd, c->base_id+1); + if (*text) + fn_prev = filename_from_wstr(text); + sfree(text); } - of.nMaxFile = lenof(filename); - of.lpstrFileTitle = NULL; - of.lpstrTitle = title_to_free = dup_mb_to_wc( - DEFAULT_CODEPAGE, ctrl->fileselect.title); - of.Flags = 0; - if (request_file_w(NULL, &of, false, - ctrl->fileselect.for_writing)) { + + Filename *fn = request_file( + dp->hwnd, ctrl->fileselect.title, fn_prev, + ctrl->fileselect.for_writing, NULL, false, + ctrl->fileselect.filter); + if (fn_prev) + filename_free(fn_prev); + + if (fn) { if (!ctrl->fileselect.just_button) { - SetDlgItemTextW(dp->hwnd, c->base_id + 1, filename); + SetDlgItemTextW(dp->hwnd, c->base_id + 1, + filename_to_wstr(fn)); ctrl->handler(ctrl, dp, dp->data, EVENT_VALCHANGE); } else { assert(!c->data); - c->data = filename; + c->data = fn; ctrl->handler(ctrl, dp, dp->data, EVENT_ACTION); c->data = NULL; } } - - sfree(title_to_free); } break; case CTRL_FONTSELECT: @@ -2433,7 +2420,7 @@ Filename *dlg_filesel_get(dlgcontrol *ctrl, dlgparam *dp) sfree(tmp); return ret; } else { - return filename_from_str(c->data); + return filename_copy(c->data); } } diff --git a/windows/pageant.c b/windows/pageant.c index eb858a3b..202a10f8 100644 --- a/windows/pageant.c +++ b/windows/pageant.c @@ -52,7 +52,7 @@ static char *putty_path; static bool restrict_putty_acl = false; /* CWD for "add key" file requester. */ -static filereq *keypath = NULL; +static filereq_saved_dir *keypath = NULL; /* From MSDN: In the WM_SYSCOMMAND message, the four low-order bits of * wParam are used by Windows, and should be masked off, so we shouldn't @@ -543,49 +543,21 @@ static void win_add_keyfile(Filename *filename, bool encrypted) */ static void prompt_add_keyfile(bool encrypted) { - OPENFILENAME of; - char *filelist = snewn(8192, char); + if (!keypath) + keypath = filereq_saved_dir_new(); - if (!keypath) keypath = filereq_new(); - memset(&of, 0, sizeof(of)); - of.hwndOwner = traywindow; - of.lpstrFilter = FILTER_KEY_FILES_C; - of.lpstrCustomFilter = NULL; - of.nFilterIndex = 1; - of.lpstrFile = filelist; - *filelist = '\0'; - of.nMaxFile = 8192; - of.lpstrFileTitle = NULL; - of.lpstrTitle = "Select Private Key File"; - of.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER; - if (request_file(keypath, &of, true, false)) { - if (strlen(filelist) > of.nFileOffset) { - /* Only one filename returned? */ - Filename *fn = filename_from_str(filelist); - win_add_keyfile(fn, encrypted); - filename_free(fn); - } else { - /* we are returned a bunch of strings, end to - * end. first string is the directory, the - * rest the filenames. terminated with an - * empty string. - */ - char *dir = filelist; - char *filewalker = filelist + strlen(dir) + 1; - while (*filewalker != '\0') { - char *filename = dupcat(dir, "\\", filewalker); - Filename *fn = filename_from_str(filename); - win_add_keyfile(fn, encrypted); - filename_free(fn); - sfree(filename); - filewalker += strlen(filewalker) + 1; - } - } + struct request_multi_file_return *rmf = request_multi_file( + traywindow, "Select Private Key File", NULL, false, + keypath, true, FILTER_KEY_FILES); + + if (rmf) { + for (size_t i = 0; i < rmf->nfilenames; i++) + win_add_keyfile(rmf->filenames[i], encrypted); + request_multi_file_free(rmf); keylist_update(); pageant_forget_passphrases(); } - sfree(filelist); } /* @@ -1957,7 +1929,8 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show) DestroyMenu(systray_menu); } - if (keypath) filereq_free(keypath); + if (keypath) + filereq_saved_dir_free(keypath); if (openssh_config_file) { /* diff --git a/windows/platform.h b/windows/platform.h index 2af9b8c4..10551e87 100644 --- a/windows/platform.h +++ b/windows/platform.h @@ -296,27 +296,6 @@ void write_aclip(HWND hwnd, int clipboard, char *, int); */ #define sk_getxdmdata(socket, lenp) (NULL) -/* - * File-selector filter strings used in the config box. On Windows, - * these strings are of exactly the type needed to go in - * `lpstrFilter' in an OPENFILENAME structure. - */ -typedef const wchar_t *FILESELECT_FILTER_TYPE; -#define FILTER_KEY_FILES (L"PuTTY Private Key Files (*.ppk)\0*.ppk\0" \ - L"All Files (*.*)\0*\0\0\0") -#define FILTER_WAVE_FILES (L"Wave Files (*.wav)\0*.WAV\0" \ - L"All Files (*.*)\0*\0\0\0") -#define FILTER_DYNLIB_FILES (L"Dynamic Library Files (*.dll)\0*.dll\0" \ - L"All Files (*.*)\0*\0\0\0") - -/* char-based versions of the above, for outlying uses of file selectors. */ -#define FILTER_KEY_FILES_C ("PuTTY Private Key Files (*.ppk)\0*.ppk\0" \ - "All Files (*.*)\0*\0\0\0") -#define FILTER_WAVE_FILES_C ("Wave Files (*.wav)\0*.WAV\0" \ - "All Files (*.*)\0*\0\0\0") -#define FILTER_DYNLIB_FILES_C ("Dynamic Library Files (*.dll)\0*.dll\0" \ - "All Files (*.*)\0*\0\0\0") - /* * Exports from network.c. */ @@ -417,12 +396,21 @@ void init_common_controls(void); /* also does some DLL-loading */ /* * Exports from utils. */ -typedef struct filereq_tag filereq; /* cwd for file requester */ -bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save); -bool request_file_w(filereq *state, OPENFILENAMEW *of, - bool preserve, bool save); -filereq *filereq_new(void); -void filereq_free(filereq *state); +typedef struct filereq_saved_dir filereq_saved_dir; +filereq_saved_dir *filereq_saved_dir_new(void); +void filereq_saved_dir_free(filereq_saved_dir *state); +Filename *request_file( + HWND hwnd, const char *title, Filename *initial, bool save, + filereq_saved_dir *dir, bool preserve_cwd, FilereqFilter filter); +struct request_multi_file_return { + Filename **filenames; + size_t nfilenames; +}; +struct request_multi_file_return *request_multi_file( + HWND hwnd, const char *title, Filename *initial, bool save, + filereq_saved_dir *dir, bool preserve_cwd, FilereqFilter filter); +void request_multi_file_free(struct request_multi_file_return *); + void pgp_fingerprints_msgbox(HWND owner); int message_box(HWND owner, LPCTSTR text, LPCTSTR caption, DWORD style, bool utf8, DWORD helpctxid); diff --git a/windows/puttygen.c b/windows/puttygen.c index 0e4f9a78..c5115c06 100644 --- a/windows/puttygen.c +++ b/windows/puttygen.c @@ -451,34 +451,6 @@ static INT_PTR CALLBACK PPKParamsProc(HWND hwnd, UINT msg, return 0; } -/* - * Prompt for a key file. Assumes the filename buffer is of size - * FILENAME_MAX. - */ -static bool prompt_keyfile(HWND hwnd, char *dlgtitle, - char *filename, bool save, bool ppk) -{ - OPENFILENAME of; - memset(&of, 0, sizeof(of)); - of.hwndOwner = hwnd; - if (ppk) { - of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0" - "All Files (*.*)\0*\0\0\0"; - of.lpstrDefExt = ".ppk"; - } else { - of.lpstrFilter = "All Files (*.*)\0*\0\0\0"; - } - of.lpstrCustomFilter = NULL; - of.nFilterIndex = 1; - of.lpstrFile = filename; - *filename = '\0'; - of.nMaxFile = FILENAME_MAX; - of.lpstrFileTitle = NULL; - of.lpstrTitle = dlgtitle; - of.Flags = 0; - return request_file(NULL, &of, false, save); -} - /* * Dialog-box function for the Licence box. */ @@ -2016,7 +1988,6 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->key_exists) { - char filename[FILENAME_MAX]; char *passphrase, *passphrase2; int type, realtype; @@ -2068,26 +2039,28 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, break; } } - if (prompt_keyfile(hwnd, "Save private key as:", - filename, true, (type == realtype))) { + Filename *fn = request_file( + hwnd, "Save private key as:", NULL, true, NULL, false, + (type==realtype ? FILTER_KEY_FILES : FILTER_ALL_FILES)); + if (fn) { int ret; - FILE *fp = fopen(filename, "r"); + FILE *fp = f_open(fn, "r", false); if (fp) { char *buffer; fclose(fp); buffer = dupprintf("Overwrite existing file\n%s?", - filename); + filename_to_str(fn)); ret = MessageBox(hwnd, buffer, "PuTTYgen Warning", MB_YESNO | MB_ICONWARNING); sfree(buffer); if (ret != IDYES) { burnstr(passphrase); + filename_free(fn); break; } } if (state->ssh2) { - Filename *fn = filename_from_str(filename); if (type != realtype) ret = export_ssh2(fn, type, &state->ssh2key, *passphrase ? passphrase : NULL); @@ -2095,21 +2068,19 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, ret = ppk_save_f(fn, &state->ssh2key, *passphrase ? passphrase : NULL, &save_params); - filename_free(fn); } else { - Filename *fn = filename_from_str(filename); if (type != realtype) ret = export_ssh1(fn, type, &state->key, *passphrase ? passphrase : NULL); else ret = rsa1_save_f(fn, &state->key, *passphrase ? passphrase : NULL); - filename_free(fn); } if (ret <= 0) { MessageBox(hwnd, "Unable to save key file", "PuTTYgen Error", MB_OK | MB_ICONERROR); } + filename_free(fn); } burnstr(passphrase); } @@ -2120,23 +2091,26 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->key_exists) { - char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "Save public key as:", - filename, true, false)) { + Filename *fn = request_file( + hwnd, "Save public key as:", NULL, true, NULL, false, + FILTER_ALL_FILES); + if (fn) { int ret; - FILE *fp = fopen(filename, "r"); + FILE *fp = f_open(fn, "r", false); if (fp) { char *buffer; fclose(fp); buffer = dupprintf("Overwrite existing file\n%s?", - filename); + filename_to_str(fn)); ret = MessageBox(hwnd, buffer, "PuTTYgen Warning", MB_YESNO | MB_ICONWARNING); sfree(buffer); - if (ret != IDYES) + if (ret != IDYES) { + filename_free(fn); break; + } } - fp = fopen(filename, "w"); + fp = f_open(fn, "w", false); if (!fp) { MessageBox(hwnd, "Unable to open key file", "PuTTYgen Error", MB_OK | MB_ICONERROR); @@ -2157,6 +2131,7 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, "PuTTYgen Error", MB_OK | MB_ICONERROR); } } + filename_free(fn); } } break; @@ -2167,10 +2142,11 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (!state->generation_thread_exists) { - char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "Load private key:", filename, false, - LOWORD(wParam) == IDC_LOAD)) { - Filename *fn = filename_from_str(filename); + Filename *fn = request_file( + hwnd, "Load private key:", NULL, false, NULL, false, + (LOWORD(wParam) == IDC_LOAD ? + FILTER_KEY_FILES : FILTER_ALL_FILES)); + if (fn) { load_key_file(hwnd, state, fn, LOWORD(wParam) != IDC_LOAD); filename_free(fn); } @@ -2182,10 +2158,10 @@ static INT_PTR CALLBACK MainDlgProc(HWND hwnd, UINT msg, state = (struct MainDlgState *) GetWindowLongPtr(hwnd, GWLP_USERDATA); if (state->key_exists && !state->generation_thread_exists) { - char filename[FILENAME_MAX]; - if (prompt_keyfile(hwnd, "Load certificate:", filename, false, - false)) { - Filename *fn = filename_from_str(filename); + Filename *fn = request_file( + hwnd, "Load certificate:", NULL, false, NULL, false, + FILTER_ALL_FILES); + if (fn) { add_certificate(hwnd, state, fn); filename_free(fn); } diff --git a/windows/utils/request_file.c b/windows/utils/request_file.c index 57363cad..e9a94291 100644 --- a/windows/utils/request_file.c +++ b/windows/utils/request_file.c @@ -1,114 +1,376 @@ -/* - * GetOpenFileName/GetSaveFileName tend to muck around with the process' - * working directory on at least some versions of Windows. - * Here's a wrapper that gives more control over this, and hides a little - * bit of other grottiness. - */ - #include "putty.h" -struct filereq_tag { - TCHAR cwd[MAX_PATH]; - WCHAR wcwd[MAX_PATH]; +typedef enum SavedDir { SD_NONE, SD_WCHAR, SD_CHAR } SavedDir; + +struct filereq_saved_dir { + SavedDir which; + union { + WCHAR wcwd[MAX_PATH]; + TCHAR cwd[MAX_PATH]; + }; }; -/* - * `of' is expected to be initialised with most interesting fields, but - * this function does some administrivia. (assume `of' was memset to 0) - * save==1 -> GetSaveFileName; save==0 -> GetOpenFileName - * `state' is optional. - */ -bool request_file(filereq *state, OPENFILENAME *of, bool preserve, bool save) +filereq_saved_dir *filereq_saved_dir_new(void) { - TCHAR cwd[MAX_PATH]; /* process CWD */ - bool ret; + filereq_saved_dir *state = snew(filereq_saved_dir); + state->which = SD_NONE; + return state; +} - /* Get process CWD */ - if (preserve) { - DWORD r = GetCurrentDirectory(lenof(cwd), cwd); - if (r == 0 || r >= lenof(cwd)) - /* Didn't work, oh well. Stop trying to be clever. */ - preserve = false; +void filereq_saved_dir_free(filereq_saved_dir *state) +{ + sfree(state); +} + +static void save_dir(filereq_saved_dir *state) +{ + DWORD dirlen; + + dirlen = GetCurrentDirectoryW(lenof(state->wcwd), state->wcwd); + if (dirlen > 0 && dirlen < lenof(state->wcwd)) { + state->which = SD_WCHAR; + return; } - /* Open the file requester, maybe setting lpstrInitialDir */ - { + dirlen = GetCurrentDirectoryA(lenof(state->cwd), state->cwd); + if (dirlen > 0 && dirlen < lenof(state->cwd)) { + state->which = SD_CHAR; + return; + } + + state->which = SD_NONE; +} + +static void restore_dir(filereq_saved_dir *state) +{ + switch (state->which) { + case SD_WCHAR: + SetCurrentDirectoryW(state->wcwd); + break; + case SD_CHAR: + SetCurrentDirectoryA(state->cwd); + break; + case SD_NONE: + break; + } +} + +/* + * Internal function that brings up an ANSI-coded file dialog, + * returning a raw char * buffer containing the output. + * + * Inputs: + * - hwnd: the parent window for the dialog, or NULL if none + * - title: the window title + * - initial: a filename to populate the new dialog with, or NULL + * - dir: a location in which to persist the logical cwd used by + * successive file dialogs + * - save: true if the file dialog is for write rather than loading a file + * - filter: the default type of file being asked about, which will inform + * the choice of which files to display in the dialog, and also a default + * file extension for saving files + * - multi_filename_offset: NULL if you want to return exactly one file. + * Otherwise points to a size_t which gets nFileOffset from the result + * structure. This is passed to the request_multi_file_populate_* helpers + * below. + * - filename: buffer to put the output in + * - filename_size: size of the buffer. + */ +static bool do_filereq_a( + HWND hwnd, const char *title, Filename *initial, filereq_saved_dir *dir, + bool save, FilereqFilter filter, size_t *multi_filename_offset, + char *filename, size_t filename_size) +{ + OPENFILENAMEA of; + + memset(&of, 0, sizeof(of)); #ifdef OPENFILENAME_SIZE_VERSION_400 - of->lStructSize = OPENFILENAME_SIZE_VERSION_400; + of.lStructSize = OPENFILENAME_SIZE_VERSION_400; #else - of->lStructSize = sizeof(*of); + of.lStructSize = sizeof(of); #endif - of->lpstrInitialDir = (state && state->cwd[0]) ? state->cwd : NULL; - /* Actually put up the requester. */ - ret = save ? GetSaveFileName(of) : GetOpenFileName(of); + + if (dir && dir->which == SD_CHAR) + of.lpstrInitialDir = dir->cwd; + + switch (filter) { + default: /* FILTER_ALL_FILES */ + of.lpstrFilter = "All Files (*.*)\0*\0\0\0"; + break; + case FILTER_KEY_FILES: + of.lpstrFilter = "PuTTY Private Key Files (*.ppk)\0*.ppk\0" + "All Files (*.*)\0*\0\0\0"; + of.lpstrDefExt = ".ppk"; + break; + case FILTER_DYNLIB_FILES: + of.lpstrFilter = "Dynamic Library Files (*.dll)\0*.dll\0" + "All Files (*.*)\0*\0\0\0"; + of.lpstrDefExt = ".dll"; + break; + case FILTER_SOUND_FILES: + of.lpstrFilter = "Wave Files (*.wav)\0*.WAV\0" + "All Files (*.*)\0*\0\0\0"; + of.lpstrDefExt = ".wav"; + break; } + of.nFilterIndex = 1; - /* Get CWD left by requester */ - if (state) { - DWORD r = GetCurrentDirectory(lenof(state->cwd), state->cwd); - if (r == 0 || r >= lenof(state->cwd)) - /* Didn't work, oh well. */ - state->cwd[0] = '\0'; + of.hwndOwner = hwnd; + + if (initial) { + strncpy(filename, filename_to_str(initial), filename_size - 1); + filename[filename_size - 1] = '\0'; + } else { + *filename = '\0'; } + of.lpstrFile = filename; + of.nMaxFile = filename_size; - /* Restore process CWD */ - if (preserve) - /* If it fails, there's not much we can do. */ - (void) SetCurrentDirectory(cwd); + of.lpstrTitle = title; - return ret; + if (multi_filename_offset) + of.Flags |= OFN_ALLOWMULTISELECT | OFN_EXPLORER; + + bool toret = save ? GetSaveFileNameA(&of) : GetOpenFileNameA(&of); + + if (dir) + save_dir(dir); + + if (multi_filename_offset) + *multi_filename_offset = of.nFileOffset; + + return toret; } /* * Here's the same one again, the wide-string version */ -bool request_file_w(filereq *state, OPENFILENAMEW *of, - bool preserve, bool save) +static bool do_filereq_w( + HWND hwnd, const char *title, Filename *initial, filereq_saved_dir *dir, + bool save, FilereqFilter filter, size_t *multi_filename_offset, + wchar_t *filename, size_t filename_size) { - WCHAR cwd[MAX_PATH]; /* process CWD */ - bool ret; + OPENFILENAMEW of; + void *tofree1 = NULL, *tofree2 = NULL; - /* Get process CWD */ - if (preserve) { - DWORD r = GetCurrentDirectoryW(lenof(cwd), cwd); - if (r == 0 || r >= lenof(cwd)) - /* Didn't work, oh well. Stop trying to be clever. */ - preserve = false; + memset(&of, 0, sizeof(of)); + of.lStructSize = sizeof(of); + + if (dir && dir->which == SD_WCHAR) + of.lpstrInitialDir = dir->wcwd; + else if (dir && dir->which == SD_CHAR) { + wchar_t *winitdir = dup_mb_to_wc(CP_ACP, dir->cwd); + tofree1 = winitdir; + of.lpstrInitialDir = winitdir; } - /* Open the file requester, maybe setting lpstrInitialDir */ - { - of->lStructSize = sizeof(*of); - of->lpstrInitialDir = (state && state->wcwd[0]) ? state->wcwd : NULL; - /* Actually put up the requester. */ - ret = save ? GetSaveFileNameW(of) : GetOpenFileNameW(of); + switch (filter) { + default: /* FILTER_ALL_FILES */ + of.lpstrFilter = L"All Files (*.*)\0*\0\0\0"; + break; + case FILTER_KEY_FILES: + of.lpstrFilter = L"PuTTY Private Key Files (*.ppk)\0*.ppk\0" + "All Files (*.*)\0*\0\0\0"; + of.lpstrDefExt = L".ppk"; + break; + case FILTER_DYNLIB_FILES: + of.lpstrFilter = L"Dynamic Library Files (*.dll)\0*.dll\0" + "All Files (*.*)\0*\0\0\0"; + of.lpstrDefExt = L".dll"; + break; + case FILTER_SOUND_FILES: + of.lpstrFilter = L"Wave Files (*.wav)\0*.WAV\0" + "All Files (*.*)\0*\0\0\0"; + of.lpstrDefExt = L".wav"; + break; + } + of.nFilterIndex = 1; + + of.hwndOwner = hwnd; + + if (initial) { + wcsncpy(filename, filename_to_wstr(initial), filename_size - 1); + filename[filename_size - 1] = L'\0'; + } else { + *filename = L'\0'; + } + of.lpstrFile = filename; + of.nMaxFile = filename_size; + + if (title) { + wchar_t *wtitle = dup_mb_to_wc(CP_ACP, title); + tofree2 = wtitle; + of.lpstrTitle = wtitle; } - /* Get CWD left by requester */ - if (state) { - DWORD r = GetCurrentDirectoryW(lenof(state->wcwd), state->wcwd); - if (r == 0 || r >= lenof(state->wcwd)) - /* Didn't work, oh well. */ - state->wcwd[0] = L'\0'; + if (multi_filename_offset) + of.Flags |= OFN_ALLOWMULTISELECT | OFN_EXPLORER; + + bool toret = save ? GetSaveFileNameW(&of) : GetOpenFileNameW(&of); + + if (dir) + save_dir(dir); + + sfree(tofree1); + sfree(tofree2); + + if (multi_filename_offset) + *multi_filename_offset = of.nFileOffset; + + return toret; +} + +Filename *request_file( + HWND hwnd, const char *title, Filename *initial, bool save, + filereq_saved_dir *dir, bool preserve_cwd, FilereqFilter filter) +{ + filereq_saved_dir saved_cwd[1]; + Filename *filename = NULL; + + if (preserve_cwd) + save_dir(saved_cwd); + + init_winver(); + if (osPlatformId != VER_PLATFORM_WIN32_NT) { + char namebuf[MAX_PATH]; + if (do_filereq_a( + hwnd, title, initial, dir, save, filter, + NULL, namebuf, sizeof(namebuf))) + filename = filename_from_str(namebuf); + } else { + wchar_t namebuf[MAX_PATH]; + if (do_filereq_w( + hwnd, title, initial, dir, save, filter, + NULL, namebuf, sizeof(namebuf))) + filename = filename_from_wstr(namebuf); } - /* Restore process CWD */ - if (preserve) - /* If it fails, there's not much we can do. */ - (void) SetCurrentDirectoryW(cwd); + if (preserve_cwd) + restore_dir(saved_cwd); - return ret; + return filename; } -filereq *filereq_new(void) +static struct request_multi_file_return *request_multi_file_populate_a( + const char *buf, size_t first_filename_offset) { - filereq *state = snew(filereq); - state->cwd[0] = '\0'; - state->wcwd[0] = L'\0'; - return state; + struct request_multi_file_return *rmf = + snew(struct request_multi_file_return); + + /* + * We expect one of two situations (guaranteed by the return from + * the OFN_MULTISELECT file dialog API function): + * + * 1. There is a single NUL-terminated filename string in buf, + * potentially including a path, and first_filename_offset points + * to the leaf name part of it. + * + * 2. There are multiple NUL-terminated strings in buf, with the + * first being a path, and the remaining ones being leaf names to + * concatenate to that path. An empty string / extra NUL + * terminates the whole list. first_filename_offset points to the + * start of the first leaf name. + * + * Hence, we can tell these apart by finding out whether a NUL + * appears in the buffer before first_filename_offset. If no, + * we're in case 1; if yes, case 2. + */ + if (strlen(buf) > first_filename_offset) { + /* Case 1: a single filename. */ + rmf->nfilenames = 1; + rmf->filenames = snewn(1, Filename *); + rmf->filenames[0] = filename_from_str(buf); + } else { + /* Case 2: multiple filenames preceded by a path. */ + size_t filenamesize = 16; + rmf->nfilenames = 0; + rmf->filenames = snewn(filenamesize, Filename *); + + const char *dir = buf; + const char *sep = + (*dir && dir[strlen(dir)-1] == '\\') ? "" : "\\"; + const char *p = buf + strlen(dir) + 1; + for (; *p; p += strlen(p) + 1) { + char *filename = dupcat(dir, sep, p); + sgrowarray(rmf->filenames, filenamesize, rmf->nfilenames); + rmf->filenames[rmf->nfilenames++] = filename_from_str(filename); + sfree(filename); + } + } + return rmf; } -void filereq_free(filereq *state) +/* + * Here's the same one again, the wide-string version + */ +static struct request_multi_file_return *request_multi_file_populate_w( + const wchar_t *buf, size_t first_filename_offset) { - sfree(state); + struct request_multi_file_return *rmf = + snew(struct request_multi_file_return); + if (wcslen(buf) > first_filename_offset) { + rmf->nfilenames = 1; + rmf->filenames = snewn(1, Filename *); + rmf->filenames[0] = filename_from_wstr(buf); + } else { + size_t filenamesize = 16; + rmf->nfilenames = 0; + rmf->filenames = snewn(filenamesize, Filename *); + + const wchar_t *dir = buf; + const wchar_t *sep = + (*dir && dir[wcslen(dir)-1] == L'\\') ? L"" : L"\\"; + const wchar_t *p = buf + wcslen(dir) + 1; + for (; *p; p += wcslen(p) + 1) { + wchar_t *filename = dupwcscat(dir, sep, p); + sgrowarray(rmf->filenames, filenamesize, rmf->nfilenames); + rmf->filenames[rmf->nfilenames++] = filename_from_wstr(filename); + sfree(filename); + } + } + return rmf; +} + +#define MULTI_FACTOR 32 + +struct request_multi_file_return *request_multi_file( + HWND hwnd, const char *title, Filename *initial, bool save, + filereq_saved_dir *dir, bool preserve_cwd, FilereqFilter filter) +{ + filereq_saved_dir saved_cwd[1]; + struct request_multi_file_return *rmf = NULL; + size_t first_filename_offset; + + if (preserve_cwd) + save_dir(saved_cwd); + + init_winver(); + if (osPlatformId != VER_PLATFORM_WIN32_NT) { + char namebuf[MAX_PATH * MULTI_FACTOR]; + if (do_filereq_a( + hwnd, title, initial, dir, save, filter, + &first_filename_offset, namebuf, sizeof(namebuf))) + rmf = request_multi_file_populate_a( + namebuf, first_filename_offset); + } else { + wchar_t namebuf[MAX_PATH * MULTI_FACTOR]; + if (do_filereq_w( + hwnd, title, initial, dir, save, filter, + &first_filename_offset, namebuf, sizeof(namebuf))) + rmf = request_multi_file_populate_w( + namebuf, first_filename_offset); + } + + if (preserve_cwd) + restore_dir(saved_cwd); + + return rmf; +} + +void request_multi_file_free(struct request_multi_file_return *rmf) +{ + for (size_t i = 0; i < rmf->nfilenames; i++) + filename_free(rmf->filenames[i]); + sfree(rmf->filenames); + sfree(rmf); }