]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/windlg.c
Merge tag '0.65'
[PuTTY.git] / windows / windlg.c
1 /*
2  * windlg.c - dialogs for PuTTY(tel), including the configuration dialog.
3  */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <limits.h>
8 #include <assert.h>
9 #include <ctype.h>
10 #include <time.h>
11
12 #include "putty.h"
13 #include "ssh.h"
14 #include "win_res.h"
15 #include "storage.h"
16 #include "dialog.h"
17
18 #include <commctrl.h>
19 #include <commdlg.h>
20 #include <shellapi.h>
21
22 #ifdef MSVC4
23 #define TVINSERTSTRUCT  TV_INSERTSTRUCT
24 #define TVITEM          TV_ITEM
25 #define ICON_BIG        1
26 #endif
27
28 /*
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.
34  */
35 static struct controlbox *ctrlbox;
36 /*
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.
40  */
41 static struct winctrls ctrls_base, ctrls_panel;
42 static struct dlgparam dp;
43
44 static char **events = NULL;
45 static int nevents = 0, negsize = 0;
46
47 extern Conf *conf;                     /* defined in 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 + 2*sizeof(LONG_PTR);
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     SetWindowLongPtr(hwnd, BOXFLAGS, 0); /* flags */
259     SetWindowLongPtr(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
260
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);
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=GetWindowLongPtr(hwnd, BOXRESULT);
273     DestroyWindow(hwnd);
274     return ret;
275 }
276
277 static void SaneEndDialog(HWND hwnd, int ret)
278 {
279     SetWindowLongPtr(hwnd, BOXRESULT, ret);
280     SetWindowLongPtr(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                         (level > 1 ? TVE_COLLAPSE : 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  * (Being a dialog procedure, in general it returns 0 if the default
368  * dialog processing should be performed, and 1 if it should not.)
369  */
370 static int CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
371                                        WPARAM wParam, LPARAM lParam)
372 {
373     HWND hw, treeview;
374     struct treeview_faff tvfaff;
375     int ret;
376
377     switch (msg) {
378       case WM_INITDIALOG:
379         dp.hwnd = hwnd;
380         create_controls(hwnd, "");     /* Open and Cancel buttons etc */
381         SetWindowText(hwnd, dp.wintitle);
382         SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
383         if (has_help())
384             SetWindowLongPtr(hwnd, GWL_EXSTYLE,
385                              GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
386                              WS_EX_CONTEXTHELP);
387         else {
388             HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
389             if (item)
390                 DestroyWindow(item);
391         }
392         SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
393                     (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
394         /*
395          * Centre the window.
396          */
397         {                              /* centre the window */
398             RECT rs, rd;
399
400             hw = GetDesktopWindow();
401             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
402                 MoveWindow(hwnd,
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);
406         }
407
408         /*
409          * Create the tree view.
410          */
411         {
412             RECT r;
413             WPARAM font;
414             HWND tvstatic;
415
416             r.left = 3;
417             r.right = r.left + 95;
418             r.top = 3;
419             r.bottom = r.top + 10;
420             MapDialogRect(hwnd, &r);
421             tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
422                                       WS_CHILD | WS_VISIBLE,
423                                       r.left, r.top,
424                                       r.right - r.left, r.bottom - r.top,
425                                       hwnd, (HMENU) IDCX_TVSTATIC, hinst,
426                                       NULL);
427             font = SendMessage(hwnd, WM_GETFONT, 0, 0);
428             SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
429
430             r.left = 3;
431             r.right = r.left + 95;
432             r.top = 13;
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
439                                       | TVS_LINESATROOT |
440                                       TVS_SHOWSELALWAYS, r.left, r.top,
441                                       r.right - r.left, r.bottom - r.top,
442                                       hwnd, (HMENU) IDCX_TREEVIEW, hinst,
443                                       NULL);
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));
448         }
449
450         /*
451          * Set up the tree view contents.
452          */
453         {
454             HTREEITEM hfirst = NULL;
455             int i;
456             char *path = NULL;
457             char *firstpath = NULL;
458
459             for (i = 0; i < ctrlbox->nctrlsets; i++) {
460                 struct controlset *s = ctrlbox->ctrlsets[i];
461                 HTREEITEM item;
462                 int j;
463                 char *c;
464
465                 if (!s->pathname[0])
466                     continue;
467                 j = path ? ctrl_path_compare(s->pathname, path) : 0;
468                 if (j == INT_MAX)
469                     continue;          /* same path, nothing to add to tree */
470
471                 /*
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
479                  * A/D/E.
480                  */
481                 assert(j == ctrl_path_elements(s->pathname) - 1);
482
483                 c = strrchr(s->pathname, '/');
484                 if (!c)
485                         c = s->pathname;
486                 else
487                         c++;
488
489                 item = treeview_insert(&tvfaff, j, c, s->pathname);
490                 if (!hfirst) {
491                     hfirst = item;
492                     firstpath = s->pathname;
493                 }
494
495                 path = s->pathname;
496             }
497
498             /*
499              * Put the treeview selection on to the first panel in the
500              * ctrlbox.
501              */
502             TreeView_SelectItem(treeview, hfirst);
503
504             /*
505              * And create the actual control set for that panel, to
506              * match the initial treeview selection.
507              */
508             create_controls(hwnd, firstpath);
509             dlg_refresh(NULL, &dp);    /* and set up control values */
510         }
511
512         /*
513          * Set focus into the first available control.
514          */
515         {
516             int i;
517             struct winctrl *c;
518
519             for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
520                  i++) {
521                 if (c->ctrl) {
522                     dlg_set_focus(c->ctrl, &dp);
523                     break;
524                 }
525             }
526         }
527
528         /*
529          * Now we've finished creating our initial set of controls,
530          * it's safe to actually show the window without risking setup
531          * flicker.
532          */
533         ShowWindow(hwnd, SW_SHOWNORMAL);
534
535         /*
536          * Set the flag that activates a couple of the other message
537          * handlers below, which were disabled until now to avoid
538          * spurious firing during the above setup procedure.
539          */
540         SetWindowLongPtr(hwnd, GWLP_USERDATA, 1);
541         return 0;
542       case WM_LBUTTONUP:
543         /*
544          * Button release should trigger WM_OK if there was a
545          * previous double click on the session list.
546          */
547         ReleaseCapture();
548         if (dp.ended)
549             SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
550         break;
551       case WM_NOTIFY:
552         if (LOWORD(wParam) == IDCX_TREEVIEW &&
553             ((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
554             /*
555              * Selection-change events on the treeview cause us to do
556              * a flurry of control deletion and creation - but only
557              * after WM_INITDIALOG has finished. The initial
558              * selection-change event(s) during treeview setup are
559              * ignored.
560              */
561             HTREEITEM i;
562             TVITEM item;
563             char buffer[64];
564
565             if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1)
566                 return 0;
567
568             i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
569  
570             SendMessage (hwnd, WM_SETREDRAW, FALSE, 0);
571  
572             item.hItem = i;
573             item.pszText = buffer;
574             item.cchTextMax = sizeof(buffer);
575             item.mask = TVIF_TEXT | TVIF_PARAM;
576             TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
577             {
578                 /* Destroy all controls in the currently visible panel. */
579                 int k;
580                 HWND item;
581                 struct winctrl *c;
582
583                 while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
584                     for (k = 0; k < c->num_ids; k++) {
585                         item = GetDlgItem(hwnd, c->base_id + k);
586                         if (item)
587                             DestroyWindow(item);
588                     }
589                     winctrl_rem_shortcuts(&dp, c);
590                     winctrl_remove(&ctrls_panel, c);
591                     sfree(c->data);
592                     sfree(c);
593                 }
594             }
595             create_controls(hwnd, (char *)item.lParam);
596
597             dlg_refresh(NULL, &dp);    /* set up control values */
598  
599             SendMessage (hwnd, WM_SETREDRAW, TRUE, 0);
600             InvalidateRect (hwnd, NULL, TRUE);
601
602             SetFocus(((LPNMHDR) lParam)->hwndFrom);     /* ensure focus stays */
603             return 0;
604         }
605         break;
606       case WM_COMMAND:
607       case WM_DRAWITEM:
608       default:                         /* also handle drag list msg here */
609         /*
610          * Only process WM_COMMAND once the dialog is fully formed.
611          */
612         if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) {
613             ret = winctrl_handle_command(&dp, msg, wParam, lParam);
614             if (dp.ended && GetCapture() != hwnd)
615                 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
616         } else
617             ret = 0;
618         return ret;
619       case WM_HELP:
620         if (!winctrl_context_help(&dp, hwnd,
621                                  ((LPHELPINFO)lParam)->iCtrlId))
622             MessageBeep(0);
623         break;
624       case WM_CLOSE:
625         quit_help(hwnd);
626         SaneEndDialog(hwnd, 0);
627         return 0;
628
629         /* Grrr Explorer will maximize Dialogs! */
630       case WM_SIZE:
631         if (wParam == SIZE_MAXIMIZED)
632             force_normal(hwnd);
633         return 0;
634
635     }
636     return 0;
637 }
638
639 void modal_about_box(HWND hwnd)
640 {
641     EnableWindow(hwnd, 0);
642     DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
643     EnableWindow(hwnd, 1);
644     SetActiveWindow(hwnd);
645 }
646
647 void show_help(HWND hwnd)
648 {
649     launch_help(hwnd, NULL);
650 }
651
652 void defuse_showwindow(void)
653 {
654     /*
655      * Work around the fact that the app's first call to ShowWindow
656      * will ignore the default in favour of the shell-provided
657      * setting.
658      */
659     {
660         HWND hwnd;
661         hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
662                             NULL, NullDlgProc);
663         ShowWindow(hwnd, SW_HIDE);
664         SetActiveWindow(hwnd);
665         DestroyWindow(hwnd);
666     }
667 }
668
669 int do_config(void)
670 {
671     int ret;
672
673     ctrlbox = ctrl_new_box();
674     setup_config_box(ctrlbox, FALSE, 0, 0);
675     win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), FALSE, 0);
676     dp_init(&dp);
677     winctrl_init(&ctrls_base);
678     winctrl_init(&ctrls_panel);
679     dp_add_tree(&dp, &ctrls_base);
680     dp_add_tree(&dp, &ctrls_panel);
681     dp.wintitle = dupprintf("%s Configuration", appname);
682     dp.errtitle = dupprintf("%s Error", appname);
683     dp.data = conf;
684     dlg_auto_set_fixed_pitch_flag(&dp);
685     dp.shortcuts['g'] = TRUE;          /* the treeview: `Cate&gory' */
686
687     ret =
688         SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
689                   GenericMainDlgProc);
690
691     ctrl_free_box(ctrlbox);
692     winctrl_cleanup(&ctrls_panel);
693     winctrl_cleanup(&ctrls_base);
694     dp_cleanup(&dp);
695
696     return ret;
697 }
698
699 int do_reconfig(HWND hwnd, int protcfginfo)
700 {
701     Conf *backup_conf;
702     int ret, protocol;
703
704     backup_conf = conf_copy(conf);
705
706     ctrlbox = ctrl_new_box();
707     protocol = conf_get_int(conf, CONF_protocol);
708     setup_config_box(ctrlbox, TRUE, protocol, protcfginfo);
709     win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), TRUE, protocol);
710     dp_init(&dp);
711     winctrl_init(&ctrls_base);
712     winctrl_init(&ctrls_panel);
713     dp_add_tree(&dp, &ctrls_base);
714     dp_add_tree(&dp, &ctrls_panel);
715     dp.wintitle = dupprintf("%s Reconfiguration", appname);
716     dp.errtitle = dupprintf("%s Error", appname);
717     dp.data = conf;
718     dlg_auto_set_fixed_pitch_flag(&dp);
719     dp.shortcuts['g'] = TRUE;          /* the treeview: `Cate&gory' */
720
721     ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
722                   GenericMainDlgProc);
723
724     ctrl_free_box(ctrlbox);
725     winctrl_cleanup(&ctrls_base);
726     winctrl_cleanup(&ctrls_panel);
727     dp_cleanup(&dp);
728
729     if (!ret)
730         conf_copy_into(conf, backup_conf);
731
732     conf_free(backup_conf);
733
734     return ret;
735 }
736
737 void logevent(void *frontend, const char *string)
738 {
739     char timebuf[40];
740     struct tm tm;
741
742     log_eventlog(logctx, string);
743
744     if (nevents >= negsize) {
745         negsize += 64;
746         events = sresize(events, negsize, char *);
747     }
748
749     tm=ltime();
750     strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
751
752     events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
753     strcpy(events[nevents], timebuf);
754     strcat(events[nevents], string);
755     if (logbox) {
756         int count;
757         SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
758                            0, (LPARAM) events[nevents]);
759         count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
760         SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
761     }
762     nevents++;
763 }
764
765 void showeventlog(HWND hwnd)
766 {
767     if (!logbox) {
768         logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
769                               hwnd, LogProc);
770         ShowWindow(logbox, SW_SHOWNORMAL);
771     }
772     SetActiveWindow(logbox);
773 }
774
775 void showabout(HWND hwnd)
776 {
777     DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
778 }
779
780 int verify_ssh_host_key(void *frontend, char *host, int port,
781                         const char *keytype, char *keystr, char *fingerprint,
782                         void (*callback)(void *ctx, int result), void *ctx)
783 {
784     int ret;
785
786     static const char absentmsg[] =
787         "The server's host key is not cached in the registry. You\n"
788         "have no guarantee that the server is the computer you\n"
789         "think it is.\n"
790         "The server's %s key fingerprint is:\n"
791         "%s\n"
792         "If you trust this host, hit Yes to add the key to\n"
793         "%s's cache and carry on connecting.\n"
794         "If you want to carry on connecting just once, without\n"
795         "adding the key to the cache, hit No.\n"
796         "If you do not trust this host, hit Cancel to abandon the\n"
797         "connection.\n";
798
799     static const char wrongmsg[] =
800         "WARNING - POTENTIAL SECURITY BREACH!\n"
801         "\n"
802         "The server's host key does not match the one %s has\n"
803         "cached in the registry. This means that either the\n"
804         "server administrator has changed the host key, or you\n"
805         "have actually connected to another computer pretending\n"
806         "to be the server.\n"
807         "The new %s key fingerprint is:\n"
808         "%s\n"
809         "If you were expecting this change and trust the new key,\n"
810         "hit Yes to update %s's cache and continue connecting.\n"
811         "If you want to carry on connecting but without updating\n"
812         "the cache, hit No.\n"
813         "If you want to abandon the connection completely, hit\n"
814         "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
815
816     static const char mbtitle[] = "%s Security Alert";
817
818     /*
819      * Verify the key against the registry.
820      */
821     ret = verify_host_key(host, port, keytype, keystr);
822
823     if (ret == 0)                      /* success - key matched OK */
824         return 1;
825     else if (ret == 2) {               /* key was different */
826         int mbret;
827         char *text = dupprintf(wrongmsg, appname, keytype, fingerprint,
828                                appname);
829         char *caption = dupprintf(mbtitle, appname);
830         mbret = message_box(text, caption,
831                             MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
832                             HELPCTXID(errors_hostkey_changed));
833         assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
834         sfree(text);
835         sfree(caption);
836         if (mbret == IDYES) {
837             store_host_key(host, port, keytype, keystr);
838             return 1;
839         } else if (mbret == IDNO)
840             return 1;
841     } else if (ret == 1) {             /* key was absent */
842         int mbret;
843         char *text = dupprintf(absentmsg, keytype, fingerprint, appname);
844         char *caption = dupprintf(mbtitle, appname);
845         mbret = message_box(text, caption,
846                             MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
847                             HELPCTXID(errors_hostkey_absent));
848         assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
849         sfree(text);
850         sfree(caption);
851         if (mbret == IDYES) {
852             store_host_key(host, port, keytype, keystr);
853             return 1;
854         } else if (mbret == IDNO)
855             return 1;
856     }
857     return 0;   /* abandon the connection */
858 }
859
860 /*
861  * Ask whether the selected algorithm is acceptable (since it was
862  * below the configured 'warn' threshold).
863  */
864 int askalg(void *frontend, const char *algtype, const char *algname,
865            void (*callback)(void *ctx, int result), void *ctx)
866 {
867     static const char mbtitle[] = "%s Security Alert";
868     static const char msg[] =
869         "The first %s supported by the server\n"
870         "is %.64s, which is below the configured\n"
871         "warning threshold.\n"
872         "Do you want to continue with this connection?\n";
873     char *message, *title;
874     int mbret;
875
876     message = dupprintf(msg, algtype, algname);
877     title = dupprintf(mbtitle, appname);
878     mbret = MessageBox(NULL, message, title,
879                        MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
880     socket_reselect_all();
881     sfree(message);
882     sfree(title);
883     if (mbret == IDYES)
884         return 1;
885     else
886         return 0;
887 }
888
889 /*
890  * Ask whether to wipe a session log file before writing to it.
891  * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
892  */
893 int askappend(void *frontend, Filename *filename,
894               void (*callback)(void *ctx, int result), void *ctx)
895 {
896     static const char msgtemplate[] =
897         "The session log file \"%.*s\" already exists.\n"
898         "You can overwrite it with a new session log,\n"
899         "append your session log to the end of it,\n"
900         "or disable session logging for this session.\n"
901         "Hit Yes to wipe the file, No to append to it,\n"
902         "or Cancel to disable logging.";
903     char *message;
904     char *mbtitle;
905     int mbret;
906
907     message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
908     mbtitle = dupprintf("%s Log to File", appname);
909
910     mbret = MessageBox(NULL, message, mbtitle,
911                        MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
912
913     socket_reselect_all();
914
915     sfree(message);
916     sfree(mbtitle);
917
918     if (mbret == IDYES)
919         return 2;
920     else if (mbret == IDNO)
921         return 1;
922     else
923         return 0;
924 }
925
926 /*
927  * Warn about the obsolescent key file format.
928  * 
929  * Uniquely among these functions, this one does _not_ expect a
930  * frontend handle. This means that if PuTTY is ported to a
931  * platform which requires frontend handles, this function will be
932  * an anomaly. Fortunately, the problem it addresses will not have
933  * been present on that platform, so it can plausibly be
934  * implemented as an empty function.
935  */
936 void old_keyfile_warning(void)
937 {
938     static const char mbtitle[] = "%s Key File Warning";
939     static const char message[] =
940         "You are loading an SSH-2 private key which has an\n"
941         "old version of the file format. This means your key\n"
942         "file is not fully tamperproof. Future versions of\n"
943         "%s may stop supporting this private key format,\n"
944         "so we recommend you convert your key to the new\n"
945         "format.\n"
946         "\n"
947         "You can perform this conversion by loading the key\n"
948         "into PuTTYgen and then saving it again.";
949
950     char *msg, *title;
951     msg = dupprintf(message, appname);
952     title = dupprintf(mbtitle, appname);
953
954     MessageBox(NULL, msg, title, MB_OK);
955
956     socket_reselect_all();
957
958     sfree(msg);
959     sfree(title);
960 }