1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-08 08:58:00 +00:00

Windows: rewrite request_file() to support Unicode.

This centralises into windows/utils/request_file.c all of the code
that deals with the OPENFILENAME structure, and decides centrally
whether to use the Unicode or ANSI version of that structure and its
associated APIs. Now the output of any request_file function is our
own 'Filename' abstract type, instead of a raw char or wchar_t buffer,
which means that _any_ file dialog can produce a full Unicode filename
if the user wants to select one - and yet, in the w32old build, they
all uniformly fall back to the ANSI version, which is the only one
that works at all pre-NT.

A side effect: I've turned the FILTER_FOO_FILES family of definitions
from platform-specific #defines into a reasonably sensible enum. This
didn't affect the GTK side of things , because I'd never got round to
figuring out how to filter a file dialog down to a subset of files in
GTK, and still haven't. So I've just moved the existing FIXME comment
from platform.h to dialog.c.
This commit is contained in:
Simon Tatham 2024-12-13 19:23:30 +00:00
parent 22dfc46fb2
commit f8e1a2b3a9
13 changed files with 442 additions and 254 deletions

View File

@ -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));

View File

@ -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)
{

View File

@ -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,

15
putty.h
View File

@ -4,6 +4,21 @@
#include <stddef.h> /* for wchar_t */
#include <limits.h> /* 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"

View File

@ -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;

View File

@ -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),

View File

@ -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.
*/

View File

@ -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));
}

View File

@ -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);
}
}

View File

@ -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) {
/*

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}