1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-10 01:48:00 +00:00
putty-source/windows/dialog.c
Simon Tatham 8bd75b85ed Some support for wide-character filenames in Windows.
The Windows version of the Filename structure now contains three
versions of the pathname, in UTF-16, UTF-8 and the system code page.
Callers can use whichever is most convenient.

All uses of filenames for actually opening files now use the UTF-16
version, which means they can tolerate 'exotic' filenames, by which I
mean those including Unicode characters outside the host system's
CP_ACP default code page.

Other uses of Filename structures inside the 'windows' subdirectory do
something appropriate, e.g. when printing a filename inside a message
box or a console message, we use the UTF-8 version of the filename
with the UTF-8 version of the appropriate API.

There are three remaining pieces to full Unicode filename support:

One is that the cross-platform code has many calls to
filename_to_str(), embodying the assumption that a file name can be
reliably converted into the unspecified current character set; those
will all need changing in some way.

Another is that write_setting_filename(), in windows/storage.c, still
saves filenames to the Registry as an ordinary REG_SZ in the system
code page. So even if an exotic filename were stored in a Conf, that
Conf couldn't round-trip via the Registry and back without corrupting
that filename by coercing it back to a string that fits in CP_ACP and
therefore doesn't represent the same file. This can't be fixed without
a compatibility break in the storage format, and I don't want to make
a minimal change in that area: if we're going to break compatibility,
then we should break it good and hard (the Nanny Ogg principle), and
devise a completely fresh storage representation that fixes as many
other legacy problems as possible at the same time. So that's my plan,
not yet started.

The final point, much more obviously, is that we're still short of
methods to _construct_ any Filename structures using a Unicode input
string! It should now work to enter one in the GUI configurer (either
by manual text input or via the file selector), but it won't
round-trip through a save and load (as discussed above), and there's
still no way to specify one on the command line (the groundwork is
laid by commit 10e1ac7752 but not yet linked up).

But this is a start.
2023-05-29 15:41:50 +01:00

1330 lines
41 KiB
C

