2 * windlg.c - dialogs for PuTTY(tel), including the configuration dialog.
24 #define TVINSERTSTRUCT TV_INSERTSTRUCT
25 #define TVITEM TV_ITEM
30 * These are the various bits of data required to handle the
31 * portable-dialog stuff in the config box. Having them at file
32 * scope in here isn't too bad a place to put them; if we were ever
33 * to need more than one config box per process we could always
34 * shift them to a per-config-box structure stored in GWL_USERDATA.
36 static struct controlbox *ctrlbox;
38 * ctrls_base holds the OK and Cancel buttons: the controls which
39 * are present in all dialog panels. ctrls_panel holds the ones
40 * which change from panel to panel.
42 static struct winctrls ctrls_base, ctrls_panel;
43 static struct dlgparam dp;
45 static char **events = NULL;
46 static int nevents = 0, negsize = 0;
48 extern Conf *conf; /* defined in window.c */
50 #define PRINTER_DISABLED_STRING "None (printing disabled)"
52 void force_normal(HWND hwnd)
54 static int recurse = 0;
62 wp.length = sizeof(wp);
63 if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
64 wp.showCmd = SW_SHOWNORMAL;
65 SetWindowPlacement(hwnd, &wp);
70 static int CALLBACK LogProc(HWND hwnd, UINT msg,
71 WPARAM wParam, LPARAM lParam)
78 char *str = dupprintf("%s Event Log", appname);
79 SetWindowText(hwnd, str);
83 static int tabs[4] = { 78, 108 };
84 SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2,
87 for (i = 0; i < nevents; i++)
88 SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
89 0, (LPARAM) events[i]);
92 switch (LOWORD(wParam)) {
96 SetActiveWindow(GetParent(hwnd));
100 if (HIWORD(wParam) == BN_CLICKED ||
101 HIWORD(wParam) == BN_DOUBLECLICKED) {
104 selcount = SendDlgItemMessage(hwnd, IDN_LIST,
105 LB_GETSELCOUNT, 0, 0);
106 if (selcount == 0) { /* don't even try to copy zero items */
111 selitems = snewn(selcount, int);
113 int count = SendDlgItemMessage(hwnd, IDN_LIST,
120 static unsigned char sel_nl[] = SEL_NL;
122 if (count == 0) { /* can't copy zero stuff */
128 for (i = 0; i < count; i++)
130 strlen(events[selitems[i]]) + sizeof(sel_nl);
132 clipdata = snewn(size, char);
135 for (i = 0; i < count; i++) {
136 char *q = events[selitems[i]];
137 int qlen = strlen(q);
140 memcpy(p, sel_nl, sizeof(sel_nl));
143 write_aclip(NULL, clipdata, size, TRUE);
148 for (i = 0; i < nevents; i++)
149 SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL,
158 SetActiveWindow(GetParent(hwnd));
165 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
166 WPARAM wParam, LPARAM lParam)
171 char *str = dupprintf("%s Licence", appname);
172 SetWindowText(hwnd, str);
174 SetDlgItemText(hwnd, IDA_TEXT, LICENCE_TEXT("\r\n\r\n"));
178 switch (LOWORD(wParam)) {
192 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
193 WPARAM wParam, LPARAM lParam)
199 str = dupprintf("About %s", appname);
200 SetWindowText(hwnd, str);
203 char *text = dupprintf
204 ("%s\r\n\r\n%s\r\n\r\n%s",
206 "\251 " SHORT_COPYRIGHT_DETAILS ". All rights reserved.");
207 SetDlgItemText(hwnd, IDA_TEXT, text);
212 switch (LOWORD(wParam)) {
215 EndDialog(hwnd, TRUE);
218 EnableWindow(hwnd, 0);
219 DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
221 EnableWindow(hwnd, 1);
222 SetActiveWindow(hwnd);
226 /* Load web browser */
227 ShellExecute(hwnd, "open",
228 "http://www.chiark.greenend.org.uk/~sgtatham/putty/",
229 0, 0, SW_SHOWDEFAULT);
234 EndDialog(hwnd, TRUE);
240 static int SaneDialogBox(HINSTANCE hinst,
243 DLGPROC lpDialogFunc)
252 wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
253 wc.lpfnWndProc = DefDlgProc;
255 wc.cbWndExtra = DLGWINDOWEXTRA + 2*sizeof(LONG_PTR);
256 wc.hInstance = hinst;
258 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
259 wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
260 wc.lpszMenuName = NULL;
261 wc.lpszClassName = "PuTTYConfigBox";
264 hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc);
266 SetWindowLongPtr(hwnd, BOXFLAGS, 0); /* flags */
267 SetWindowLongPtr(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
269 while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
270 flags=GetWindowLongPtr(hwnd, BOXFLAGS);
271 if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg))
272 DispatchMessage(&msg);
278 PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */
280 ret=GetWindowLongPtr(hwnd, BOXRESULT);
285 static void SaneEndDialog(HWND hwnd, int ret)
287 SetWindowLongPtr(hwnd, BOXRESULT, ret);
288 SetWindowLongPtr(hwnd, BOXFLAGS, DF_END);
292 * Null dialog procedure.
294 static int CALLBACK NullDlgProc(HWND hwnd, UINT msg,
295 WPARAM wParam, LPARAM lParam)
301 IDCX_ABOUT = IDC_ABOUT,
305 IDCX_PANELBASE = IDCX_STDBASE + 32
308 struct treeview_faff {
313 static HTREEITEM treeview_insert(struct treeview_faff *faff,
314 int level, char *text, char *path)
319 ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT);
320 ins.hInsertAfter = faff->lastat[level];
321 #if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION
322 #define INSITEM DUMMYUNIONNAME.item
326 ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
327 ins.INSITEM.pszText = text;
328 ins.INSITEM.cchTextMax = strlen(text)+1;
329 ins.INSITEM.lParam = (LPARAM)path;
330 newitem = TreeView_InsertItem(faff->treeview, &ins);
332 TreeView_Expand(faff->treeview, faff->lastat[level - 1],
333 (level > 1 ? TVE_COLLAPSE : TVE_EXPAND));
334 faff->lastat[level] = newitem;
335 for (i = level + 1; i < 4; i++)
336 faff->lastat[i] = NULL;
341 * Create the panelfuls of controls in the configuration box.
343 static void create_controls(HWND hwnd, char *path)
352 * Here we must create the basic standard controls.
354 ctlposinit(&cp, hwnd, 3, 3, 235);
356 base_id = IDCX_STDBASE;
359 * Otherwise, we're creating the controls for a particular
362 ctlposinit(&cp, hwnd, 100, 3, 13);
364 base_id = IDCX_PANELBASE;
367 for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) {
368 struct controlset *s = ctrlbox->ctrlsets[index];
369 winctrl_layout(&dp, wc, &cp, s, &base_id);
374 * This function is the configuration box.
375 * (Being a dialog procedure, in general it returns 0 if the default
376 * dialog processing should be performed, and 1 if it should not.)
378 static int CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
379 WPARAM wParam, LPARAM lParam)
382 struct treeview_faff tvfaff;
388 create_controls(hwnd, ""); /* Open and Cancel buttons etc */
389 SetWindowText(hwnd, dp.wintitle);
390 SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
392 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
393 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
396 HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
400 SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
401 (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
405 { /* centre the window */
408 hw = GetDesktopWindow();
409 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
411 (rs.right + rs.left + rd.left - rd.right) / 2,
412 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
413 rd.right - rd.left, rd.bottom - rd.top, TRUE);
417 * Create the tree view.
425 r.right = r.left + 95;
427 r.bottom = r.top + 10;
428 MapDialogRect(hwnd, &r);
429 tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
430 WS_CHILD | WS_VISIBLE,
432 r.right - r.left, r.bottom - r.top,
433 hwnd, (HMENU) IDCX_TVSTATIC, hinst,
435 font = SendMessage(hwnd, WM_GETFONT, 0, 0);
436 SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
439 r.right = r.left + 95;
441 r.bottom = r.top + 219;
442 MapDialogRect(hwnd, &r);
443 treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
444 WS_CHILD | WS_VISIBLE |
445 WS_TABSTOP | TVS_HASLINES |
446 TVS_DISABLEDRAGDROP | TVS_HASBUTTONS
448 TVS_SHOWSELALWAYS, r.left, r.top,
449 r.right - r.left, r.bottom - r.top,
450 hwnd, (HMENU) IDCX_TREEVIEW, hinst,
452 font = SendMessage(hwnd, WM_GETFONT, 0, 0);
453 SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
454 tvfaff.treeview = treeview;
455 memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat));
459 * Set up the tree view contents.
462 HTREEITEM hfirst = NULL;
465 char *firstpath = NULL;
467 for (i = 0; i < ctrlbox->nctrlsets; i++) {
468 struct controlset *s = ctrlbox->ctrlsets[i];
475 j = path ? ctrl_path_compare(s->pathname, path) : 0;
477 continue; /* same path, nothing to add to tree */
480 * We expect never to find an implicit path
481 * component. For example, we expect never to see
482 * A/B/C followed by A/D/E, because that would
483 * _implicitly_ create A/D. All our path prefixes
484 * are expected to contain actual controls and be
485 * selectable in the treeview; so we would expect
486 * to see A/D _explicitly_ before encountering
489 assert(j == ctrl_path_elements(s->pathname) - 1);
491 c = strrchr(s->pathname, '/');
497 item = treeview_insert(&tvfaff, j, c, s->pathname);
500 firstpath = s->pathname;
507 * Put the treeview selection on to the first panel in the
510 TreeView_SelectItem(treeview, hfirst);
513 * And create the actual control set for that panel, to
514 * match the initial treeview selection.
516 create_controls(hwnd, firstpath);
517 dlg_refresh(NULL, &dp); /* and set up control values */
521 * Set focus into the first available control.
527 for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
530 dlg_set_focus(c->ctrl, &dp);
537 * Now we've finished creating our initial set of controls,
538 * it's safe to actually show the window without risking setup
541 ShowWindow(hwnd, SW_SHOWNORMAL);
544 * Set the flag that activates a couple of the other message
545 * handlers below, which were disabled until now to avoid
546 * spurious firing during the above setup procedure.
548 SetWindowLongPtr(hwnd, GWLP_USERDATA, 1);
552 * Button release should trigger WM_OK if there was a
553 * previous double click on the session list.
557 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
560 if (LOWORD(wParam) == IDCX_TREEVIEW &&
561 ((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
563 * Selection-change events on the treeview cause us to do
564 * a flurry of control deletion and creation - but only
565 * after WM_INITDIALOG has finished. The initial
566 * selection-change event(s) during treeview setup are
573 if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1)
576 i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
578 SendMessage (hwnd, WM_SETREDRAW, FALSE, 0);
581 item.pszText = buffer;
582 item.cchTextMax = sizeof(buffer);
583 item.mask = TVIF_TEXT | TVIF_PARAM;
584 TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
586 /* Destroy all controls in the currently visible panel. */
591 while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
592 for (k = 0; k < c->num_ids; k++) {
593 item = GetDlgItem(hwnd, c->base_id + k);
597 winctrl_rem_shortcuts(&dp, c);
598 winctrl_remove(&ctrls_panel, c);
603 create_controls(hwnd, (char *)item.lParam);
605 dlg_refresh(NULL, &dp); /* set up control values */
607 SendMessage (hwnd, WM_SETREDRAW, TRUE, 0);
608 InvalidateRect (hwnd, NULL, TRUE);
610 SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */
616 default: /* also handle drag list msg here */
618 * Only process WM_COMMAND once the dialog is fully formed.
620 if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) {
621 ret = winctrl_handle_command(&dp, msg, wParam, lParam);
622 if (dp.ended && GetCapture() != hwnd)
623 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
628 if (!winctrl_context_help(&dp, hwnd,
629 ((LPHELPINFO)lParam)->iCtrlId))
634 SaneEndDialog(hwnd, 0);
637 /* Grrr Explorer will maximize Dialogs! */
639 if (wParam == SIZE_MAXIMIZED)
647 void modal_about_box(HWND hwnd)
649 EnableWindow(hwnd, 0);
650 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
651 EnableWindow(hwnd, 1);
652 SetActiveWindow(hwnd);
655 void show_help(HWND hwnd)
657 launch_help(hwnd, NULL);
660 void defuse_showwindow(void)
663 * Work around the fact that the app's first call to ShowWindow
664 * will ignore the default in favour of the shell-provided
669 hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
671 ShowWindow(hwnd, SW_HIDE);
672 SetActiveWindow(hwnd);
681 ctrlbox = ctrl_new_box();
682 setup_config_box(ctrlbox, FALSE, 0, 0);
683 win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), FALSE, 0);
685 winctrl_init(&ctrls_base);
686 winctrl_init(&ctrls_panel);
687 dp_add_tree(&dp, &ctrls_base);
688 dp_add_tree(&dp, &ctrls_panel);
689 dp.wintitle = dupprintf("%s Configuration", appname);
690 dp.errtitle = dupprintf("%s Error", appname);
692 dlg_auto_set_fixed_pitch_flag(&dp);
693 dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
696 SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
699 ctrl_free_box(ctrlbox);
700 winctrl_cleanup(&ctrls_panel);
701 winctrl_cleanup(&ctrls_base);
707 int do_reconfig(HWND hwnd, int protcfginfo)
712 backup_conf = conf_copy(conf);
714 ctrlbox = ctrl_new_box();
715 protocol = conf_get_int(conf, CONF_protocol);
716 setup_config_box(ctrlbox, TRUE, protocol, protcfginfo);
717 win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), TRUE, protocol);
719 winctrl_init(&ctrls_base);
720 winctrl_init(&ctrls_panel);
721 dp_add_tree(&dp, &ctrls_base);
722 dp_add_tree(&dp, &ctrls_panel);
723 dp.wintitle = dupprintf("%s Reconfiguration", appname);
724 dp.errtitle = dupprintf("%s Error", appname);
726 dlg_auto_set_fixed_pitch_flag(&dp);
727 dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
729 ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
732 ctrl_free_box(ctrlbox);
733 winctrl_cleanup(&ctrls_base);
734 winctrl_cleanup(&ctrls_panel);
738 conf_copy_into(conf, backup_conf);
740 conf_free(backup_conf);
745 void logevent(void *frontend, const char *string)
750 log_eventlog(logctx, string);
752 if (nevents >= negsize) {
754 events = sresize(events, negsize, char *);
758 strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
760 events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
761 strcpy(events[nevents], timebuf);
762 strcat(events[nevents], string);
765 SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
766 0, (LPARAM) events[nevents]);
767 count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
768 SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
773 void showeventlog(HWND hwnd)
776 logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
778 ShowWindow(logbox, SW_SHOWNORMAL);
780 SetActiveWindow(logbox);
783 void showabout(HWND hwnd)
785 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
788 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
789 char *keystr, char *fingerprint,
790 void (*callback)(void *ctx, int result), void *ctx)
794 static const char absentmsg[] =
795 "The server's host key is not cached in the registry. You\n"
796 "have no guarantee that the server is the computer you\n"
798 "The server's %s key fingerprint is:\n"
800 "If you trust this host, hit Yes to add the key to\n"
801 "%s's cache and carry on connecting.\n"
802 "If you want to carry on connecting just once, without\n"
803 "adding the key to the cache, hit No.\n"
804 "If you do not trust this host, hit Cancel to abandon the\n"
807 static const char wrongmsg[] =
808 "WARNING - POTENTIAL SECURITY BREACH!\n"
810 "The server's host key does not match the one %s has\n"
811 "cached in the registry. This means that either the\n"
812 "server administrator has changed the host key, or you\n"
813 "have actually connected to another computer pretending\n"
814 "to be the server.\n"
815 "The new %s key fingerprint is:\n"
817 "If you were expecting this change and trust the new key,\n"
818 "hit Yes to update %s's cache and continue connecting.\n"
819 "If you want to carry on connecting but without updating\n"
820 "the cache, hit No.\n"
821 "If you want to abandon the connection completely, hit\n"
822 "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
824 static const char mbtitle[] = "%s Security Alert";
827 * Verify the key against the registry.
829 ret = verify_host_key(host, port, keytype, keystr);
831 if (ret == 0) /* success - key matched OK */
833 else if (ret == 2) { /* key was different */
835 char *text = dupprintf(wrongmsg, appname, keytype, fingerprint,
837 char *caption = dupprintf(mbtitle, appname);
838 mbret = message_box(text, caption,
839 MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
840 HELPCTXID(errors_hostkey_changed));
841 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
844 if (mbret == IDYES) {
845 store_host_key(host, port, keytype, keystr);
847 } else if (mbret == IDNO)
849 } else if (ret == 1) { /* key was absent */
851 char *text = dupprintf(absentmsg, keytype, fingerprint, appname);
852 char *caption = dupprintf(mbtitle, appname);
853 mbret = message_box(text, caption,
854 MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
855 HELPCTXID(errors_hostkey_absent));
856 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
859 if (mbret == IDYES) {
860 store_host_key(host, port, keytype, keystr);
862 } else if (mbret == IDNO)
865 return 0; /* abandon the connection */
869 * Ask whether the selected algorithm is acceptable (since it was
870 * below the configured 'warn' threshold).
872 int askalg(void *frontend, const char *algtype, const char *algname,
873 void (*callback)(void *ctx, int result), void *ctx)
875 static const char mbtitle[] = "%s Security Alert";
876 static const char msg[] =
877 "The first %s supported by the server\n"
878 "is %.64s, which is below the configured\n"
879 "warning threshold.\n"
880 "Do you want to continue with this connection?\n";
881 char *message, *title;
884 message = dupprintf(msg, algtype, algname);
885 title = dupprintf(mbtitle, appname);
886 mbret = MessageBox(NULL, message, title,
887 MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
888 socket_reselect_all();
898 * Ask whether to wipe a session log file before writing to it.
899 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
901 int askappend(void *frontend, Filename *filename,
902 void (*callback)(void *ctx, int result), void *ctx)
904 static const char msgtemplate[] =
905 "The session log file \"%.*s\" already exists.\n"
906 "You can overwrite it with a new session log,\n"
907 "append your session log to the end of it,\n"
908 "or disable session logging for this session.\n"
909 "Hit Yes to wipe the file, No to append to it,\n"
910 "or Cancel to disable logging.";
915 message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
916 mbtitle = dupprintf("%s Log to File", appname);
918 mbret = MessageBox(NULL, message, mbtitle,
919 MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
921 socket_reselect_all();
928 else if (mbret == IDNO)
935 * Warn about the obsolescent key file format.
937 * Uniquely among these functions, this one does _not_ expect a
938 * frontend handle. This means that if PuTTY is ported to a
939 * platform which requires frontend handles, this function will be
940 * an anomaly. Fortunately, the problem it addresses will not have
941 * been present on that platform, so it can plausibly be
942 * implemented as an empty function.
944 void old_keyfile_warning(void)
946 static const char mbtitle[] = "%s Key File Warning";
947 static const char message[] =
948 "You are loading an SSH-2 private key which has an\n"
949 "old version of the file format. This means your key\n"
950 "file is not fully tamperproof. Future versions of\n"
951 "%s may stop supporting this private key format,\n"
952 "so we recommend you convert your key to the new\n"
955 "You can perform this conversion by loading the key\n"
956 "into PuTTYgen and then saving it again.";
959 msg = dupprintf(message, appname);
960 title = dupprintf(mbtitle, appname);
962 MessageBox(NULL, msg, title, MB_OK);
964 socket_reselect_all();