]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/windlg.c
5aa20d2c7d649046fa0ceb69e8e12e2e9461753e
[PuTTY.git] / windows / windlg.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <limits.h>
4 #include <assert.h>
5 #include <ctype.h>
6 #include <time.h>
7
8 #include "putty.h"
9 #include "ssh.h"
10 #include "win_res.h"
11 #include "storage.h"
12 #include "dialog.h"
13
14 #include <commctrl.h>
15 #include <commdlg.h>
16 #include <shellapi.h>
17
18 #ifdef MSVC4
19 #define TVINSERTSTRUCT  TV_INSERTSTRUCT
20 #define TVITEM          TV_ITEM
21 #define ICON_BIG        1
22 #endif
23
24 /*
25  * These are the various bits of data required to handle the
26  * portable-dialog stuff in the config box. Having them at file
27  * scope in here isn't too bad a place to put them; if we were ever
28  * to need more than one config box per process we could always
29  * shift them to a per-config-box structure stored in GWL_USERDATA.
30  */
31 static struct controlbox *ctrlbox;
32 /*
33  * ctrls_base holds the OK and Cancel buttons: the controls which
34  * are present in all dialog panels. ctrls_panel holds the ones
35  * which change from panel to panel.
36  */
37 static struct winctrls ctrls_base, ctrls_panel;
38 static struct dlgparam dp;
39
40 static char **events = NULL;
41 static int nevents = 0, negsize = 0;
42
43 static int requested_help;
44
45 extern Config cfg;                     /* defined in window.c */
46
47 struct sesslist sesslist;              /* exported to window.c */
48
49 #define PRINTER_DISABLED_STRING "None (printing disabled)"
50
51 void force_normal(HWND hwnd)
52 {
53     static int recurse = 0;
54
55     WINDOWPLACEMENT wp;
56
57     if (recurse)
58         return;
59     recurse = 1;
60
61     wp.length = sizeof(wp);
62     if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
63         wp.showCmd = SW_SHOWNORMAL;
64         SetWindowPlacement(hwnd, &wp);
65     }
66     recurse = 0;
67 }
68
69 static int CALLBACK LogProc(HWND hwnd, UINT msg,
70                             WPARAM wParam, LPARAM lParam)
71 {
72     int i;
73
74     switch (msg) {
75       case WM_INITDIALOG:
76         {
77             char *str = dupprintf("%s Event Log", appname);
78             SetWindowText(hwnd, str);
79             sfree(str);
80         }
81         {
82             static int tabs[4] = { 78, 108 };
83             SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2,
84                                (LPARAM) tabs);
85         }
86         for (i = 0; i < nevents; i++)
87             SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
88                                0, (LPARAM) events[i]);
89         return 1;
90       case WM_COMMAND:
91         switch (LOWORD(wParam)) {
92           case IDOK:
93           case IDCANCEL:
94             logbox = NULL;
95             SetActiveWindow(GetParent(hwnd));
96             DestroyWindow(hwnd);
97             return 0;
98           case IDN_COPY:
99             if (HIWORD(wParam) == BN_CLICKED ||
100                 HIWORD(wParam) == BN_DOUBLECLICKED) {
101                 int selcount;
102                 int *selitems;
103                 selcount = SendDlgItemMessage(hwnd, IDN_LIST,
104                                               LB_GETSELCOUNT, 0, 0);
105                 if (selcount == 0) {   /* don't even try to copy zero items */
106                     MessageBeep(0);
107                     break;
108                 }
109
110                 selitems = snewn(selcount, int);
111                 if (selitems) {
112                     int count = SendDlgItemMessage(hwnd, IDN_LIST,
113                                                    LB_GETSELITEMS,
114                                                    selcount,
115                                                    (LPARAM) selitems);
116                     int i;
117                     int size;
118                     char *clipdata;
119                     static unsigned char sel_nl[] = SEL_NL;
120
121                     if (count == 0) {  /* can't copy zero stuff */
122                         MessageBeep(0);
123                         break;
124                     }
125
126                     size = 0;
127                     for (i = 0; i < count; i++)
128                         size +=
129                             strlen(events[selitems[i]]) + sizeof(sel_nl);
130
131                     clipdata = snewn(size, char);
132                     if (clipdata) {
133                         char *p = clipdata;
134                         for (i = 0; i < count; i++) {
135                             char *q = events[selitems[i]];
136                             int qlen = strlen(q);
137                             memcpy(p, q, qlen);
138                             p += qlen;
139                             memcpy(p, sel_nl, sizeof(sel_nl));
140                             p += sizeof(sel_nl);
141                         }
142                         write_aclip(NULL, clipdata, size, TRUE);
143                         sfree(clipdata);
144                     }
145                     sfree(selitems);
146
147                     for (i = 0; i < nevents; i++)
148                         SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL,
149                                            FALSE, i);
150                 }
151             }
152             return 0;
153         }
154         return 0;
155       case WM_CLOSE:
156         logbox = NULL;
157         SetActiveWindow(GetParent(hwnd));
158         DestroyWindow(hwnd);
159         return 0;
160     }
161     return 0;
162 }
163
164 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
165                                 WPARAM wParam, LPARAM lParam)
166 {
167     switch (msg) {
168       case WM_INITDIALOG:
169         {
170             char *str = dupprintf("%s Licence", appname);
171             SetWindowText(hwnd, str);
172             sfree(str);
173         }
174         return 1;
175       case WM_COMMAND:
176         switch (LOWORD(wParam)) {
177           case IDOK:
178           case IDCANCEL:
179             EndDialog(hwnd, 1);
180             return 0;
181         }
182         return 0;
183       case WM_CLOSE:
184         EndDialog(hwnd, 1);
185         return 0;
186     }
187     return 0;
188 }
189
190 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
191                               WPARAM wParam, LPARAM lParam)
192 {
193     char *str;
194
195     switch (msg) {
196       case WM_INITDIALOG:
197         str = dupprintf("About %s", appname);
198         SetWindowText(hwnd, str);
199         sfree(str);
200         SetDlgItemText(hwnd, IDA_TEXT1, appname);
201         SetDlgItemText(hwnd, IDA_VERSION, ver);
202         return 1;
203       case WM_COMMAND:
204         switch (LOWORD(wParam)) {
205           case IDOK:
206           case IDCANCEL:
207             EndDialog(hwnd, TRUE);
208             return 0;
209           case IDA_LICENCE:
210             EnableWindow(hwnd, 0);
211             DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
212                       hwnd, LicenceProc);
213             EnableWindow(hwnd, 1);
214             SetActiveWindow(hwnd);
215             return 0;
216
217           case IDA_WEB:
218             /* Load web browser */
219             ShellExecute(hwnd, "open",
220                          "http://www.chiark.greenend.org.uk/~sgtatham/putty/",
221                          0, 0, SW_SHOWDEFAULT);
222             return 0;
223         }
224         return 0;
225       case WM_CLOSE:
226         EndDialog(hwnd, TRUE);
227         return 0;
228     }
229     return 0;
230 }
231
232 static int SaneDialogBox(HINSTANCE hinst,
233                          LPCTSTR tmpl,
234                          HWND hwndparent,
235                          DLGPROC lpDialogFunc)
236 {
237     WNDCLASS wc;
238     HWND hwnd;
239     MSG msg;
240     int flags;
241     int ret;
242     int gm;
243
244     wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
245     wc.lpfnWndProc = DefDlgProc;
246     wc.cbClsExtra = 0;
247     wc.cbWndExtra = DLGWINDOWEXTRA + 8;
248     wc.hInstance = hinst;
249     wc.hIcon = NULL;
250     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
251     wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
252     wc.lpszMenuName = NULL;
253     wc.lpszClassName = "PuTTYConfigBox";
254     RegisterClass(&wc);
255
256     hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc);
257
258     SetWindowLong(hwnd, BOXFLAGS, 0); /* flags */
259     SetWindowLong(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
260
261     while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
262         flags=GetWindowLong(hwnd, BOXFLAGS);
263         if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg))
264             DispatchMessage(&msg);
265         if (flags & DF_END)
266             break;
267     }
268
269     if (gm == 0)
270         PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */
271
272     ret=GetWindowLong(hwnd, BOXRESULT);
273     DestroyWindow(hwnd);
274     return ret;
275 }
276
277 static void SaneEndDialog(HWND hwnd, int ret)
278 {
279     SetWindowLong(hwnd, BOXRESULT, ret);
280     SetWindowLong(hwnd, BOXFLAGS, DF_END);
281 }
282
283 /*
284  * Null dialog procedure.
285  */
286 static int CALLBACK NullDlgProc(HWND hwnd, UINT msg,
287                                 WPARAM wParam, LPARAM lParam)
288 {
289     return 0;
290 }
291
292 enum {
293     IDCX_ABOUT = IDC_ABOUT,
294     IDCX_TVSTATIC,
295     IDCX_TREEVIEW,
296     IDCX_STDBASE,
297     IDCX_PANELBASE = IDCX_STDBASE + 32
298 };
299
300 struct treeview_faff {
301     HWND treeview;
302     HTREEITEM lastat[4];
303 };
304
305 static HTREEITEM treeview_insert(struct treeview_faff *faff,
306                                  int level, char *text, char *path)
307 {
308     TVINSERTSTRUCT ins;
309     int i;
310     HTREEITEM newitem;
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
315 #else
316 #define INSITEM item
317 #endif
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);
323     if (level > 0)
324         TreeView_Expand(faff->treeview, faff->lastat[level - 1],
325                         TVE_EXPAND);
326     faff->lastat[level] = newitem;
327     for (i = level + 1; i < 4; i++)
328         faff->lastat[i] = NULL;
329     return newitem;
330 }
331
332 /*
333  * Create the panelfuls of controls in the configuration box.
334  */
335 static void create_controls(HWND hwnd, char *path)
336 {
337     struct ctlpos cp;
338     int index;
339     int base_id;
340     struct winctrls *wc;
341
342     if (!path[0]) {
343         /*
344          * Here we must create the basic standard controls.
345          */
346         ctlposinit(&cp, hwnd, 3, 3, 235);
347         wc = &ctrls_base;
348         base_id = IDCX_STDBASE;
349     } else {
350         /*
351          * Otherwise, we're creating the controls for a particular
352          * panel.
353          */
354         ctlposinit(&cp, hwnd, 100, 3, 13);
355         wc = &ctrls_panel;
356         base_id = IDCX_PANELBASE;
357     }
358
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);
362     }
363 }
364
365 /*
366  * This function is the configuration box.
367  */
368 static int CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
369                                        WPARAM wParam, LPARAM lParam)
370 {
371     HWND hw, treeview;
372     struct treeview_faff tvfaff;
373     int ret;
374
375     switch (msg) {
376       case WM_INITDIALOG:
377         dp.hwnd = hwnd;
378         create_controls(hwnd, "");     /* Open and Cancel buttons etc */
379         SetWindowText(hwnd, dp.wintitle);
380         SetWindowLong(hwnd, GWL_USERDATA, 0);
381         if (help_path)
382             SetWindowLong(hwnd, GWL_EXSTYLE,
383                           GetWindowLong(hwnd, GWL_EXSTYLE) | WS_EX_CONTEXTHELP);
384         else {
385             HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
386             if (item)
387                 DestroyWindow(item);
388         }
389         requested_help = FALSE;
390         SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
391                     (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
392         /*
393          * Centre the window.
394          */
395         {                              /* centre the window */
396             RECT rs, rd;
397
398             hw = GetDesktopWindow();
399             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
400                 MoveWindow(hwnd,
401                            (rs.right + rs.left + rd.left - rd.right) / 2,
402                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
403                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
404         }
405
406         /*
407          * Create the tree view.
408          */
409         {
410             RECT r;
411             WPARAM font;
412             HWND tvstatic;
413
414             r.left = 3;
415             r.right = r.left + 95;
416             r.top = 3;
417             r.bottom = r.top + 10;
418             MapDialogRect(hwnd, &r);
419             tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
420                                       WS_CHILD | WS_VISIBLE,
421                                       r.left, r.top,
422                                       r.right - r.left, r.bottom - r.top,
423                                       hwnd, (HMENU) IDCX_TVSTATIC, hinst,
424                                       NULL);
425             font = SendMessage(hwnd, WM_GETFONT, 0, 0);
426             SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
427
428             r.left = 3;
429             r.right = r.left + 95;
430             r.top = 13;
431             r.bottom = r.top + 219;
432             MapDialogRect(hwnd, &r);
433             treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
434                                       WS_CHILD | WS_VISIBLE |
435                                       WS_TABSTOP | TVS_HASLINES |
436                                       TVS_DISABLEDRAGDROP | TVS_HASBUTTONS
437                                       | TVS_LINESATROOT |
438                                       TVS_SHOWSELALWAYS, r.left, r.top,
439                                       r.right - r.left, r.bottom - r.top,
440                                       hwnd, (HMENU) IDCX_TREEVIEW, hinst,
441                                       NULL);
442             font = SendMessage(hwnd, WM_GETFONT, 0, 0);
443             SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
444             tvfaff.treeview = treeview;
445             memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat));
446         }
447
448         /*
449          * Set up the tree view contents.
450          */
451         {
452             HTREEITEM hfirst = NULL;
453             int i;
454             char *path = NULL;
455
456             for (i = 0; i < ctrlbox->nctrlsets; i++) {
457                 struct controlset *s = ctrlbox->ctrlsets[i];
458                 HTREEITEM item;
459                 int j;
460                 char *c;
461
462                 if (!s->pathname[0])
463                     continue;
464                 j = path ? ctrl_path_compare(s->pathname, path) : 0;
465                 if (j == INT_MAX)
466                     continue;          /* same path, nothing to add to tree */
467
468                 /*
469                  * We expect never to find an implicit path
470                  * component. For example, we expect never to see
471                  * A/B/C followed by A/D/E, because that would
472                  * _implicitly_ create A/D. All our path prefixes
473                  * are expected to contain actual controls and be
474                  * selectable in the treeview; so we would expect
475                  * to see A/D _explicitly_ before encountering
476                  * A/D/E.
477                  */
478                 assert(j == ctrl_path_elements(s->pathname) - 1);
479
480                 c = strrchr(s->pathname, '/');
481                 if (!c)
482                         c = s->pathname;
483                 else
484                         c++;
485
486                 item = treeview_insert(&tvfaff, j, c, s->pathname);
487                 if (!hfirst)
488                     hfirst = item;
489
490                 path = s->pathname;
491             }
492
493             /*
494              * Put the treeview selection on to the Session panel.
495              * This should also cause creation of the relevant
496              * controls.
497              */
498             TreeView_SelectItem(treeview, hfirst);
499         }
500
501         /*
502          * Set focus into the first available control.
503          */
504         {
505             int i;
506             struct winctrl *c;
507
508             for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
509                  i++) {
510                 if (c->ctrl) {
511                     dlg_set_focus(c->ctrl, &dp);
512                     break;
513                 }
514             }
515         }
516
517         SetWindowLong(hwnd, GWL_USERDATA, 1);
518         return 0;
519       case WM_LBUTTONUP:
520         /*
521          * Button release should trigger WM_OK if there was a
522          * previous double click on the session list.
523          */
524         ReleaseCapture();
525         if (dp.ended)
526             SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
527         break;
528       case WM_NOTIFY:
529         if (LOWORD(wParam) == IDCX_TREEVIEW &&
530             ((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
531             HTREEITEM i =
532                 TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
533             TVITEM item;
534             char buffer[64];
535  
536             SendMessage (hwnd, WM_SETREDRAW, FALSE, 0);
537  
538             item.hItem = i;
539             item.pszText = buffer;
540             item.cchTextMax = sizeof(buffer);
541             item.mask = TVIF_TEXT | TVIF_PARAM;
542             TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
543             {
544                 /* Destroy all controls in the currently visible panel. */
545                 int k;
546                 HWND item;
547                 struct winctrl *c;
548
549                 while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
550                     for (k = 0; k < c->num_ids; k++) {
551                         item = GetDlgItem(hwnd, c->base_id + k);
552                         if (item)
553                             DestroyWindow(item);
554                     }
555                     winctrl_rem_shortcuts(&dp, c);
556                     winctrl_remove(&ctrls_panel, c);
557                     sfree(c->data);
558                     sfree(c);
559                 }
560             }
561             create_controls(hwnd, (char *)item.lParam);
562
563             dlg_refresh(NULL, &dp);    /* set up control values */
564  
565             SendMessage (hwnd, WM_SETREDRAW, TRUE, 0);
566             InvalidateRect (hwnd, NULL, TRUE);
567
568             SetFocus(((LPNMHDR) lParam)->hwndFrom);     /* ensure focus stays */
569             return 0;
570         }
571         break;
572       case WM_COMMAND:
573       case WM_DRAWITEM:
574       default:                         /* also handle drag list msg here */
575         /*
576          * Only process WM_COMMAND once the dialog is fully formed.
577          */
578         if (GetWindowLong(hwnd, GWL_USERDATA) == 1) {
579             ret = winctrl_handle_command(&dp, msg, wParam, lParam);
580             if (dp.ended && GetCapture() != hwnd)
581                 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
582         } else
583             ret = 0;
584         return ret;
585       case WM_HELP:
586         if (help_path) {
587             if (winctrl_context_help(&dp, hwnd,
588                                      ((LPHELPINFO)lParam)->iCtrlId))
589                 requested_help = TRUE;
590             else
591                 MessageBeep(0);
592         }
593         break;
594       case WM_CLOSE:
595         if (requested_help) {
596             WinHelp(hwnd, help_path, HELP_QUIT, 0);
597             requested_help = FALSE;
598         }
599         SaneEndDialog(hwnd, 0);
600         return 0;
601
602         /* Grrr Explorer will maximize Dialogs! */
603       case WM_SIZE:
604         if (wParam == SIZE_MAXIMIZED)
605             force_normal(hwnd);
606         return 0;
607
608     }
609     return 0;
610 }
611
612 void modal_about_box(HWND hwnd)
613 {
614     EnableWindow(hwnd, 0);
615     DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
616     EnableWindow(hwnd, 1);
617     SetActiveWindow(hwnd);
618 }
619
620 void show_help(HWND hwnd)
621 {
622     if (help_path) {
623         WinHelp(hwnd, help_path,
624                 help_has_contents ? HELP_FINDER : HELP_CONTENTS,
625                 0);
626         requested_help = TRUE;
627     }
628 }
629
630 void defuse_showwindow(void)
631 {
632     /*
633      * Work around the fact that the app's first call to ShowWindow
634      * will ignore the default in favour of the shell-provided
635      * setting.
636      */
637     {
638         HWND hwnd;
639         hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
640                             NULL, NullDlgProc);
641         ShowWindow(hwnd, SW_HIDE);
642         SetActiveWindow(hwnd);
643         DestroyWindow(hwnd);
644     }
645 }
646
647 int do_config(void)
648 {
649     int ret;
650
651     ctrlbox = ctrl_new_box();
652     setup_config_box(ctrlbox, &sesslist, FALSE, 0, 0);
653     win_setup_config_box(ctrlbox, &dp.hwnd, (help_path != NULL), FALSE);
654     dp_init(&dp);
655     winctrl_init(&ctrls_base);
656     winctrl_init(&ctrls_panel);
657     dp_add_tree(&dp, &ctrls_base);
658     dp_add_tree(&dp, &ctrls_panel);
659     dp.wintitle = dupprintf("%s Configuration", appname);
660     dp.errtitle = dupprintf("%s Error", appname);
661     dp.data = &cfg;
662     dp.shortcuts['g'] = TRUE;          /* the treeview: `Cate&gory' */
663
664     get_sesslist(&sesslist, TRUE);
665     ret =
666         SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
667                   GenericMainDlgProc);
668     get_sesslist(&sesslist, FALSE);
669
670     ctrl_free_box(ctrlbox);
671     winctrl_cleanup(&ctrls_panel);
672     winctrl_cleanup(&ctrls_base);
673     dp_cleanup(&dp);
674
675     return ret;
676 }
677
678 int do_reconfig(HWND hwnd, int protcfginfo)
679 {
680     Config backup_cfg;
681     int ret;
682
683     backup_cfg = cfg;                  /* structure copy */
684
685     ctrlbox = ctrl_new_box();
686     setup_config_box(ctrlbox, &sesslist, TRUE, cfg.protocol, protcfginfo);
687     win_setup_config_box(ctrlbox, &dp.hwnd, (help_path != NULL), TRUE);
688     dp_init(&dp);
689     winctrl_init(&ctrls_base);
690     winctrl_init(&ctrls_panel);
691     dp_add_tree(&dp, &ctrls_base);
692     dp_add_tree(&dp, &ctrls_panel);
693     dp.wintitle = dupprintf("%s Reconfiguration", appname);
694     dp.errtitle = dupprintf("%s Error", appname);
695     dp.data = &cfg;
696     dp.shortcuts['g'] = TRUE;          /* the treeview: `Cate&gory' */
697
698     ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
699                   GenericMainDlgProc);
700
701     ctrl_free_box(ctrlbox);
702     winctrl_cleanup(&ctrls_base);
703     winctrl_cleanup(&ctrls_panel);
704     dp_cleanup(&dp);
705
706     if (!ret)
707         cfg = backup_cfg;              /* structure copy */
708
709     return ret;
710 }
711
712 void logevent(void *frontend, const char *string)
713 {
714     char timebuf[40];
715     struct tm tm;
716
717     log_eventlog(logctx, string);
718
719     if (nevents >= negsize) {
720         negsize += 64;
721         events = sresize(events, negsize, char *);
722     }
723
724     tm=ltime();
725     strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
726
727     events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
728     strcpy(events[nevents], timebuf);
729     strcat(events[nevents], string);
730     if (logbox) {
731         int count;
732         SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
733                            0, (LPARAM) events[nevents]);
734         count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
735         SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
736     }
737     nevents++;
738 }
739
740 void showeventlog(HWND hwnd)
741 {
742     if (!logbox) {
743         logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
744                               hwnd, LogProc);
745         ShowWindow(logbox, SW_SHOWNORMAL);
746     }
747     SetActiveWindow(logbox);
748 }
749
750 void showabout(HWND hwnd)
751 {
752     DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
753 }
754
755 /* Helper function for verify_ssh_host_key(). */
756 static VOID CALLBACK verify_ssh_host_key_help(LPHELPINFO lpHelpInfo)
757 {
758     if (help_path) {
759         char *context = NULL;
760 #define CHECK_CTX(name) \
761         do { \
762             if (lpHelpInfo->dwContextId == WINHELP_CTXID_ ## name) \
763                 context = WINHELP_CTX_ ## name; \
764         } while (0)
765         CHECK_CTX(errors_hostkey_absent);
766         CHECK_CTX(errors_hostkey_changed);
767 #undef CHECK_CTX
768         if (context) {
769             char *cmd = dupprintf("JI(`',`%s')", context);
770             WinHelp(hwnd, help_path, HELP_COMMAND, (DWORD)cmd);
771             sfree(cmd);
772             requested_help = TRUE;
773         }
774     }
775 }
776
777 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
778                         char *keystr, char *fingerprint,
779                         void (*callback)(void *ctx, int result), void *ctx)
780 {
781     int ret;
782
783     static const char absentmsg[] =
784         "The server's host key is not cached in the registry. You\n"
785         "have no guarantee that the server is the computer you\n"
786         "think it is.\n"
787         "The server's %s key fingerprint is:\n"
788         "%s\n"
789         "If you trust this host, hit Yes to add the key to\n"
790         "%s's cache and carry on connecting.\n"
791         "If you want to carry on connecting just once, without\n"
792         "adding the key to the cache, hit No.\n"
793         "If you do not trust this host, hit Cancel to abandon the\n"
794         "connection.\n";
795
796     static const char wrongmsg[] =
797         "WARNING - POTENTIAL SECURITY BREACH!\n"
798         "\n"
799         "The server's host key does not match the one %s has\n"
800         "cached in the registry. This means that either the\n"
801         "server administrator has changed the host key, or you\n"
802         "have actually connected to another computer pretending\n"
803         "to be the server.\n"
804         "The new %s key fingerprint is:\n"
805         "%s\n"
806         "If you were expecting this change and trust the new key,\n"
807         "hit Yes to update %s's cache and continue connecting.\n"
808         "If you want to carry on connecting but without updating\n"
809         "the cache, hit No.\n"
810         "If you want to abandon the connection completely, hit\n"
811         "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
812
813     static const char mbtitle[] = "%s Security Alert";
814
815     UINT help_button = 0;
816     MSGBOXPARAMS mbox;
817
818     /*
819      * We use MessageBoxIndirect() because it allows us to specify a
820      * callback function for the Help button.
821      */
822     mbox.cbSize = sizeof(mbox);
823     mbox.hInstance = hinst;
824     mbox.hwndOwner = hwnd;
825     mbox.lpfnMsgBoxCallback = &verify_ssh_host_key_help;
826     mbox.dwLanguageId = LANG_NEUTRAL;
827
828     /* Do we have a help file? */
829     if (help_path)
830         help_button = MB_HELP;
831
832     /*
833      * Verify the key against the registry.
834      */
835     ret = verify_host_key(host, port, keytype, keystr);
836
837     if (ret == 0)                      /* success - key matched OK */
838         return 1;
839     if (ret == 2) {                    /* key was different */
840         int mbret;
841         mbox.lpszText = dupprintf(wrongmsg, appname, keytype, fingerprint,
842                                   appname);
843         mbox.lpszCaption = dupprintf(mbtitle, appname);
844         mbox.dwContextHelpId = HELPCTXID(errors_hostkey_changed);
845         mbox.dwStyle = MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3 |
846             help_button;
847         mbret = MessageBoxIndirect(&mbox);
848         assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
849         sfree((void *)mbox.lpszText);
850         sfree((void *)mbox.lpszCaption);
851         if (mbret == IDYES) {
852             store_host_key(host, port, keytype, keystr);
853             return 1;
854         } else if (mbret == IDNO)
855             return 1;
856         return 0;
857     }
858     if (ret == 1) {                    /* key was absent */
859         int mbret;
860         mbox.lpszText = dupprintf(absentmsg, keytype, fingerprint, appname);
861         mbox.lpszCaption = dupprintf(mbtitle, appname);
862         mbox.dwContextHelpId = HELPCTXID(errors_hostkey_absent);
863         mbox.dwStyle = MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3 |
864             help_button;
865         mbret = MessageBoxIndirect(&mbox);
866         assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
867         sfree((void *)mbox.lpszText);
868         sfree((void *)mbox.lpszCaption);
869         if (mbret == IDYES)
870             store_host_key(host, port, keytype, keystr);
871         if (mbret == IDNO)
872             return 1;
873         return 0;
874     }
875 }
876
877 /*
878  * Ask whether the selected algorithm is acceptable (since it was
879  * below the configured 'warn' threshold).
880  */
881 int askalg(void *frontend, const char *algtype, const char *algname,
882            void (*callback)(void *ctx, int result), void *ctx)
883 {
884     static const char mbtitle[] = "%s Security Alert";
885     static const char msg[] =
886         "The first %s supported by the server\n"
887         "is %.64s, which is below the configured\n"
888         "warning threshold.\n"
889         "Do you want to continue with this connection?\n";
890     char *message, *title;
891     int mbret;
892
893     message = dupprintf(msg, algtype, algname);
894     title = dupprintf(mbtitle, appname);
895     mbret = MessageBox(NULL, message, title,
896                        MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
897     sfree(message);
898     sfree(title);
899     if (mbret == IDYES)
900         return 1;
901     else
902         return 0;
903 }
904
905 /*
906  * Ask whether to wipe a session log file before writing to it.
907  * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
908  */
909 int askappend(void *frontend, Filename filename,
910               void (*callback)(void *ctx, int result), void *ctx)
911 {
912     static const char msgtemplate[] =
913         "The session log file \"%.*s\" already exists.\n"
914         "You can overwrite it with a new session log,\n"
915         "append your session log to the end of it,\n"
916         "or disable session logging for this session.\n"
917         "Hit Yes to wipe the file, No to append to it,\n"
918         "or Cancel to disable logging.";
919     char *message;
920     char *mbtitle;
921     int mbret;
922
923     message = dupprintf(msgtemplate, FILENAME_MAX, filename.path);
924     mbtitle = dupprintf("%s Log to File", appname);
925
926     mbret = MessageBox(NULL, message, mbtitle,
927                        MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
928
929     sfree(message);
930     sfree(mbtitle);
931
932     if (mbret == IDYES)
933         return 2;
934     else if (mbret == IDNO)
935         return 1;
936     else
937         return 0;
938 }
939
940 /*
941  * Warn about the obsolescent key file format.
942  * 
943  * Uniquely among these functions, this one does _not_ expect a
944  * frontend handle. This means that if PuTTY is ported to a
945  * platform which requires frontend handles, this function will be
946  * an anomaly. Fortunately, the problem it addresses will not have
947  * been present on that platform, so it can plausibly be
948  * implemented as an empty function.
949  */
950 void old_keyfile_warning(void)
951 {
952     static const char mbtitle[] = "%s Key File Warning";
953     static const char message[] =
954         "You are loading an SSH 2 private key which has an\n"
955         "old version of the file format. This means your key\n"
956         "file is not fully tamperproof. Future versions of\n"
957         "%s may stop supporting this private key format,\n"
958         "so we recommend you convert your key to the new\n"
959         "format.\n"
960         "\n"
961         "You can perform this conversion by loading the key\n"
962         "into PuTTYgen and then saving it again.";
963
964     char *msg, *title;
965     msg = dupprintf(message, appname);
966     title = dupprintf(mbtitle, appname);
967
968     MessageBox(NULL, msg, title, MB_OK);
969
970     sfree(msg);
971     sfree(title);
972 }