/*
* dialog.c - dialogs for PuTTY(tel), including the configuration dialog.
*/
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <assert.h>
#include <ctype.h>
#include <time.h>
#include "putty.h"
#include "ssh.h"
#include "putty-rc.h"
#include "win-gui-seat.h"
#include "storage.h"
#include "dialog.h"
#include "licence.h"
#include <commctrl.h>
#include <commdlg.h>
#include <shellapi.h>
#ifdef MSVC4
#define TVINSERTSTRUCT TV_INSERTSTRUCT
#define TVITEM TV_ITEM
#define ICON_BIG 1
#endif
typedef struct PortableDialogStuff {
/*
* These are the various bits of data required to handle a dialog
* box that's been built up from the cross-platform dialog.c
* system.
*/
/* The 'controlbox' that was returned from the portable setup function */
struct controlbox *ctrlbox;
/* The dlgparam that's passed to all the runtime dlg_* functions.
* Declared as an array of 1 so it's convenient to pass it as a pointer. */
struct dlgparam dp[1];
/*
* Collections of instantiated controls. There can be more than
* one of these, because sometimes you want to destroy and
* recreate a subset of them - e.g. when switching panes in the
* main PuTTY config box, you delete and recreate _most_ of the
* controls, but not the OK and Cancel buttons at the bottom.
*/
size_t nctrltrees;
struct winctrls *ctrltrees;
/*
* Flag indicating whether the dialog box has been initialised.
* Used to suppresss spurious firing of message handlers during
* setup.
*/
bool initialised;
} PortableDialogStuff;
/*
* Initialise a PortableDialogStuff, before launching the dialog box.
*/
static PortableDialogStuff *pds_new(size_t nctrltrees)
{
PortableDialogStuff *pds = snew(PortableDialogStuff);
memset(pds, 0, sizeof(*pds));
pds->ctrlbox = ctrl_new_box();
dp_init(pds->dp);
pds->nctrltrees = nctrltrees;
pds->ctrltrees = snewn(pds->nctrltrees, struct winctrls);
for (size_t i = 0; i < pds->nctrltrees; i++) {
winctrl_init(&pds->ctrltrees[i]);
dp_add_tree(pds->dp, &pds->ctrltrees[i]);
}
pds->dp->errtitle = dupprintf("%s Error", appname);
pds->initialised = false;
return pds;
}
static void pds_free(PortableDialogStuff *pds)
{
ctrl_free_box(pds->ctrlbox);
dp_cleanup(pds->dp);
for (size_t i = 0; i < pds->nctrltrees; i++)
winctrl_cleanup(&pds->ctrltrees[i]);
sfree(pds->ctrltrees);
sfree(pds);
}
static INT_PTR pds_default_dlgproc(PortableDialogStuff *pds, HWND hwnd,
UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_LBUTTONUP:
/*
* Button release should trigger WM_OK if there was a
* previous double click on the host CA list.
*/
ReleaseCapture();
if (pds->dp->ended)
ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0);
break;
case WM_COMMAND:
case WM_DRAWITEM:
default: { /* also handle drag list msg here */
/*
* Only process WM_COMMAND once the dialog is fully formed.
*/
int ret;
if (pds->initialised) {
ret = winctrl_handle_command(pds->dp, msg, wParam, lParam);
if (pds->dp->ended && GetCapture() != hwnd)
ShinyEndDialog(hwnd, pds->dp->endresult ? 1 : 0);
} else
ret = 0;
return ret;
}
case WM_HELP:
if (!winctrl_context_help(pds->dp,
hwnd, ((LPHELPINFO)lParam)->iCtrlId))
MessageBeep(0);
break;
case WM_CLOSE:
quit_help(hwnd);
ShinyEndDialog(hwnd, 0);
return 0;
/* Grrr Explorer will maximize Dialogs! */
case WM_SIZE:
if (wParam == SIZE_MAXIMIZED)
force_normal(hwnd);
return 0;
}
return 0;
}
static void pds_initdialog_start(PortableDialogStuff *pds, HWND hwnd)
{
pds->dp->hwnd = hwnd;
if (pds->dp->wintitle) /* apply override title, if provided */
SetWindowText(hwnd, pds->dp->wintitle);
/* The portable dialog system generally includes the ability to
* handle context help for particular controls. Enable the
* relevant window styles if we have a help file available. */
if (has_help()) {
LONG_PTR style = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
SetWindowLongPtr(hwnd, GWL_EXSTYLE, style | WS_EX_CONTEXTHELP);
} else {
/* If not, and if the dialog template provided a top-level
* Help button, delete it */
HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
if (item)
DestroyWindow(item);
}
}
/*
* Create the panelfuls of controls in the configuration box.
*/
static void pds_create_controls(
PortableDialogStuff *pds, size_t which_tree, int base_id,
int left, int right, int top, char *path)
{
struct ctlpos cp;
ctlposinit(&cp, pds->dp->hwnd, left, right, top);
for (int index = -1; (index = ctrl_find_path(
pds->ctrlbox, path, index)) >= 0 ;) {
struct controlset *s = pds->ctrlbox->ctrlsets[index];
winctrl_layout(pds->dp, &pds->ctrltrees[which_tree], &cp, s, &base_id);
}
}
static void pds_initdialog_finish(PortableDialogStuff *pds)
{
/*
* Set focus into the first available control in ctrltree #0,
* which the caller was expected to set up to be the one
* containing the dialog controls likely to be used first.
*/
struct winctrl *c;
for (int i = 0; (c = winctrl_findbyindex(&pds->ctrltrees[0], i)) != NULL;
i++) {
if (c->ctrl) {
dlg_set_focus(c->ctrl, pds->dp);
break;
}
}
/*
* Now we've finished creating our initial set of controls,
* it's safe to actually show the window without risking setup
* flicker.
*/
ShowWindow(pds->dp->hwnd, SW_SHOWNORMAL);
pds->initialised = true;
}
#define LOGEVENT_INITIAL_MAX 128
#define LOGEVENT_CIRCULAR_MAX 128
static char *events_initial[LOGEVENT_INITIAL_MAX];
static char *events_circular[LOGEVENT_CIRCULAR_MAX];
static int ninitial = 0, ncircular = 0, circular_first = 0;
#define PRINTER_DISABLED_STRING "None (printing disabled)"
void force_normal(HWND hwnd)
{
static bool recurse = false;
WINDOWPLACEMENT wp;
if (recurse)
return;
recurse = true;
wp.length = sizeof(wp);
if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
wp.showCmd = SW_SHOWNORMAL;
SetWindowPlacement(hwnd, &wp);
}
recurse = false;
}
static char *getevent(int i)
{
if (i < ninitial)
return events_initial[i];
if ((i -= ninitial) < ncircular)
return events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX];
return NULL;
}
static HWND logbox;
HWND event_log_window(void) { return logbox; }
static INT_PTR CALLBACK LogProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
int i;
switch (msg) {
case WM_INITDIALOG: {
char *str = dupprintf("%s Event Log", appname);
SetWindowText(hwnd, str);
sfree(str);
static int tabs[4] = { 78, 108 };
SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2,
(LPARAM) tabs);
for (i = 0; i < ninitial; i++)
SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
0, (LPARAM) events_initial[i]);
for (i = 0; i < ncircular; i++)
SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
0, (LPARAM) events_circular[(circular_first + i) % LOGEVENT_CIRCULAR_MAX]);
return 1;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
case IDCANCEL:
logbox = NULL;
SetActiveWindow(GetParent(hwnd));
DestroyWindow(hwnd);
return 0;
case IDN_COPY:
if (HIWORD(wParam) == BN_CLICKED ||
HIWORD(wParam) == BN_DOUBLECLICKED) {
int selcount;
int *selitems;
selcount = SendDlgItemMessage(hwnd, IDN_LIST,
LB_GETSELCOUNT, 0, 0);
if (selcount == 0) { /* don't even try to copy zero items */
MessageBeep(0);
break;
}
selitems = snewn(selcount, int);
if (selitems) {
int count = SendDlgItemMessage(hwnd, IDN_LIST,
LB_GETSELITEMS,
selcount,
(LPARAM) selitems);
static const unsigned char sel_nl[] = SEL_NL;
if (count == 0) { /* can't copy zero stuff */
MessageBeep(0);
break;
}
strbuf *sb = strbuf_new();
for (int i = 0; i < count; i++) {
char *q = getevent(selitems[i]);
put_datapl(sb, ptrlen_from_asciz(q));
put_data(sb, sel_nl, sizeof(sel_nl));
}
write_aclip(hwnd, CLIP_SYSTEM, sb->s, sb->len);
strbuf_free(sb);
sfree(selitems);
for (i = 0; i < (ninitial + ncircular); i++)
SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL,
false, i);
}
}
return 0;
}
return 0;
case WM_CLOSE:
logbox = NULL;
SetActiveWindow(GetParent(hwnd));
DestroyWindow(hwnd);
return 0;
}
return 0;
}
static INT_PTR CALLBACK LicenceProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
switch (msg) {
case WM_INITDIALOG: {
char *str = dupprintf("%s Licence", appname);
SetWindowText(hwnd, str);
sfree(str);
SetDlgItemText(hwnd, IDA_TEXT, LICENCE_TEXT("\r\n\r\n"));
return 1;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
case IDCANCEL:
EndDialog(hwnd, 1);
return 0;
}
return 0;
case WM_CLOSE:
EndDialog(hwnd, 1);
return 0;
}
return 0;
}
static INT_PTR CALLBACK AboutProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
char *str;
switch (msg) {
case WM_INITDIALOG: {
str = dupprintf("About %s", appname);
SetWindowText(hwnd, str);
sfree(str);
char *buildinfo_text = buildinfo("\r\n");
char *text = dupprintf(
"%s\r\n\r\n%s\r\n\r\n%s\r\n\r\n%s",
appname, ver, buildinfo_text,
"\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
sfree(buildinfo_text);
SetDlgItemText(hwnd, IDA_TEXT, text);
MakeDlgItemBorderless(hwnd, IDA_TEXT);
sfree(text);
return 1;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
case IDCANCEL:
EndDialog(hwnd, true);
return 0;
case IDA_LICENCE:
EnableWindow(hwnd, 0);
DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
hwnd, LicenceProc);
EnableWindow(hwnd, 1);
SetActiveWindow(hwnd);
return 0;
case IDA_WEB:
/* Load web browser */
ShellExecute(hwnd, "open",
"https://www.chiark.greenend.org.uk/~sgtatham/putty/",
0, 0, SW_SHOWDEFAULT);
return 0;
}
return 0;
case WM_CLOSE:
EndDialog(hwnd, true);
return 0;
}
return 0;
}
/*
* Null dialog procedure.
*/
static INT_PTR CALLBACK NullDlgProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
return 0;
}
enum {
IDCX_ABOUT = IDC_ABOUT,
IDCX_TVSTATIC,
IDCX_TREEVIEW,
IDCX_STDBASE,
IDCX_PANELBASE = IDCX_STDBASE + 32
};
struct treeview_faff {
HWND treeview;
HTREEITEM lastat[4];
};
static HTREEITEM treeview_insert(struct treeview_faff *faff,
int level, char *text, char *path)
{
TVINSERTSTRUCT ins;
int i;
HTREEITEM newitem;
ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT);
ins.hInsertAfter = faff->lastat[level];
#if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION
#define INSITEM DUMMYUNIONNAME.item
#else
#define INSITEM item
#endif
ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
ins.INSITEM.pszText = text;
ins.INSITEM.cchTextMax = strlen(text)+1;
ins.INSITEM.lParam = (LPARAM)path;
newitem = TreeView_InsertItem(faff->treeview, &ins);
if (level > 0)
TreeView_Expand(faff->treeview, faff->lastat[level - 1],
(level > 1 ? TVE_COLLAPSE : TVE_EXPAND));
faff->lastat[level] = newitem;
for (i = level + 1; i < 4; i++)
faff->lastat[i] = NULL;
return newitem;
}
const char *dialog_box_demo_screenshot_filename = NULL;
/* ctrltrees indices for the main dialog box */
enum {
TREE_PANEL, /* things we swap out every time treeview selects a new pane */
TREE_BASE, /* fixed things at the bottom like OK and Cancel buttons */
};
/*
* This function is the configuration box.
* (Being a dialog procedure, in general it returns 0 if the default
* dialog processing should be performed, and 1 if it should not.)
*/
static INT_PTR GenericMainDlgProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam, void *ctx)
{
PortableDialogStuff *pds = (PortableDialogStuff *)ctx;
const int DEMO_SCREENSHOT_TIMER_ID = 1230;
HWND treeview;
struct treeview_faff tvfaff;
switch (msg) {
case WM_INITDIALOG:
pds_initdialog_start(pds, hwnd);
pds_create_controls(pds, TREE_BASE, IDCX_STDBASE, 3, 3, 235, "");
SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
(LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
centre_window(hwnd);
/*
* Create the tree view.
*/
{
RECT r;
WPARAM font;
HWND tvstatic;
r.left = 3;
r.right = r.left + 95;
r.top = 3;
r.bottom = r.top + 10;
MapDialogRect(hwnd, &r);
tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
WS_CHILD | WS_VISIBLE,
r.left, r.top,
r.right - r.left, r.bottom - r.top,
hwnd, (HMENU) IDCX_TVSTATIC, hinst,
NULL);
font = SendMessage(hwnd, WM_GETFONT, 0, 0);
SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(true, 0));
r.left = 3;
r.right = r.left + 95;
r.top = 13;
r.bottom = r.top + 219;
MapDialogRect(hwnd, &r);
treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
WS_CHILD | WS_VISIBLE |
WS_TABSTOP | TVS_HASLINES |
TVS_DISABLEDRAGDROP | TVS_HASBUTTONS
| TVS_LINESATROOT |
TVS_SHOWSELALWAYS, r.left, r.top,
r.right - r.left, r.bottom - r.top,
hwnd, (HMENU) IDCX_TREEVIEW, hinst,
NULL);
font = SendMessage(hwnd, WM_GETFONT, 0, 0);
SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(true, 0));
tvfaff.treeview = treeview;
memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat));
}
/*
* Set up the tree view contents.
*/
{
HTREEITEM hfirst = NULL;
int i;
char *path = NULL;
char *firstpath = NULL;
for (i = 0; i < pds->ctrlbox->nctrlsets; i++) {
struct controlset *s = pds->ctrlbox->ctrlsets[i];
HTREEITEM item;
int j;
char *c;
if (!s->pathname[0])
continue;
j = path ? ctrl_path_compare(s->pathname, path) : 0;
if (j == INT_MAX)
continue; /* same path, nothing to add to tree */
/*
* We expect never to find an implicit path
* component. For example, we expect never to see
* A/B/C followed by A/D/E, because that would
* _implicitly_ create A/D. All our path prefixes
* are expected to contain actual controls and be
* selectable in the treeview; so we would expect
* to see A/D _explicitly_ before encountering
* A/D/E.
*/
assert(j == ctrl_path_elements(s->pathname) - 1);
c = strrchr(s->pathname, '/');
if (!c)
c = s->pathname;
else
c++;
item = treeview_insert(&tvfaff, j, c, s->pathname);
if (!hfirst) {
hfirst = item;
firstpath = s->pathname;
}
path = s->pathname;
}
/*
* Put the treeview selection on to the first panel in the
* ctrlbox.
*/
TreeView_SelectItem(treeview, hfirst);
/*
* And create the actual control set for that panel, to
* match the initial treeview selection.
*/
assert(firstpath); /* config.c must have given us _something_ */
pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE,
100, 3, 13, firstpath);
dlg_refresh(NULL, pds->dp); /* and set up control values */
}
if (dialog_box_demo_screenshot_filename)
SetTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID, TICKSPERSEC, NULL);
pds_initdialog_finish(pds);
return 0;
case WM_TIMER:
if (dialog_box_demo_screenshot_filename &&
(UINT_PTR)wParam == DEMO_SCREENSHOT_TIMER_ID) {
KillTimer(hwnd, DEMO_SCREENSHOT_TIMER_ID);
char *err = save_screenshot(
hwnd, dialog_box_demo_screenshot_filename);
if (err) {
MessageBox(hwnd, err, "Demo screenshot failure",
MB_OK | MB_ICONERROR);
sfree(err);
}
ShinyEndDialog(hwnd, 0);
}
return 0;
case WM_NOTIFY:
if (LOWORD(wParam) == IDCX_TREEVIEW &&
((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
/*
* Selection-change events on the treeview cause us to do
* a flurry of control deletion and creation - but only
* after WM_INITDIALOG has finished. The initial
* selection-change event(s) during treeview setup are
* ignored.
*/
HTREEITEM i;
TVITEM item;
char buffer[64];
if (!pds->initialised)
return 0;
i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
SendMessage (hwnd, WM_SETREDRAW, false, 0);
item.hItem = i;
item.pszText = buffer;
item.cchTextMax = sizeof(buffer);
item.mask = TVIF_TEXT | TVIF_PARAM;
TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
{
/* Destroy all controls in the currently visible panel. */
int k;
HWND item;
struct winctrl *c;
while ((c = winctrl_findbyindex(
&pds->ctrltrees[TREE_PANEL], 0)) != NULL) {
for (k = 0; k < c->num_ids; k++) {
item = GetDlgItem(hwnd, c->base_id + k);
if (item)
DestroyWindow(item);
}
winctrl_rem_shortcuts(pds->dp, c);
winctrl_remove(&pds->ctrltrees[TREE_PANEL], c);
sfree(c->data);
sfree(c);
}
}
pds_create_controls(pds, TREE_PANEL, IDCX_PANELBASE,
100, 3, 13, (char *)item.lParam);
dlg_refresh(NULL, pds->dp); /* set up control values */
SendMessage (hwnd, WM_SETREDRAW, true, 0);
InvalidateRect (hwnd, NULL, true);
SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */
}
return 0;
default:
return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam);
}
}
void modal_about_box(HWND hwnd)
{
EnableWindow(hwnd, 0);
DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
EnableWindow(hwnd, 1);
SetActiveWindow(hwnd);
}
void show_help(HWND hwnd)
{
launch_help(hwnd, NULL);
}
void defuse_showwindow(void)
{
/*
* Work around the fact that the app's first call to ShowWindow
* will ignore the default in favour of the shell-provided
* setting.
*/
{
HWND hwnd;
hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
NULL, NullDlgProc);
ShowWindow(hwnd, SW_HIDE);
SetActiveWindow(hwnd);
DestroyWindow(hwnd);
}
}
bool do_config(Conf *conf)
{
bool ret;
PortableDialogStuff *pds = pds_new(2);
setup_config_box(pds->ctrlbox, false, 0, 0);
win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(), false, 0);
pds->dp->wintitle = dupprintf("%s Configuration", appname);
pds->dp->data = conf;
dlg_auto_set_fixed_pitch_flag(pds->dp);
pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */
ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox",
NULL, GenericMainDlgProc, pds);
pds_free(pds);
return ret;
}
bool do_reconfig(HWND hwnd, Conf *conf, int protcfginfo)
{
Conf *backup_conf;
bool ret;
int protocol;
PortableDialogStuff *pds = pds_new(2);
backup_conf = conf_copy(conf);
protocol = conf_get_int(conf, CONF_protocol);
setup_config_box(pds->ctrlbox, true, protocol, protcfginfo);
win_setup_config_box(pds->ctrlbox, &pds->dp->hwnd, has_help(),
true, protocol);
pds->dp->wintitle = dupprintf("%s Reconfiguration", appname);
pds->dp->data = conf;
dlg_auto_set_fixed_pitch_flag(pds->dp);
pds->dp->shortcuts['g'] = true; /* the treeview: `Cate&gory' */
ret = ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), "PuTTYConfigBox",
NULL, GenericMainDlgProc, pds);
pds_free(pds);
if (!ret)
conf_copy_into(conf, backup_conf);
conf_free(backup_conf);
return ret;
}
static void win_gui_eventlog(LogPolicy *lp, const char *string)
{
char timebuf[40];
char **location;
struct tm tm;
tm=ltime();
strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
if (ninitial < LOGEVENT_INITIAL_MAX)
location = &events_initial[ninitial];
else
location = &events_circular[(circular_first + ncircular) % LOGEVENT_CIRCULAR_MAX];
if (*location)
sfree(*location);
*location = dupcat(timebuf, string);
if (logbox) {
int count;
SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
0, (LPARAM) *location);
count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
}
if (ninitial < LOGEVENT_INITIAL_MAX) {
ninitial++;
} else if (ncircular < LOGEVENT_CIRCULAR_MAX) {
ncircular++;
} else if (ncircular == LOGEVENT_CIRCULAR_MAX) {
circular_first = (circular_first + 1) % LOGEVENT_CIRCULAR_MAX;
sfree(events_circular[circular_first]);
events_circular[circular_first] = dupstr("..");
}
}
static void win_gui_logging_error(LogPolicy *lp, const char *event)
{
WinGuiSeat *wgs = container_of(lp, WinGuiSeat, logpolicy);
/* Send 'can't open log file' errors to the terminal window.
* (Marked as stderr, although terminal.c won't care.) */
seat_stderr_pl(&wgs->seat, ptrlen_from_asciz(event));
seat_stderr_pl(&wgs->seat, PTRLEN_LITERAL("\r\n"));
}
void showeventlog(HWND hwnd)
{
if (!logbox) {
logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
hwnd, LogProc);
ShowWindow(logbox, SW_SHOWNORMAL);
}
SetActiveWindow(logbox);
}
void showabout(HWND hwnd)
{
DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
}
struct hostkey_dialog_ctx {
SeatDialogText *text;
bool has_title;
const char *helpctx;
};
static INT_PTR HostKeyMoreInfoProc(HWND hwnd, UINT msg, WPARAM wParam,
LPARAM lParam, void *vctx)
{
struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx;
switch (msg) {
case WM_INITDIALOG: {
int index = 100, y = 12;
WPARAM font = SendMessage(hwnd, WM_GETFONT, 0, 0);
const char *key = NULL;
for (SeatDialogTextItem *item = ctx->text->items,
*end = item + ctx->text->nitems; item < end; item++) {
switch (item->type) {
case SDT_MORE_INFO_KEY:
key = item->text;
break;
case SDT_MORE_INFO_VALUE_SHORT:
case SDT_MORE_INFO_VALUE_BLOB: {
RECT rk, rv;
DWORD editstyle = WS_CHILD | WS_VISIBLE | WS_TABSTOP |
ES_AUTOHSCROLL | ES_READONLY;
if (item->type == SDT_MORE_INFO_VALUE_BLOB) {
rk.left = 12;
rk.right = 376;
rk.top = y;
rk.bottom = 8;
y += 10;
editstyle |= ES_MULTILINE;
rv.left = 12;
rv.right = 376;
rv.top = y;
rv.bottom = 64;
y += 68;
} else {
rk.left = 12;
rk.right = 80;
rk.top = y+2;
rk.bottom = 8;
rv.left = 100;
rv.right = 288;
rv.top = y;
rv.bottom = 12;
y += 16;
}
MapDialogRect(hwnd, &rk);
HWND ctl = CreateWindowEx(
0, "STATIC", key, WS_CHILD | WS_VISIBLE,
rk.left, rk.top, rk.right, rk.bottom,
hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL);
SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0));
MapDialogRect(hwnd, &rv);
ctl = CreateWindowEx(
WS_EX_CLIENTEDGE, "EDIT", item->text, editstyle,
rv.left, rv.top, rv.right, rv.bottom,
hwnd, (HMENU)(ULONG_PTR)index++, hinst, NULL);
SendMessage(ctl, WM_SETFONT, font, MAKELPARAM(true, 0));
break;
}
default:
break;
}
}
/*
* Now resize the overall window, and move the Close button at
* the bottom.
*/
RECT r;
r.left = 176;
r.top = y + 10;
r.right = r.bottom = 0;
MapDialogRect(hwnd, &r);
HWND ctl = GetDlgItem(hwnd, IDOK);
SetWindowPos(ctl, NULL, r.left, r.top, 0, 0,
SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER);
r.left = r.top = r.right = 0;
r.bottom = 300;
MapDialogRect(hwnd, &r);
int oldheight = r.bottom;
r.left = r.top = r.right = 0;
r.bottom = y + 30;
MapDialogRect(hwnd, &r);
int newheight = r.bottom;
GetWindowRect(hwnd, &r);
SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left,
r.bottom - r.top + newheight - oldheight,
SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
ShowWindow(hwnd, SW_SHOWNORMAL);
return 1;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDOK:
ShinyEndDialog(hwnd, 0);
return 0;
}
return 0;
case WM_CLOSE:
ShinyEndDialog(hwnd, 0);
return 0;
}
return 0;
}
static INT_PTR HostKeyDialogProc(HWND hwnd, UINT msg,
WPARAM wParam, LPARAM lParam, void *vctx)
{
struct hostkey_dialog_ctx *ctx = (struct hostkey_dialog_ctx *)vctx;
switch (msg) {
case WM_INITDIALOG: {
strbuf *dlg_text = strbuf_new();
const char *dlg_title = "";
ctx->has_title = false;
LPCTSTR iconid = IDI_QUESTION;
for (SeatDialogTextItem *item = ctx->text->items,
*end = item + ctx->text->nitems; item < end; item++) {
switch (item->type) {
case SDT_PARA:
put_fmt(dlg_text, "%s\r\n\r\n", item->text);
break;
case SDT_DISPLAY:
put_fmt(dlg_text, "%s\r\n\r\n", item->text);
break;
case SDT_SCARY_HEADING:
SetDlgItemText(hwnd, IDC_HK_TITLE, item->text);
iconid = IDI_WARNING;
ctx->has_title = true;
break;
case SDT_TITLE:
dlg_title = item->text;
break;
default:
break;
}
}
while (strbuf_chomp(dlg_text, '\r') || strbuf_chomp(dlg_text, '\n'));
SetDlgItemText(hwnd, IDC_HK_TEXT, dlg_text->s);
MakeDlgItemBorderless(hwnd, IDC_HK_TEXT);
strbuf_free(dlg_text);
SetWindowText(hwnd, dlg_title);
if (!ctx->has_title) {
HWND item = GetDlgItem(hwnd, IDC_HK_TITLE);
if (item)
DestroyWindow(item);
}
/*
* Find out how tall the text in the edit control really ended
* up (after line wrapping), and adjust the height of the
* whole box to match it.
*/
int height = SendDlgItemMessage(hwnd, IDC_HK_TEXT,
EM_GETLINECOUNT, 0, 0);
height *= 8; /* height of a text line, by definition of dialog units */
int edittop = ctx->has_title ? 40 : 20;
RECT r;
r.left = 40;
r.top = edittop;
r.right = 290;
r.bottom = height;
MapDialogRect(hwnd, &r);
SetWindowPos(GetDlgItem(hwnd, IDC_HK_TEXT), NULL,
r.left, r.top, r.right, r.bottom,
SWP_NOREDRAW | SWP_NOZORDER);
static const struct {
int id, x;
} buttons[] = {
{ IDCANCEL, 288 },
{ IDC_HK_ACCEPT, 168 },
{ IDC_HK_ONCE, 216 },
{ IDC_HK_MOREINFO, 60 },
{ IDHELP, 12 },
};
for (size_t i = 0; i < lenof(buttons); i++) {
HWND ctl = GetDlgItem(hwnd, buttons[i].id);
r.left = buttons[i].x;
r.top = edittop + height + 20;
r.right = r.bottom = 0;
MapDialogRect(hwnd, &r);
SetWindowPos(ctl, NULL, r.left, r.top, 0, 0,
SWP_NOSIZE | SWP_NOREDRAW | SWP_NOZORDER);
}
r.left = r.top = r.right = 0;
r.bottom = 240;
MapDialogRect(hwnd, &r);
int oldheight = r.bottom;
r.left = r.top = r.right = 0;
r.bottom = edittop + height + 40;
MapDialogRect(hwnd, &r);
int newheight = r.bottom;
GetWindowRect(hwnd, &r);
SetWindowPos(hwnd, NULL, 0, 0, r.right - r.left,
r.bottom - r.top + newheight - oldheight,
SWP_NOMOVE | SWP_NOREDRAW | SWP_NOZORDER);
HANDLE icon = LoadImage(
NULL, iconid, IMAGE_ICON,
GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON),
LR_SHARED);
SendDlgItemMessage(hwnd, IDC_HK_ICON, STM_SETICON, (WPARAM)icon, 0);
if (!has_help()) {
HWND item = GetDlgItem(hwnd, IDHELP);
if (item)
DestroyWindow(item);
}
ShowWindow(hwnd, SW_SHOWNORMAL);
return 1;
}
case WM_CTLCOLORSTATIC: {
HDC hdc = (HDC)wParam;
HWND control = (HWND)lParam;
if (GetWindowLongPtr(control, GWLP_ID) == IDC_HK_TITLE &&
ctx->has_title) {
SetBkMode(hdc, TRANSPARENT);
HFONT prev_font = (HFONT)SelectObject(
hdc, (HFONT)GetStockObject(SYSTEM_FONT));
LOGFONT lf;
if (GetObject(prev_font, sizeof(lf), &lf)) {
lf.lfWeight = FW_BOLD;
lf.lfHeight = lf.lfHeight * 3 / 2;
HFONT bold_font = CreateFontIndirect(&lf);
if (bold_font)
SelectObject(hdc, bold_font);
}
return (INT_PTR)GetSysColorBrush(COLOR_BTNFACE);
}
return 0;
}
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDC_HK_ACCEPT:
case IDC_HK_ONCE:
case IDCANCEL:
ShinyEndDialog(hwnd, LOWORD(wParam));
return 0;
case IDHELP: {
launch_help(hwnd, ctx->helpctx);
return 0;
}
case IDC_HK_MOREINFO: {
ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_HK_MOREINFO),
"PuTTYHostKeyMoreInfo", hwnd,
HostKeyMoreInfoProc, ctx);
}
}
return 0;
case WM_CLOSE:
ShinyEndDialog(hwnd, IDCANCEL);
return 0;
}
return 0;
}
const SeatDialogPromptDescriptions *win_seat_prompt_descriptions(Seat *seat)
{
static const SeatDialogPromptDescriptions descs = {
.hk_accept_action = "press \"Accept\"",
.hk_connect_once_action = "press \"Connect Once\"",
.hk_cancel_action = "press \"Cancel\"",
.hk_cancel_action_Participle = "Pressing \"Cancel\"",
};
return &descs;
}
SeatPromptResult win_seat_confirm_ssh_host_key(
Seat *seat, const char *host, int port, const char *keytype,
char *keystr, SeatDialogText *text, HelpCtx helpctx,
void (*callback)(void *ctx, SeatPromptResult result), void *cbctx)
{
WinGuiSeat *wgs = container_of(seat, WinGuiSeat, seat);
struct hostkey_dialog_ctx ctx[1];
ctx->text = text;
ctx->helpctx = helpctx;
int mbret = ShinyDialogBox(
hinst, MAKEINTRESOURCE(IDD_HOSTKEY), "PuTTYHostKeyDialog",
wgs->term_hwnd, HostKeyDialogProc, ctx);
assert(mbret==IDC_HK_ACCEPT || mbret==IDC_HK_ONCE || mbret==IDCANCEL);
if (mbret == IDC_HK_ACCEPT) {
store_host_key(seat, host, port, keytype, keystr);
return SPR_OK;
} else if (mbret == IDC_HK_ONCE) {
return SPR_OK;
}
return SPR_USER_ABORT;
}
/*
* Ask whether the selected algorithm is acceptable (since it was
* below the configured 'warn' threshold).
*/
SeatPromptResult win_seat_confirm_weak_crypto_primitive(
Seat *seat, const char *algtype, const char *algname,
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
{
static const char mbtitle[] = "%s Security Alert";
static const char msg[] =
"The first %s supported by the server\n"
"is %s, which is below the configured\n"
"warning threshold.\n"
"Do you want to continue with this connection?\n";
char *message, *title;
int mbret;
message = dupprintf(msg, algtype, algname);
title = dupprintf(mbtitle, appname);
mbret = MessageBox(NULL, message, title,
MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
socket_reselect_all();
sfree(message);
sfree(title);
if (mbret == IDYES)
return SPR_OK;
else
return SPR_USER_ABORT;
}
SeatPromptResult win_seat_confirm_weak_cached_hostkey(
Seat *seat, const char *algname, const char *betteralgs,
void (*callback)(void *ctx, SeatPromptResult result), void *ctx)
{
static const char mbtitle[] = "%s Security Alert";
static const char msg[] =
"The first host key type we have stored for this server\n"
"is %s, which is below the configured warning threshold.\n"
"The server also provides the following types of host key\n"
"above the threshold, which we do not have stored:\n"
"%s\n"
"Do you want to continue with this connection?\n";
char *message, *title;
int mbret;
message = dupprintf(msg, algname, betteralgs);
title = dupprintf(mbtitle, appname);
mbret = MessageBox(NULL, message, title,
MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
socket_reselect_all();
sfree(message);
sfree(title);
if (mbret == IDYES)
return SPR_OK;
else
return SPR_USER_ABORT;
}
/*
* Ask whether to wipe a session log file before writing to it.
* Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
*/
static int win_gui_askappend(LogPolicy *lp, Filename *filename,
void (*callback)(void *ctx, int result),
void *ctx)
{
static const char msgtemplate[] =
"The session log file \"%.*s\" already exists.\n"
"You can overwrite it with a new session log,\n"
"append your session log to the end of it,\n"
"or disable session logging for this session.\n"
"Hit Yes to wipe the file, No to append to it,\n"
"or Cancel to disable logging.";
char *message;
char *mbtitle;
int mbret;
message = dupprintf(msgtemplate, FILENAME_MAX, filename->utf8path);
mbtitle = dupprintf("%s Log to File", appname);
mbret = message_box(NULL, message, mbtitle,
MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3,
true, 0);
socket_reselect_all();
sfree(message);
sfree(mbtitle);
if (mbret == IDYES)
return 2;
else if (mbret == IDNO)
return 1;
else
return 0;
}
const LogPolicyVtable win_gui_logpolicy_vt = {
.eventlog = win_gui_eventlog,
.askappend = win_gui_askappend,
.logging_error = win_gui_logging_error,
.verbose = null_lp_verbose_yes,
};
/*
* Warn about the obsolescent key file format.
*
* Uniquely among these functions, this one does _not_ expect a
* frontend handle. This means that if PuTTY is ported to a
* platform which requires frontend handles, this function will be
* an anomaly. Fortunately, the problem it addresses will not have
* been present on that platform, so it can plausibly be
* implemented as an empty function.
*/
void old_keyfile_warning(void)
{
static const char mbtitle[] = "%s Key File Warning";
static const char message[] =
"You are loading an SSH-2 private key which has an\n"
"old version of the file format. This means your key\n"
"file is not fully tamperproof. Future versions of\n"
"%s may stop supporting this private key format,\n"
"so we recommend you convert your key to the new\n"
"format.\n"
"\n"
"You can perform this conversion by loading the key\n"
"into PuTTYgen and then saving it again.";
char *msg, *title;
msg = dupprintf(message, appname);
title = dupprintf(mbtitle, appname);
MessageBox(NULL, msg, title, MB_OK);
socket_reselect_all();
sfree(msg);
sfree(title);
}
static INT_PTR CAConfigProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam,
void *ctx)
{
PortableDialogStuff *pds = (PortableDialogStuff *)ctx;
switch (msg) {
case WM_INITDIALOG:
pds_initdialog_start(pds, hwnd);
SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
(LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
centre_window(hwnd);
pds_create_controls(pds, 0, IDCX_PANELBASE, 3, 3, 3, "Main");
pds_create_controls(pds, 0, IDCX_STDBASE, 3, 3, 243, "");
dlg_refresh(NULL, pds->dp); /* and set up control values */
pds_initdialog_finish(pds);
return 0;
default:
return pds_default_dlgproc(pds, hwnd, msg, wParam, lParam);
}
}
void show_ca_config_box(dlgparam *dp)
{
PortableDialogStuff *pds = pds_new(1);
setup_ca_config_box(pds->ctrlbox);
ShinyDialogBox(hinst, MAKEINTRESOURCE(IDD_CA_CONFIG), "PuTTYConfigBox",
dp ? dp->hwnd : NULL, CAConfigProc, pds);
pds_free(pds);
}