]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - windows/windlg.c
Use readonly edit controls in some Windows dialogs.
[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             SetDlgItemText(hwnd, IDA_TEXT,
175         "Copyright 1997-2015 Simon Tatham.\r\n\r\n"
176
177         "Portions copyright Robert de Bath, Joris van Rantwijk, Delian "
178         "Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas "
179         "Barry, Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, "
180         "Markus Kuhn, Colin Watson, Christopher Staite, and CORE SDI S.A.\r\n\r\n"
181
182         "Permission is hereby granted, free of charge, to any person "
183         "obtaining a copy of this software and associated documentation "
184         "files (the ""Software""), to deal in the Software without restriction, "
185         "including without limitation the rights to use, copy, modify, merge, "
186         "publish, distribute, sublicense, and/or sell copies of the Software, "
187         "and to permit persons to whom the Software is furnished to do so, "
188         "subject to the following conditions:\r\n\r\n"
189
190         "The above copyright notice and this permission notice shall be "
191         "included in all copies or substantial portions of the Software.\r\n\r\n"
192
193         "THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT "
194         "WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, "
195         "INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF "
196         "MERCHANTABILITY, FITNESS FOR A PARTICULAR "
197         "PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE "
198         "COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES "
199         "OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, "
200         "TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN "
201         "CONNECTION WITH THE SOFTWARE OR THE USE OR "
202         "OTHER DEALINGS IN THE SOFTWARE."
203 );
204         }
205         return 1;
206       case WM_COMMAND:
207         switch (LOWORD(wParam)) {
208           case IDOK:
209           case IDCANCEL:
210             EndDialog(hwnd, 1);
211             return 0;
212         }
213         return 0;
214       case WM_CLOSE:
215         EndDialog(hwnd, 1);
216         return 0;
217     }
218     return 0;
219 }
220
221 static int CALLBACK AboutProc(HWND hwnd, UINT msg,
222                               WPARAM wParam, LPARAM lParam)
223 {
224     char *str;
225
226     switch (msg) {
227       case WM_INITDIALOG:
228         str = dupprintf("About %s", appname);
229         SetWindowText(hwnd, str);
230         sfree(str);
231         {
232             char *text = dupprintf
233                 ("%s\r\n\r\n%s\r\n\r\n%s",
234                  appname, ver,
235                  "\251 1997-2015 Simon Tatham. All rights reserved.");
236             SetDlgItemText(hwnd, IDA_TEXT, text);
237             sfree(text);
238         }
239         return 1;
240       case WM_COMMAND:
241         switch (LOWORD(wParam)) {
242           case IDOK:
243           case IDCANCEL:
244             EndDialog(hwnd, TRUE);
245             return 0;
246           case IDA_LICENCE:
247             EnableWindow(hwnd, 0);
248             DialogBox(hinst, MAKEINTRESOURCE(IDD_LICENCEBOX),
249                       hwnd, LicenceProc);
250             EnableWindow(hwnd, 1);
251             SetActiveWindow(hwnd);
252             return 0;
253
254           case IDA_WEB:
255             /* Load web browser */
256             ShellExecute(hwnd, "open",
257                          "http://www.chiark.greenend.org.uk/~sgtatham/putty/",
258                          0, 0, SW_SHOWDEFAULT);
259             return 0;
260         }
261         return 0;
262       case WM_CLOSE:
263         EndDialog(hwnd, TRUE);
264         return 0;
265     }
266     return 0;
267 }
268
269 static int SaneDialogBox(HINSTANCE hinst,
270                          LPCTSTR tmpl,
271                          HWND hwndparent,
272                          DLGPROC lpDialogFunc)
273 {
274     WNDCLASS wc;
275     HWND hwnd;
276     MSG msg;
277     int flags;
278     int ret;
279     int gm;
280
281     wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
282     wc.lpfnWndProc = DefDlgProc;
283     wc.cbClsExtra = 0;
284     wc.cbWndExtra = DLGWINDOWEXTRA + 2*sizeof(LONG_PTR);
285     wc.hInstance = hinst;
286     wc.hIcon = NULL;
287     wc.hCursor = LoadCursor(NULL, IDC_ARROW);
288     wc.hbrBackground = (HBRUSH) (COLOR_BACKGROUND +1);
289     wc.lpszMenuName = NULL;
290     wc.lpszClassName = "PuTTYConfigBox";
291     RegisterClass(&wc);
292
293     hwnd = CreateDialog(hinst, tmpl, hwndparent, lpDialogFunc);
294
295     SetWindowLongPtr(hwnd, BOXFLAGS, 0); /* flags */
296     SetWindowLongPtr(hwnd, BOXRESULT, 0); /* result from SaneEndDialog */
297
298     while ((gm=GetMessage(&msg, NULL, 0, 0)) > 0) {
299         flags=GetWindowLongPtr(hwnd, BOXFLAGS);
300         if (!(flags & DF_END) && !IsDialogMessage(hwnd, &msg))
301             DispatchMessage(&msg);
302         if (flags & DF_END)
303             break;
304     }
305
306     if (gm == 0)
307         PostQuitMessage(msg.wParam); /* We got a WM_QUIT, pass it on */
308
309     ret=GetWindowLongPtr(hwnd, BOXRESULT);
310     DestroyWindow(hwnd);
311     return ret;
312 }
313
314 static void SaneEndDialog(HWND hwnd, int ret)
315 {
316     SetWindowLongPtr(hwnd, BOXRESULT, ret);
317     SetWindowLongPtr(hwnd, BOXFLAGS, DF_END);
318 }
319
320 /*
321  * Null dialog procedure.
322  */
323 static int CALLBACK NullDlgProc(HWND hwnd, UINT msg,
324                                 WPARAM wParam, LPARAM lParam)
325 {
326     return 0;
327 }
328
329 enum {
330     IDCX_ABOUT = IDC_ABOUT,
331     IDCX_TVSTATIC,
332     IDCX_TREEVIEW,
333     IDCX_STDBASE,
334     IDCX_PANELBASE = IDCX_STDBASE + 32
335 };
336
337 struct treeview_faff {
338     HWND treeview;
339     HTREEITEM lastat[4];
340 };
341
342 static HTREEITEM treeview_insert(struct treeview_faff *faff,
343                                  int level, char *text, char *path)
344 {
345     TVINSERTSTRUCT ins;
346     int i;
347     HTREEITEM newitem;
348     ins.hParent = (level > 0 ? faff->lastat[level - 1] : TVI_ROOT);
349     ins.hInsertAfter = faff->lastat[level];
350 #if _WIN32_IE >= 0x0400 && defined NONAMELESSUNION
351 #define INSITEM DUMMYUNIONNAME.item
352 #else
353 #define INSITEM item
354 #endif
355     ins.INSITEM.mask = TVIF_TEXT | TVIF_PARAM;
356     ins.INSITEM.pszText = text;
357     ins.INSITEM.cchTextMax = strlen(text)+1;
358     ins.INSITEM.lParam = (LPARAM)path;
359     newitem = TreeView_InsertItem(faff->treeview, &ins);
360     if (level > 0)
361         TreeView_Expand(faff->treeview, faff->lastat[level - 1],
362                         (level > 1 ? TVE_COLLAPSE : TVE_EXPAND));
363     faff->lastat[level] = newitem;
364     for (i = level + 1; i < 4; i++)
365         faff->lastat[i] = NULL;
366     return newitem;
367 }
368
369 /*
370  * Create the panelfuls of controls in the configuration box.
371  */
372 static void create_controls(HWND hwnd, char *path)
373 {
374     struct ctlpos cp;
375     int index;
376     int base_id;
377     struct winctrls *wc;
378
379     if (!path[0]) {
380         /*
381          * Here we must create the basic standard controls.
382          */
383         ctlposinit(&cp, hwnd, 3, 3, 235);
384         wc = &ctrls_base;
385         base_id = IDCX_STDBASE;
386     } else {
387         /*
388          * Otherwise, we're creating the controls for a particular
389          * panel.
390          */
391         ctlposinit(&cp, hwnd, 100, 3, 13);
392         wc = &ctrls_panel;
393         base_id = IDCX_PANELBASE;
394     }
395
396     for (index=-1; (index = ctrl_find_path(ctrlbox, path, index)) >= 0 ;) {
397         struct controlset *s = ctrlbox->ctrlsets[index];
398         winctrl_layout(&dp, wc, &cp, s, &base_id);
399     }
400 }
401
402 /*
403  * This function is the configuration box.
404  * (Being a dialog procedure, in general it returns 0 if the default
405  * dialog processing should be performed, and 1 if it should not.)
406  */
407 static int CALLBACK GenericMainDlgProc(HWND hwnd, UINT msg,
408                                        WPARAM wParam, LPARAM lParam)
409 {
410     HWND hw, treeview;
411     struct treeview_faff tvfaff;
412     int ret;
413
414     switch (msg) {
415       case WM_INITDIALOG:
416         dp.hwnd = hwnd;
417         create_controls(hwnd, "");     /* Open and Cancel buttons etc */
418         SetWindowText(hwnd, dp.wintitle);
419         SetWindowLongPtr(hwnd, GWLP_USERDATA, 0);
420         if (has_help())
421             SetWindowLongPtr(hwnd, GWL_EXSTYLE,
422                              GetWindowLongPtr(hwnd, GWL_EXSTYLE) |
423                              WS_EX_CONTEXTHELP);
424         else {
425             HWND item = GetDlgItem(hwnd, IDC_HELPBTN);
426             if (item)
427                 DestroyWindow(item);
428         }
429         SendMessage(hwnd, WM_SETICON, (WPARAM) ICON_BIG,
430                     (LPARAM) LoadIcon(hinst, MAKEINTRESOURCE(IDI_CFGICON)));
431         /*
432          * Centre the window.
433          */
434         {                              /* centre the window */
435             RECT rs, rd;
436
437             hw = GetDesktopWindow();
438             if (GetWindowRect(hw, &rs) && GetWindowRect(hwnd, &rd))
439                 MoveWindow(hwnd,
440                            (rs.right + rs.left + rd.left - rd.right) / 2,
441                            (rs.bottom + rs.top + rd.top - rd.bottom) / 2,
442                            rd.right - rd.left, rd.bottom - rd.top, TRUE);
443         }
444
445         /*
446          * Create the tree view.
447          */
448         {
449             RECT r;
450             WPARAM font;
451             HWND tvstatic;
452
453             r.left = 3;
454             r.right = r.left + 95;
455             r.top = 3;
456             r.bottom = r.top + 10;
457             MapDialogRect(hwnd, &r);
458             tvstatic = CreateWindowEx(0, "STATIC", "Cate&gory:",
459                                       WS_CHILD | WS_VISIBLE,
460                                       r.left, r.top,
461                                       r.right - r.left, r.bottom - r.top,
462                                       hwnd, (HMENU) IDCX_TVSTATIC, hinst,
463                                       NULL);
464             font = SendMessage(hwnd, WM_GETFONT, 0, 0);
465             SendMessage(tvstatic, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
466
467             r.left = 3;
468             r.right = r.left + 95;
469             r.top = 13;
470             r.bottom = r.top + 219;
471             MapDialogRect(hwnd, &r);
472             treeview = CreateWindowEx(WS_EX_CLIENTEDGE, WC_TREEVIEW, "",
473                                       WS_CHILD | WS_VISIBLE |
474                                       WS_TABSTOP | TVS_HASLINES |
475                                       TVS_DISABLEDRAGDROP | TVS_HASBUTTONS
476                                       | TVS_LINESATROOT |
477                                       TVS_SHOWSELALWAYS, r.left, r.top,
478                                       r.right - r.left, r.bottom - r.top,
479                                       hwnd, (HMENU) IDCX_TREEVIEW, hinst,
480                                       NULL);
481             font = SendMessage(hwnd, WM_GETFONT, 0, 0);
482             SendMessage(treeview, WM_SETFONT, font, MAKELPARAM(TRUE, 0));
483             tvfaff.treeview = treeview;
484             memset(tvfaff.lastat, 0, sizeof(tvfaff.lastat));
485         }
486
487         /*
488          * Set up the tree view contents.
489          */
490         {
491             HTREEITEM hfirst = NULL;
492             int i;
493             char *path = NULL;
494             char *firstpath = NULL;
495
496             for (i = 0; i < ctrlbox->nctrlsets; i++) {
497                 struct controlset *s = ctrlbox->ctrlsets[i];
498                 HTREEITEM item;
499                 int j;
500                 char *c;
501
502                 if (!s->pathname[0])
503                     continue;
504                 j = path ? ctrl_path_compare(s->pathname, path) : 0;
505                 if (j == INT_MAX)
506                     continue;          /* same path, nothing to add to tree */
507
508                 /*
509                  * We expect never to find an implicit path
510                  * component. For example, we expect never to see
511                  * A/B/C followed by A/D/E, because that would
512                  * _implicitly_ create A/D. All our path prefixes
513                  * are expected to contain actual controls and be
514                  * selectable in the treeview; so we would expect
515                  * to see A/D _explicitly_ before encountering
516                  * A/D/E.
517                  */
518                 assert(j == ctrl_path_elements(s->pathname) - 1);
519
520                 c = strrchr(s->pathname, '/');
521                 if (!c)
522                         c = s->pathname;
523                 else
524                         c++;
525
526                 item = treeview_insert(&tvfaff, j, c, s->pathname);
527                 if (!hfirst) {
528                     hfirst = item;
529                     firstpath = s->pathname;
530                 }
531
532                 path = s->pathname;
533             }
534
535             /*
536              * Put the treeview selection on to the first panel in the
537              * ctrlbox.
538              */
539             TreeView_SelectItem(treeview, hfirst);
540
541             /*
542              * And create the actual control set for that panel, to
543              * match the initial treeview selection.
544              */
545             assert(firstpath);   /* config.c must have given us _something_ */
546             create_controls(hwnd, firstpath);
547             dlg_refresh(NULL, &dp);    /* and set up control values */
548         }
549
550         /*
551          * Set focus into the first available control.
552          */
553         {
554             int i;
555             struct winctrl *c;
556
557             for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL;
558                  i++) {
559                 if (c->ctrl) {
560                     dlg_set_focus(c->ctrl, &dp);
561                     break;
562                 }
563             }
564         }
565
566         /*
567          * Now we've finished creating our initial set of controls,
568          * it's safe to actually show the window without risking setup
569          * flicker.
570          */
571         ShowWindow(hwnd, SW_SHOWNORMAL);
572
573         /*
574          * Set the flag that activates a couple of the other message
575          * handlers below, which were disabled until now to avoid
576          * spurious firing during the above setup procedure.
577          */
578         SetWindowLongPtr(hwnd, GWLP_USERDATA, 1);
579         return 0;
580       case WM_LBUTTONUP:
581         /*
582          * Button release should trigger WM_OK if there was a
583          * previous double click on the session list.
584          */
585         ReleaseCapture();
586         if (dp.ended)
587             SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
588         break;
589       case WM_NOTIFY:
590         if (LOWORD(wParam) == IDCX_TREEVIEW &&
591             ((LPNMHDR) lParam)->code == TVN_SELCHANGED) {
592             /*
593              * Selection-change events on the treeview cause us to do
594              * a flurry of control deletion and creation - but only
595              * after WM_INITDIALOG has finished. The initial
596              * selection-change event(s) during treeview setup are
597              * ignored.
598              */
599             HTREEITEM i;
600             TVITEM item;
601             char buffer[64];
602
603             if (GetWindowLongPtr(hwnd, GWLP_USERDATA) != 1)
604                 return 0;
605
606             i = TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom);
607  
608             SendMessage (hwnd, WM_SETREDRAW, FALSE, 0);
609  
610             item.hItem = i;
611             item.pszText = buffer;
612             item.cchTextMax = sizeof(buffer);
613             item.mask = TVIF_TEXT | TVIF_PARAM;
614             TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item);
615             {
616                 /* Destroy all controls in the currently visible panel. */
617                 int k;
618                 HWND item;
619                 struct winctrl *c;
620
621                 while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) {
622                     for (k = 0; k < c->num_ids; k++) {
623                         item = GetDlgItem(hwnd, c->base_id + k);
624                         if (item)
625                             DestroyWindow(item);
626                     }
627                     winctrl_rem_shortcuts(&dp, c);
628                     winctrl_remove(&ctrls_panel, c);
629                     sfree(c->data);
630                     sfree(c);
631                 }
632             }
633             create_controls(hwnd, (char *)item.lParam);
634
635             dlg_refresh(NULL, &dp);    /* set up control values */
636  
637             SendMessage (hwnd, WM_SETREDRAW, TRUE, 0);
638             InvalidateRect (hwnd, NULL, TRUE);
639
640             SetFocus(((LPNMHDR) lParam)->hwndFrom);     /* ensure focus stays */
641             return 0;
642         }
643         break;
644       case WM_COMMAND:
645       case WM_DRAWITEM:
646       default:                         /* also handle drag list msg here */
647         /*
648          * Only process WM_COMMAND once the dialog is fully formed.
649          */
650         if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) {
651             ret = winctrl_handle_command(&dp, msg, wParam, lParam);
652             if (dp.ended && GetCapture() != hwnd)
653                 SaneEndDialog(hwnd, dp.endresult ? 1 : 0);
654         } else
655             ret = 0;
656         return ret;
657       case WM_HELP:
658         if (!winctrl_context_help(&dp, hwnd,
659                                  ((LPHELPINFO)lParam)->iCtrlId))
660             MessageBeep(0);
661         break;
662       case WM_CLOSE:
663         quit_help(hwnd);
664         SaneEndDialog(hwnd, 0);
665         return 0;
666
667         /* Grrr Explorer will maximize Dialogs! */
668       case WM_SIZE:
669         if (wParam == SIZE_MAXIMIZED)
670             force_normal(hwnd);
671         return 0;
672
673     }
674     return 0;
675 }
676
677 void modal_about_box(HWND hwnd)
678 {
679     EnableWindow(hwnd, 0);
680     DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
681     EnableWindow(hwnd, 1);
682     SetActiveWindow(hwnd);
683 }
684
685 void show_help(HWND hwnd)
686 {
687     launch_help(hwnd, NULL);
688 }
689
690 void defuse_showwindow(void)
691 {
692     /*
693      * Work around the fact that the app's first call to ShowWindow
694      * will ignore the default in favour of the shell-provided
695      * setting.
696      */
697     {
698         HWND hwnd;
699         hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX),
700                             NULL, NullDlgProc);
701         ShowWindow(hwnd, SW_HIDE);
702         SetActiveWindow(hwnd);
703         DestroyWindow(hwnd);
704     }
705 }
706
707 int do_config(void)
708 {
709     int ret;
710
711     ctrlbox = ctrl_new_box();
712     setup_config_box(ctrlbox, FALSE, 0, 0);
713     win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), FALSE, 0);
714     dp_init(&dp);
715     winctrl_init(&ctrls_base);
716     winctrl_init(&ctrls_panel);
717     dp_add_tree(&dp, &ctrls_base);
718     dp_add_tree(&dp, &ctrls_panel);
719     dp.wintitle = dupprintf("%s Configuration", appname);
720     dp.errtitle = dupprintf("%s Error", appname);
721     dp.data = conf;
722     dlg_auto_set_fixed_pitch_flag(&dp);
723     dp.shortcuts['g'] = TRUE;          /* the treeview: `Cate&gory' */
724
725     ret =
726         SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
727                   GenericMainDlgProc);
728
729     ctrl_free_box(ctrlbox);
730     winctrl_cleanup(&ctrls_panel);
731     winctrl_cleanup(&ctrls_base);
732     dp_cleanup(&dp);
733
734     return ret;
735 }
736
737 int do_reconfig(HWND hwnd, int protcfginfo)
738 {
739     Conf *backup_conf;
740     int ret, protocol;
741
742     backup_conf = conf_copy(conf);
743
744     ctrlbox = ctrl_new_box();
745     protocol = conf_get_int(conf, CONF_protocol);
746     setup_config_box(ctrlbox, TRUE, protocol, protcfginfo);
747     win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), TRUE, protocol);
748     dp_init(&dp);
749     winctrl_init(&ctrls_base);
750     winctrl_init(&ctrls_panel);
751     dp_add_tree(&dp, &ctrls_base);
752     dp_add_tree(&dp, &ctrls_panel);
753     dp.wintitle = dupprintf("%s Reconfiguration", appname);
754     dp.errtitle = dupprintf("%s Error", appname);
755     dp.data = conf;
756     dlg_auto_set_fixed_pitch_flag(&dp);
757     dp.shortcuts['g'] = TRUE;          /* the treeview: `Cate&gory' */
758
759     ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,
760                   GenericMainDlgProc);
761
762     ctrl_free_box(ctrlbox);
763     winctrl_cleanup(&ctrls_base);
764     winctrl_cleanup(&ctrls_panel);
765     dp_cleanup(&dp);
766
767     if (!ret)
768         conf_copy_into(conf, backup_conf);
769
770     conf_free(backup_conf);
771
772     return ret;
773 }
774
775 void logevent(void *frontend, const char *string)
776 {
777     char timebuf[40];
778     struct tm tm;
779
780     log_eventlog(logctx, string);
781
782     if (nevents >= negsize) {
783         negsize += 64;
784         events = sresize(events, negsize, char *);
785     }
786
787     tm=ltime();
788     strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm);
789
790     events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char);
791     strcpy(events[nevents], timebuf);
792     strcat(events[nevents], string);
793     if (logbox) {
794         int count;
795         SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING,
796                            0, (LPARAM) events[nevents]);
797         count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0);
798         SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0);
799     }
800     nevents++;
801 }
802
803 void showeventlog(HWND hwnd)
804 {
805     if (!logbox) {
806         logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX),
807                               hwnd, LogProc);
808         ShowWindow(logbox, SW_SHOWNORMAL);
809     }
810     SetActiveWindow(logbox);
811 }
812
813 void showabout(HWND hwnd)
814 {
815     DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc);
816 }
817
818 int verify_ssh_host_key(void *frontend, char *host, int port,
819                         const char *keytype, char *keystr, char *fingerprint,
820                         void (*callback)(void *ctx, int result), void *ctx)
821 {
822     int ret;
823
824     static const char absentmsg[] =
825         "The server's host key is not cached in the registry. You\n"
826         "have no guarantee that the server is the computer you\n"
827         "think it is.\n"
828         "The server's %s key fingerprint is:\n"
829         "%s\n"
830         "If you trust this host, hit Yes to add the key to\n"
831         "%s's cache and carry on connecting.\n"
832         "If you want to carry on connecting just once, without\n"
833         "adding the key to the cache, hit No.\n"
834         "If you do not trust this host, hit Cancel to abandon the\n"
835         "connection.\n";
836
837     static const char wrongmsg[] =
838         "WARNING - POTENTIAL SECURITY BREACH!\n"
839         "\n"
840         "The server's host key does not match the one %s has\n"
841         "cached in the registry. This means that either the\n"
842         "server administrator has changed the host key, or you\n"
843         "have actually connected to another computer pretending\n"
844         "to be the server.\n"
845         "The new %s key fingerprint is:\n"
846         "%s\n"
847         "If you were expecting this change and trust the new key,\n"
848         "hit Yes to update %s's cache and continue connecting.\n"
849         "If you want to carry on connecting but without updating\n"
850         "the cache, hit No.\n"
851         "If you want to abandon the connection completely, hit\n"
852         "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n";
853
854     static const char mbtitle[] = "%s Security Alert";
855
856     /*
857      * Verify the key against the registry.
858      */
859     ret = verify_host_key(host, port, keytype, keystr);
860
861     if (ret == 0)                      /* success - key matched OK */
862         return 1;
863     else if (ret == 2) {               /* key was different */
864         int mbret;
865         char *text = dupprintf(wrongmsg, appname, keytype, fingerprint,
866                                appname);
867         char *caption = dupprintf(mbtitle, appname);
868         mbret = message_box(text, caption,
869                             MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
870                             HELPCTXID(errors_hostkey_changed));
871         assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
872         sfree(text);
873         sfree(caption);
874         if (mbret == IDYES) {
875             store_host_key(host, port, keytype, keystr);
876             return 1;
877         } else if (mbret == IDNO)
878             return 1;
879     } else if (ret == 1) {             /* key was absent */
880         int mbret;
881         char *text = dupprintf(absentmsg, keytype, fingerprint, appname);
882         char *caption = dupprintf(mbtitle, appname);
883         mbret = message_box(text, caption,
884                             MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3,
885                             HELPCTXID(errors_hostkey_absent));
886         assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL);
887         sfree(text);
888         sfree(caption);
889         if (mbret == IDYES) {
890             store_host_key(host, port, keytype, keystr);
891             return 1;
892         } else if (mbret == IDNO)
893             return 1;
894     }
895     return 0;   /* abandon the connection */
896 }
897
898 /*
899  * Ask whether the selected algorithm is acceptable (since it was
900  * below the configured 'warn' threshold).
901  */
902 int askalg(void *frontend, const char *algtype, const char *algname,
903            void (*callback)(void *ctx, int result), void *ctx)
904 {
905     static const char mbtitle[] = "%s Security Alert";
906     static const char msg[] =
907         "The first %s supported by the server\n"
908         "is %.64s, which is below the configured\n"
909         "warning threshold.\n"
910         "Do you want to continue with this connection?\n";
911     char *message, *title;
912     int mbret;
913
914     message = dupprintf(msg, algtype, algname);
915     title = dupprintf(mbtitle, appname);
916     mbret = MessageBox(NULL, message, title,
917                        MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2);
918     socket_reselect_all();
919     sfree(message);
920     sfree(title);
921     if (mbret == IDYES)
922         return 1;
923     else
924         return 0;
925 }
926
927 /*
928  * Ask whether to wipe a session log file before writing to it.
929  * Returns 2 for wipe, 1 for append, 0 for cancel (don't log).
930  */
931 int askappend(void *frontend, Filename *filename,
932               void (*callback)(void *ctx, int result), void *ctx)
933 {
934     static const char msgtemplate[] =
935         "The session log file \"%.*s\" already exists.\n"
936         "You can overwrite it with a new session log,\n"
937         "append your session log to the end of it,\n"
938         "or disable session logging for this session.\n"
939         "Hit Yes to wipe the file, No to append to it,\n"
940         "or Cancel to disable logging.";
941     char *message;
942     char *mbtitle;
943     int mbret;
944
945     message = dupprintf(msgtemplate, FILENAME_MAX, filename->path);
946     mbtitle = dupprintf("%s Log to File", appname);
947
948     mbret = MessageBox(NULL, message, mbtitle,
949                        MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3);
950
951     socket_reselect_all();
952
953     sfree(message);
954     sfree(mbtitle);
955
956     if (mbret == IDYES)
957         return 2;
958     else if (mbret == IDNO)
959         return 1;
960     else
961         return 0;
962 }
963
964 /*
965  * Warn about the obsolescent key file format.
966  * 
967  * Uniquely among these functions, this one does _not_ expect a
968  * frontend handle. This means that if PuTTY is ported to a
969  * platform which requires frontend handles, this function will be
970  * an anomaly. Fortunately, the problem it addresses will not have
971  * been present on that platform, so it can plausibly be
972  * implemented as an empty function.
973  */
974 void old_keyfile_warning(void)
975 {
976     static const char mbtitle[] = "%s Key File Warning";
977     static const char message[] =
978         "You are loading an SSH-2 private key which has an\n"
979         "old version of the file format. This means your key\n"
980         "file is not fully tamperproof. Future versions of\n"
981         "%s may stop supporting this private key format,\n"
982         "so we recommend you convert your key to the new\n"
983         "format.\n"
984         "\n"
985         "You can perform this conversion by loading the key\n"
986         "into PuTTYgen and then saving it again.";
987
988     char *msg, *title;
989     msg = dupprintf(message, appname);
990     title = dupprintf(mbtitle, appname);
991
992     MessageBox(NULL, msg, title, MB_OK);
993
994     socket_reselect_all();
995
996     sfree(msg);
997     sfree(title);
998 }