]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/windlg.c
Add comments about default processing in DialogProc/WindowProc, since I
[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 extern Config cfg;                     /* defined in window.c */
44
45 struct sesslist sesslist;              /* exported to window.c */
46
47 #define PRINTER_DISABLED_STRING "None (printing disabled)"
48
49 void force_normal(HWND hwnd)
50 {
51     static int recurse = 0;
52
53     WINDOWPLACEMENT wp;
54
55     if (recurse)
56         return;
57     recurse = 1;
58
59     wp.length = sizeof(wp);
60     if (GetWindowPlacement(hwnd, &wp) && wp.showCmd == SW_SHOWMAXIMIZED) {
61         wp.showCmd = SW_SHOWNORMAL;
62         SetWindowPlacement(hwnd, &wp);
63     }
64     recurse = 0;
65 }
66
67 static int CALLBACK LogProc(HWND hwnd, UINT msg,
68                             WPARAM wParam, LPARAM lParam)
69 {
70     int i;
71
72     switch (msg) {
73       case WM_INITDIALOG:
74         {
75             char *str = dupprintf("%s Event Log", appname);
76             SetWindowText(hwnd, str);
77             sfree(str);
78         }
79         {
80             static int tabs[4] = { 78, 108 };
81             SendDlgItemMessage(hwnd, IDN_LIST, LB_SETTABSTOPS, 2,
82                                (LPARAM) tabs);
83         }
84         for (i = 0; i < nevents; i++)
85             SendDlgItemMessage(hwnd, IDN_LIST, LB_ADDSTRING,
86                                0, (LPARAM) events[i]);
87         return 1;
88       case WM_COMMAND:
89         switch (LOWORD(wParam)) {
90           case IDOK:
91           case IDCANCEL:
92             logbox = NULL;
93             SetActiveWindow(GetParent(hwnd));
94             DestroyWindow(hwnd);
95             return 0;
96           case IDN_COPY:
97             if (HIWORD(wParam) == BN_CLICKED ||
98                 HIWORD(wParam) == BN_DOUBLECLICKED) {
99                 int selcount;
100                 int *selitems;
101                 selcount = SendDlgItemMessage(hwnd, IDN_LIST,
102                                               LB_GETSELCOUNT, 0, 0);
103                 if (selcount == 0) {   /* don't even try to copy zero items */
104                     MessageBeep(0);
105                     break;
106                 }
107
108                 selitems = snewn(selcount, int);
109                 if (selitems) {
110                     int count = SendDlgItemMessage(hwnd, IDN_LIST,
111                                                    LB_GETSELITEMS,
112                                                    selcount,
113                                                    (LPARAM) selitems);
114                     int i;
115                     int size;
116                     char *clipdata;
117                     static unsigned char sel_nl[] = SEL_NL;
118
119                     if (count == 0) {  /* can't copy zero stuff */
120                         MessageBeep(0);
121                         break;
122                     }
123
124                     size = 0;
125                     for (i = 0; i < count; i++)
126                         size +=
127                             strlen(events[selitems[i]]) + sizeof(sel_nl);
128
129                     clipdata = snewn(size, char);
130                     if (clipdata) {
131                         char *p = clipdata;
132                         for (i = 0; i < count; i++) {
133                             char *q = events[selitems[i]];
134                             int qlen = strlen(q);
135                             memcpy(p, q, qlen);
136                             p += qlen;
137                             memcpy(p, sel_nl, sizeof(sel_nl));
138                             p += sizeof(sel_nl);
139                         }
140                         write_aclip(NULL, clipdata, size, TRUE);
141                         sfree(clipdata);
142                     }
143                     sfree(selitems);
144
145                     for (i = 0; i < nevents; i++)
146                         SendDlgItemMessage(hwnd, IDN_LIST, LB_SETSEL,
147                                            FALSE, i);
148                 }
149             }
150             return 0;
151         }
152         return 0;
153       case WM_CLOSE:
154         logbox = NULL;
155         SetActiveWindow(GetParent(hwnd));
156         DestroyWindow(hwnd);
157         return 0;
158     }
159     return 0;
160 }
161
162 static int CALLBACK LicenceProc(HWND hwnd, UINT msg,
163                                 WPARAM wParam, LPARAM lParam)
164 {
165     switch (msg) {
166       case WM_INITDIALOG:
167         {
168             char *str = dupprintf("%s Licence", appname);
169             SetWindowText(hwnd, str);
170             sfree(str);
171         }
172         return 1;
173       case WM_COMMAND:
174         switch (LOWORD(wParam)) {
175           case IDOK:
176           case IDCANCEL:
177             EndDialog(hwnd, 1);
178             return 0;
179         }
180         return 0;
181       case WM_CLOSE:
182         EndDialog(hwnd, 1);
183         return 0;
184     }
185     return 0;
186 }
187
188 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
189                               WPARAM wParam, LPARAM lParam)
190 {
191     char *str;
192
193     switch (msg) {
194       case WM_INITDIALOG:
195         str = dupprintf("About %s", appname);
196         SetWindowText(hwnd, str);
197         sfree(str);
198         SetDlgItemText(hwnd, IDA_TEXT1, appname);
199         SetDlgItemText(hwnd, IDA_VERSION, ver);
200         return 1;
201       case WM_COMMAND:
202         switch (LOWORD(wParam)) {
203           case IDOK:
204           case IDCANCEL:
205             EndDialog(hwnd, TRUE);
206             return 0;
207           case IDA_LICENCE:
208             EnableWindow(hwnd, 0);
209             DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
210                       hwnd, LicenceProc);
211             EnableWindow(hwnd, 1);
212             SetActiveWindow(hwnd);
213             return 0;
214
215           case IDA_WEB:
216             /* Load web browser */
217             ShellExecute(hwnd, "open",
218                          "http://www.chiark.greenend.org.uk/~sgtatham/putty/",
219                          0, 0, SW_SHOWDEFAULT);
220             return 0;
221         }
222         return 0;
223       case WM_CLOSE:
224         EndDialog(hwnd, TRUE);
225         return 0;
226     }
227     return 0;
228 }
229
230 static int SaneDialogBox(HINSTANCE hinst,
231                          LPCTSTR tmpl,
232                          HWND hwndparent,
233                          DLGPROC lpDialogFunc)
234 {
235     WNDCLASS wc;
236     HWND hwnd;
237     MSG msg;
238     int flags;
239     int ret;
240     int gm;
241
242     wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
243     wc.lpfnWndProc = DefDlgProc;
244     wc.cbClsExtra = 0;
245     wc.cbWndExtra = DLGWINDOWEXTRA + 8;
246     wc.hInstance = hinst;
247     wc.hIcon = NULL;
248     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
249     wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
250     wc.lpszMenuName = NULL;
251     wc.lpszClassName = "PuTTYConfigBox";
252     RegisterClass(&wc);
253
254     hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc);
255
256     SetWindowLong(hwnd, BOXFLAGS, 0); /* flags */
257     SetWindowLong(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
258
259     while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
260         flags=GetWindowLong(hwnd, BOXFLAGS);
261         if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg))
262             DispatchMessage(&msg);
263         if (flags & DF_END)
264             break;
265     }
266
267     if (gm == 0)
268         PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */
269
270     ret=GetWindowLong(hwnd, BOXRESULT);
271     DestroyWindow(hwnd);
272     return ret;
273 }
274
275 static void SaneEndDialog(HWND hwnd, int ret)
276 {
277     SetWindowLong(hwnd, BOXRESULT, ret);
278     SetWindowLong(hwnd, BOXFLAGS, DF_END);
279 }
280
281 /*
282  * Null dialog procedure.
283  */
284 static int CALLBACK NullDlgProc(HWND hwnd, UINT msg,
285                                 WPARAM wParam, LPARAM lParam)
286 {
287     return 0;
288 }
289
290 enum {
291     IDCX_ABOUT = IDC_ABOUT,
292     IDCX_TVSTATIC,
293     IDCX_TREEVIEW,
294     IDCX_STDBASE,
295     IDCX_PANELBASE = IDCX_STDBASE + 32
296 };
297
298 struct treeview_faff {
299     HWND treeview;
300     HTREEITEM lastat[4];
301 };
302
303 static HTREEITEM treeview_insert(struct treeview_faff *faff,
304                                  int level, char *text, char *path)
305 {
306     TVINSERTSTRUCT ins;
307     int i;
308     HTREEITEM newitem;
309     ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT);
310     ins.hInsertAfter = faff->lastat[level];
311 #if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION
312 #define INSITEM DUMMYUNIONNAME.item
313 #else
314 #define INSITEM item
315 #endif
316     ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
317     ins.INSITEM.pszText = text;
318     ins.INSITEM.cchTextMax = strlen(text)+1;
319     ins.INSITEM.lParam = (LPARAM)path;
320     newitem = TreeView_InsertItem(faff->treeview, &ins);
321     if (level > 0)
322         TreeView_Expand(faff->treeview, faff->lastat[level - 1],
323                         TVE_EXPAND);
324     faff->lastat[level] = newitem;
325     for (i = level + 1; i < 4; i++)
326         faff->lastat[i] = NULL;
327     return newitem;
328 }
329
330 /*
331  * Create the panelfuls of controls in the configuration box.
332  */
333 static void create_controls(HWND hwnd, char *path)
334 {
335     struct ctlpos cp;
336     int index;
337     int base_id;
338     struct winctrls *wc;
339
340     if (!path[0]) {
341         /*
342          * Here we must create the basic standard controls.
343          */
344         ctlposinit(&cp, hwnd, 3, 3, 235);
345         wc = &ctrls_base;
346         base_id = IDCX_STDBASE;
347     } else {
348         /*
349          * Otherwise, we're creating the controls for a particular
350          * panel.
351          */
352         ctlposinit(&cp, hwnd, 100, 3, 13);
353         wc = &ctrls_panel;
354         base_id = IDCX_PANELBASE;
355     }
356
357     for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) {
358         struct controlset *s = ctrlbox->ctrlsets[index];
359         winctrl_layout(&dp, wc, &cp, s, &base_id);
360     }
361 }
362
363 /*
364  * This function is the configuration box.
365  * (Being a dialog procedure, in general it returns 0 if the default
366  * dialog processing should be performed, and 1 if it should not.)
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 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
756                         char *keystr, char *fingerprint,
757                         void (*callback)(void *ctx, int result), void *ctx)
758 {
759     int ret;
760
761     static const char absentmsg[] =
762         "The server's host key is not cached in the registry. You\n"
763         "have no guarantee that the server is the computer you\n"
764         "think it is.\n"
765         "The server's %s key fingerprint is:\n"
766         "%s\n"
767         "If you trust this host, hit Yes to add the key to\n"
768         "%s's cache and carry on connecting.\n"
769         "If you want to carry on connecting just once, without\n"
770         "adding the key to the cache, hit No.\n"
771         "If you do not trust this host, hit Cancel to abandon the\n"
772         "connection.\n";
773
774     static const char wrongmsg[] =
775         "WARNING - POTENTIAL SECURITY BREACH!\n"
776         "\n"
777         "The server's host key does not match the one %s has\n"
778         "cached in the registry. This means that either the\n"
779         "server administrator has changed the host key, or you\n"
780         "have actually connected to another computer pretending\n"
781         "to be the server.\n"
782         "The new %s key fingerprint is:\n"
783         "%s\n"
784         "If you were expecting this change and trust the new key,\n"
785         "hit Yes to update %s's cache and continue connecting.\n"
786         "If you want to carry on connecting but without updating\n"
787         "the cache, hit No.\n"
788         "If you want to abandon the connection completely, hit\n"
789         "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
790
791     static const char mbtitle[] = "%s Security Alert";
792
793     /*
794      * Verify the key against the registry.
795      */
796     ret = verify_host_key(host, port, keytype, keystr);
797
798     if (ret == 0)                      /* success - key matched OK */
799         return 1;
800     if (ret == 2) {                    /* key was different */
801         int mbret;
802         char *text = dupprintf(wrongmsg, appname, keytype, fingerprint,
803                                appname);
804         char *caption = dupprintf(mbtitle, appname);
805         mbret = message_box(text, caption,
806                             MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
807                             HELPCTXID(errors_hostkey_changed));
808         assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
809         sfree(text);
810         sfree(caption);
811         if (mbret == IDYES) {
812             store_host_key(host, port, keytype, keystr);
813             return 1;
814         } else if (mbret == IDNO)
815             return 1;
816         return 0;
817     }
818     if (ret == 1) {                    /* key was absent */
819         int mbret;
820         char *text = dupprintf(absentmsg, keytype, fingerprint, appname);
821         char *caption = dupprintf(mbtitle, appname);
822         mbret = message_box(text, caption,
823                             MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
824                             HELPCTXID(errors_hostkey_absent));
825         assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
826         sfree(text);
827         sfree(caption);
828         if (mbret == IDYES) {
829             store_host_key(host, port, keytype, keystr);
830             return 1;
831         } else if (mbret == IDNO)
832             return 1;
833         return 0;
834     }
835 }
836
837 /*
838  * Ask whether the selected algorithm is acceptable (since it was
839  * below the configured 'warn' threshold).
840  */
841 int askalg(void *frontend, const char *algtype, const char *algname,
842            void (*callback)(void *ctx, int result), void *ctx)
843 {
844     static const char mbtitle[] = "%s Security Alert";
845     static const char msg[] =
846         "The first %s supported by the server\n"
847         "is %.64s, which is below the configured\n"
848         "warning threshold.\n"
849         "Do you want to continue with this connection?\n";
850     char *message, *title;
851     int mbret;
852
853     message = dupprintf(msg, algtype, algname);
854     title = dupprintf(mbtitle, appname);
855     mbret = MessageBox(NULL, message, title,
856                        MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
857     sfree(message);
858     sfree(title);
859     if (mbret == IDYES)
860         return 1;
861     else
862         return 0;
863 }
864
865 /*
866  * Ask whether to wipe a session log file before writing to it.
867  * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
868  */
869 int askappend(void *frontend, Filename filename,
870               void (*callback)(void *ctx, int result), void *ctx)
871 {
872     static const char msgtemplate[] =
873         "The session log file \"%.*s\" already exists.\n"
874         "You can overwrite it with a new session log,\n"
875         "append your session log to the end of it,\n"
876         "or disable session logging for this session.\n"
877         "Hit Yes to wipe the file, No to append to it,\n"
878         "or Cancel to disable logging.";
879     char *message;
880     char *mbtitle;
881     int mbret;
882
883     message = dupprintf(msgtemplate, FILENAME_MAX, filename.path);
884     mbtitle = dupprintf("%s Log to File", appname);
885
886     mbret = MessageBox(NULL, message, mbtitle,
887                        MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
888
889     sfree(message);
890     sfree(mbtitle);
891
892     if (mbret == IDYES)
893         return 2;
894     else if (mbret == IDNO)
895         return 1;
896     else
897         return 0;
898 }
899
900 /*
901  * Warn about the obsolescent key file format.
902  * 
903  * Uniquely among these functions, this one does _not_ expect a
904  * frontend handle. This means that if PuTTY is ported to a
905  * platform which requires frontend handles, this function will be
906  * an anomaly. Fortunately, the problem it addresses will not have
907  * been present on that platform, so it can plausibly be
908  * implemented as an empty function.
909  */
910 void old_keyfile_warning(void)
911 {
912     static const char mbtitle[] = "%s Key File Warning";
913     static const char message[] =
914         "You are loading an SSH-2 private key which has an\n"
915         "old version of the file format. This means your key\n"
916         "file is not fully tamperproof. Future versions of\n"
917         "%s may stop supporting this private key format,\n"
918         "so we recommend you convert your key to the new\n"
919         "format.\n"
920         "\n"
921         "You can perform this conversion by loading the key\n"
922         "into PuTTYgen and then saving it again.";
923
924     char *msg, *title;
925     msg = dupprintf(message, appname);
926     title = dupprintf(mbtitle, appname);
927
928     MessageBox(NULL, msg, title, MB_OK);
929
930     sfree(msg);
931     sfree(title);
932 }