2 * windlg.c - dialogs for PuTTY(tel), including the configuration dialog.
23 #define TVINSERTSTRUCT TV_INSERTSTRUCT
24 #define TVITEM TV_ITEM
29 * These are the various bits of data required to handle the
30 * portable-dialog stuff in the config box. Having them at file
31 * scope in here isn't too bad a place to put them; if we were ever
32 * to need more than one config box per process we could always
33 * shift them to a per-config-box structure stored in GWL_USERDATA.
35 static struct controlbox *ctrlbox;
37 * ctrls_base holds the OK and Cancel buttons: the controls which
38 * are present in all dialog panels. ctrls_panel holds the ones
39 * which change from panel to panel.
41 static struct winctrls ctrls_base, ctrls_panel;
42 static struct dlgparam dp;
44 static char **events = NULL;
45 static int nevents = 0, negsize = 0;
47 extern Conf *conf; /* defined in window.c */
49 #define PRINTER_DISABLED_STRING "None (printing disabled)"
51 void force_normal(HWND hwnd)
53 static int recurse = 0;
61 wp.length = sizeof(wp);
62 if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
63 wp.showCmd = SW_SHOWNORMAL;
64 SetWindowPlacement(hwnd, &wp);
69 static int CALLBACK LogProc(HWND hwnd, UINT msg,
70 WPARAM wParam, LPARAM lParam)
77 char *str = dupprintf("%s Event Log", appname);
78 SetWindowText(hwnd, str);
82 static int tabs[4] = { 78, 108 };
83 SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2,
86 for (i = 0; i < nevents; i++)
87 SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
88 0, (LPARAM) events[i]);
91 switch (LOWORD(wParam)) {
95 SetActiveWindow(GetParent(hwnd));
99 if (HIWORD(wParam) == BN_CLICKED ||
100 HIWORD(wParam) == BN_DOUBLECLICKED) {
103 selcount = SendDlgItemMessage(hwnd, IDN_LIST,
104 LB_GETSELCOUNT, 0, 0);
105 if (selcount == 0) { /* don't even try to copy zero items */
110 selitems = snewn(selcount, int);
112 int count = SendDlgItemMessage(hwnd, IDN_LIST,
119 static unsigned char sel_nl[] = SEL_NL;
121 if (count == 0) { /* can't copy zero stuff */
127 for (i = 0; i < count; i++)
129 strlen(events[selitems[i]]) + sizeof(sel_nl);
131 clipdata = snewn(size, char);
134 for (i = 0; i < count; i++) {
135 char *q = events[selitems[i]];
136 int qlen = strlen(q);
139 memcpy(p, sel_nl, sizeof(sel_nl));
142 write_aclip(NULL, clipdata, size, TRUE);
147 for (i = 0; i < nevents; i++)
148 SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL,
157 SetActiveWindow(GetParent(hwnd));
164 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
165 WPARAM wParam, LPARAM lParam)
170 char *str = dupprintf("%s Licence", appname);
171 SetWindowText(hwnd, str);
176 switch (LOWORD(wParam)) {
190 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
191 WPARAM wParam, LPARAM lParam)
197 str = dupprintf("About %s", appname);
198 SetWindowText(hwnd, str);
200 SetDlgItemText(hwnd, IDA_TEXT1, appname);
201 SetDlgItemText(hwnd, IDA_VERSION, ver);
204 switch (LOWORD(wParam)) {
207 EndDialog(hwnd, TRUE);
210 EnableWindow(hwnd, 0);
211 DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
213 EnableWindow(hwnd, 1);
214 SetActiveWindow(hwnd);
218 /* Load web browser */
219 ShellExecute(hwnd, "open",
220 "http://www.chiark.greenend.org.uk/~sgtatham/putty/",
221 0, 0, SW_SHOWDEFAULT);
226 EndDialog(hwnd, TRUE);
232 static int SaneDialogBox(HINSTANCE hinst,
235 DLGPROC lpDialogFunc)
244 wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
245 wc.lpfnWndProc = DefDlgProc;
247 wc.cbWndExtra = DLGWINDOWEXTRA + 2*sizeof(LONG_PTR);
248 wc.hInstance = hinst;
250 wc.hCursor = LoadCursor(NULL, IDC_ARROW);
251 wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
252 wc.lpszMenuName = NULL;
253 wc.lpszClassName = "PuTTYConfigBox";
256 hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc);
258 SetWindowLongPtr(hwnd, BOXFLAGS, 0); /* flags */
259 SetWindowLongPtr(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
261 while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
262 flags=GetWindowLongPtr(hwnd, BOXFLAGS);
263 if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg))
264 DispatchMessage(&msg);
270 PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */
272 ret=GetWindowLongPtr(hwnd, BOXRESULT);
277 static void SaneEndDialog(HWND hwnd, int ret)
279 SetWindowLongPtr(hwnd, BOXRESULT, ret);
280 SetWindowLongPtr(hwnd, BOXFLAGS, DF_END);
284 * Null dialog procedure.
286 static int CALLBACK NullDlgProc(HWND hwnd, UINT msg,
287 WPARAM wParam, LPARAM lParam)
293 IDCX_ABOUT = IDC_ABOUT,
297 IDCX_PANELBASE = IDCX_STDBASE + 32
300 struct treeview_faff {
305 static HTREEITEM treeview_insert(struct treeview_faff *faff,
306 int level, char *text, char *path)
311 ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT);
312 ins.hInsertAfter = faff->lastat[level];
313 #if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION
314 #define INSITEM DUMMYUNIONNAME.item
318 ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
319 ins.INSITEM.pszText = text;
320 ins.INSITEM.cchTextMax = strlen(text)+1;
321 ins.INSITEM.lParam = (LPARAM)path;
322 newitem = TreeView_InsertItem(faff->treeview, &ins);
324 TreeView_Expand(faff->treeview, faff->lastat[level - 1],
325 (level > 1 ? TVE_COLLAPSE : TVE_EXPAND));
326 faff->lastat[level] = newitem;
327 for (i = level + 1; i < 4; i++)
328 faff->lastat[i] = NULL;
333 * Create the panelfuls of controls in the configuration box.
335 static void create_controls(HWND hwnd, char *path)
344 * Here we must create the basic standard controls.
346 ctlposinit(&cp, hwnd, 3, 3, 235);
348 base_id = IDCX_STDBASE;
351 * Otherwise, we're creating the controls for a particular
354 ctlposinit(&cp, hwnd, 100, 3, 13);
356 base_id = IDCX_PANELBASE;
359 for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) {
360 struct controlset *s = ctrlbox->ctrlsets[index];
361 winctrl_layout(&dp, wc, &cp, s, &base_id);
366 * This function is the configuration box.
367 * (Being a dialog procedure, in general it returns 0 if the default
368 * dialog processing should be performed, and 1 if it should not.)
370 static int CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
371 WPARAM wParam, LPARAM lParam)
374 struct treeview_faff tvfaff;
380 create_controls(hwnd, ""); /* Open and Cancel buttons etc */
381 SetWindowText(hwnd, dp.wintitle);
382 SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
384 SetWindowLongPtr(hwnd, GWL_EXSTYLE,
385 GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
388 HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
392 SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
393 (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
397 { /* centre the window */
400 hw = GetDesktopWindow();
401 if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
403 (rs.right + rs.left + rd.left - rd.right) / 2,
404 (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
405 rd.right - rd.left, rd.bottom - rd.top, TRUE);
409 * Create the tree view.
417 r.right = r.left + 95;
419 r.bottom = r.top + 10;
420 MapDialogRect(hwnd, &r);
421 tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
422 WS_CHILD | WS_VISIBLE,
424 r.right - r.left, r.bottom - r.top,
425 hwnd, (HMENU) IDCX_TVSTATIC, hinst,
427 font = SendMessage(hwnd, WM_GETFONT, 0, 0);
428 SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
431 r.right = r.left + 95;
433 r.bottom = r.top + 219;
434 MapDialogRect(hwnd, &r);
435 treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
436 WS_CHILD | WS_VISIBLE |
437 WS_TABSTOP | TVS_HASLINES |
438 TVS_DISABLEDRAGDROP | TVS_HASBUTTONS
440 TVS_SHOWSELALWAYS, r.left, r.top,
441 r.right - r.left, r.bottom - r.top,
442 hwnd, (HMENU) IDCX_TREEVIEW, hinst,
444 font = SendMessage(hwnd, WM_GETFONT, 0, 0);
445 SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
446 tvfaff.treeview = treeview;
447 memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat));
451 * Set up the tree view contents.
454 HTREEITEM hfirst = NULL;
457 char *firstpath = NULL;
459 for (i = 0; i < ctrlbox->nctrlsets; i++) {
460 struct controlset *s = ctrlbox->ctrlsets[i];
467 j = path ? ctrl_path_compare(s->pathname, path) : 0;
469 continue; /* same path, nothing to add to tree */
472 * We expect never to find an implicit path
473 * component. For example, we expect never to see
474 * A/B/C followed by A/D/E, because that would
475 * _implicitly_ create A/D. All our path prefixes
476 * are expected to contain actual controls and be
477 * selectable in the treeview; so we would expect
478 * to see A/D _explicitly_ before encountering
481 assert(j == ctrl_path_elements(s->pathname) - 1);
483 c = strrchr(s->pathname, '/');
489 item = treeview_insert(&tvfaff, j, c, s->pathname);
492 firstpath = s->pathname;
499 * Put the treeview selection on to the first panel in the
502 TreeView_SelectItem(treeview, hfirst);
505 * And create the actual control set for that panel, to
506 * match the initial treeview selection.
508 assert(firstpath); /* config.c must have given us _something_ */
509 create_controls(hwnd, firstpath);
510 dlg_refresh(NULL, &dp); /* and set up control values */
514 * Set focus into the first available control.
520 for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
523 dlg_set_focus(c->ctrl, &dp);
530 * Now we've finished creating our initial set of controls,
531 * it's safe to actually show the window without risking setup
534 ShowWindow(hwnd, SW_SHOWNORMAL);
537 * Set the flag that activates a couple of the other message
538 * handlers below, which were disabled until now to avoid
539 * spurious firing during the above setup procedure.
541 SetWindowLongPtr(hwnd, GWLP_USERDATA, 1);
545 * Button release should trigger WM_OK if there was a
546 * previous double click on the session list.
550 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
553 if (LOWORD(wParam) == IDCX_TREEVIEW &&
554 ((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
556 * Selection-change events on the treeview cause us to do
557 * a flurry of control deletion and creation - but only
558 * after WM_INITDIALOG has finished. The initial
559 * selection-change event(s) during treeview setup are
566 if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1)
569 i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
571 SendMessage (hwnd, WM_SETREDRAW, FALSE, 0);
574 item.pszText = buffer;
575 item.cchTextMax = sizeof(buffer);
576 item.mask = TVIF_TEXT | TVIF_PARAM;
577 TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
579 /* Destroy all controls in the currently visible panel. */
584 while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
585 for (k = 0; k < c->num_ids; k++) {
586 item = GetDlgItem(hwnd, c->base_id + k);
590 winctrl_rem_shortcuts(&dp, c);
591 winctrl_remove(&ctrls_panel, c);
596 create_controls(hwnd, (char *)item.lParam);
598 dlg_refresh(NULL, &dp); /* set up control values */
600 SendMessage (hwnd, WM_SETREDRAW, TRUE, 0);
601 InvalidateRect (hwnd, NULL, TRUE);
603 SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */
609 default: /* also handle drag list msg here */
611 * Only process WM_COMMAND once the dialog is fully formed.
613 if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) {
614 ret = winctrl_handle_command(&dp, msg, wParam, lParam);
615 if (dp.ended && GetCapture() != hwnd)
616 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
621 if (!winctrl_context_help(&dp, hwnd,
622 ((LPHELPINFO)lParam)->iCtrlId))
627 SaneEndDialog(hwnd, 0);
630 /* Grrr Explorer will maximize Dialogs! */
632 if (wParam == SIZE_MAXIMIZED)
640 void modal_about_box(HWND hwnd)
642 EnableWindow(hwnd, 0);
643 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
644 EnableWindow(hwnd, 1);
645 SetActiveWindow(hwnd);
648 void show_help(HWND hwnd)
650 launch_help(hwnd, NULL);
653 void defuse_showwindow(void)
656 * Work around the fact that the app's first call to ShowWindow
657 * will ignore the default in favour of the shell-provided
662 hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
664 ShowWindow(hwnd, SW_HIDE);
665 SetActiveWindow(hwnd);
674 ctrlbox = ctrl_new_box();
675 setup_config_box(ctrlbox, FALSE, 0, 0);
676 win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), FALSE, 0);
678 winctrl_init(&ctrls_base);
679 winctrl_init(&ctrls_panel);
680 dp_add_tree(&dp, &ctrls_base);
681 dp_add_tree(&dp, &ctrls_panel);
682 dp.wintitle = dupprintf("%s Configuration", appname);
683 dp.errtitle = dupprintf("%s Error", appname);
685 dlg_auto_set_fixed_pitch_flag(&dp);
686 dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
689 SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
692 ctrl_free_box(ctrlbox);
693 winctrl_cleanup(&ctrls_panel);
694 winctrl_cleanup(&ctrls_base);
700 int do_reconfig(HWND hwnd, int protcfginfo)
705 backup_conf = conf_copy(conf);
707 ctrlbox = ctrl_new_box();
708 protocol = conf_get_int(conf, CONF_protocol);
709 setup_config_box(ctrlbox, TRUE, protocol, protcfginfo);
710 win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), TRUE, protocol);
712 winctrl_init(&ctrls_base);
713 winctrl_init(&ctrls_panel);
714 dp_add_tree(&dp, &ctrls_base);
715 dp_add_tree(&dp, &ctrls_panel);
716 dp.wintitle = dupprintf("%s Reconfiguration", appname);
717 dp.errtitle = dupprintf("%s Error", appname);
719 dlg_auto_set_fixed_pitch_flag(&dp);
720 dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
722 ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
725 ctrl_free_box(ctrlbox);
726 winctrl_cleanup(&ctrls_base);
727 winctrl_cleanup(&ctrls_panel);
731 conf_copy_into(conf, backup_conf);
733 conf_free(backup_conf);
738 void logevent(void *frontend, const char *string)
743 log_eventlog(logctx, string);
745 if (nevents >= negsize) {
747 events = sresize(events, negsize, char *);
751 strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
753 events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
754 strcpy(events[nevents], timebuf);
755 strcat(events[nevents], string);
758 SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
759 0, (LPARAM) events[nevents]);
760 count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
761 SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
766 void showeventlog(HWND hwnd)
769 logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
771 ShowWindow(logbox, SW_SHOWNORMAL);
773 SetActiveWindow(logbox);
776 void showabout(HWND hwnd)
778 DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
781 int verify_ssh_host_key(void *frontend, char *host, int port,
782 const char *keytype, char *keystr, char *fingerprint,
783 void (*callback)(void *ctx, int result), void *ctx)
787 static const char absentmsg[] =
788 "The server's host key is not cached in the registry. You\n"
789 "have no guarantee that the server is the computer you\n"
791 "The server's %s key fingerprint is:\n"
793 "If you trust this host, hit Yes to add the key to\n"
794 "%s's cache and carry on connecting.\n"
795 "If you want to carry on connecting just once, without\n"
796 "adding the key to the cache, hit No.\n"
797 "If you do not trust this host, hit Cancel to abandon the\n"
800 static const char wrongmsg[] =
801 "WARNING - POTENTIAL SECURITY BREACH!\n"
803 "The server's host key does not match the one %s has\n"
804 "cached in the registry. This means that either the\n"
805 "server administrator has changed the host key, or you\n"
806 "have actually connected to another computer pretending\n"
807 "to be the server.\n"
808 "The new %s key fingerprint is:\n"
810 "If you were expecting this change and trust the new key,\n"
811 "hit Yes to update %s's cache and continue connecting.\n"
812 "If you want to carry on connecting but without updating\n"
813 "the cache, hit No.\n"
814 "If you want to abandon the connection completely, hit\n"
815 "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
817 static const char mbtitle[] = "%s Security Alert";
820 * Verify the key against the registry.
822 ret = verify_host_key(host, port, keytype, keystr);
824 if (ret == 0) /* success - key matched OK */
826 else if (ret == 2) { /* key was different */
828 char *text = dupprintf(wrongmsg, appname, keytype, fingerprint,
830 char *caption = dupprintf(mbtitle, appname);
831 mbret = message_box(text, caption,
832 MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
833 HELPCTXID(errors_hostkey_changed));
834 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
837 if (mbret == IDYES) {
838 store_host_key(host, port, keytype, keystr);
840 } else if (mbret == IDNO)
842 } else if (ret == 1) { /* key was absent */
844 char *text = dupprintf(absentmsg, keytype, fingerprint, appname);
845 char *caption = dupprintf(mbtitle, appname);
846 mbret = message_box(text, caption,
847 MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
848 HELPCTXID(errors_hostkey_absent));
849 assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
852 if (mbret == IDYES) {
853 store_host_key(host, port, keytype, keystr);
855 } else if (mbret == IDNO)
858 return 0; /* abandon the connection */
862 * Ask whether the selected algorithm is acceptable (since it was
863 * below the configured 'warn' threshold).
865 int askalg(void *frontend, const char *algtype, const char *algname,
866 void (*callback)(void *ctx, int result), void *ctx)
868 static const char mbtitle[] = "%s Security Alert";
869 static const char msg[] =
870 "The first %s supported by the server\n"
871 "is %.64s, which is below the configured\n"
872 "warning threshold.\n"
873 "Do you want to continue with this connection?\n";
874 char *message, *title;
877 message = dupprintf(msg, algtype, algname);
878 title = dupprintf(mbtitle, appname);
879 mbret = MessageBox(NULL, message, title,
880 MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
881 socket_reselect_all();
891 * Ask whether to wipe a session log file before writing to it.
892 * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
894 int askappend(void *frontend, Filename *filename,
895 void (*callback)(void *ctx, int result), void *ctx)
897 static const char msgtemplate[] =
898 "The session log file \"%.*s\" already exists.\n"
899 "You can overwrite it with a new session log,\n"
900 "append your session log to the end of it,\n"
901 "or disable session logging for this session.\n"
902 "Hit Yes to wipe the file, No to append to it,\n"
903 "or Cancel to disable logging.";
908 message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
909 mbtitle = dupprintf("%s Log to File", appname);
911 mbret = MessageBox(NULL, message, mbtitle,
912 MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
914 socket_reselect_all();
921 else if (mbret == IDNO)
928 * Warn about the obsolescent key file format.
930 * Uniquely among these functions, this one does _not_ expect a
931 * frontend handle. This means that if PuTTY is ported to a
932 * platform which requires frontend handles, this function will be
933 * an anomaly. Fortunately, the problem it addresses will not have
934 * been present on that platform, so it can plausibly be
935 * implemented as an empty function.
937 void old_keyfile_warning(void)
939 static const char mbtitle[] = "%s Key File Warning";
940 static const char message[] =
941 "You are loading an SSH-2 private key which has an\n"
942 "old version of the file format. This means your key\n"
943 "file is not fully tamperproof. Future versions of\n"
944 "%s may stop supporting this private key format,\n"
945 "so we recommend you convert your key to the new\n"
948 "You can perform this conversion by loading the key\n"
949 "into PuTTYgen and then saving it again.";
952 msg = dupprintf(message, appname);
953 title = dupprintf(mbtitle, appname);
955 MessageBox(NULL, msg, title, MB_OK);
957 socket_reselect_all();