]>
Commit | Line | Data |
---|---|---|
1c1af145 | 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 Config cfg; /* 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 | ||
458 | for (i = 0; i < ctrlbox->nctrlsets; i++) { | |
459 | struct controlset *s = ctrlbox->ctrlsets[i]; | |
460 | HTREEITEM item; | |
461 | int j; | |
462 | char *c; | |
463 | ||
464 | if (!s->pathname[0]) | |
465 | continue; | |
466 | j = path ? ctrl_path_compare(s->pathname, path) : 0; | |
467 | if (j == INT_MAX) | |
468 | continue; /* same path, nothing to add to tree */ | |
469 | ||
470 | /* | |
471 | * We expect never to find an implicit path | |
472 | * component. For example, we expect never to see | |
473 | * A/B/C followed by A/D/E, because that would | |
474 | * _implicitly_ create A/D. All our path prefixes | |
475 | * are expected to contain actual controls and be | |
476 | * selectable in the treeview; so we would expect | |
477 | * to see A/D _explicitly_ before encountering | |
478 | * A/D/E. | |
479 | */ | |
480 | assert(j == ctrl_path_elements(s->pathname) - 1); | |
481 | ||
482 | c = strrchr(s->pathname, '/'); | |
483 | if (!c) | |
484 | c = s->pathname; | |
485 | else | |
486 | c++; | |
487 | ||
488 | item = treeview_insert(&tvfaff, j, c, s->pathname); | |
489 | if (!hfirst) | |
490 | hfirst = item; | |
491 | ||
492 | path = s->pathname; | |
493 | } | |
494 | ||
495 | /* | |
496 | * Put the treeview selection on to the Session panel. | |
497 | * This should also cause creation of the relevant | |
498 | * controls. | |
499 | */ | |
500 | TreeView_SelectItem(treeview, hfirst); | |
501 | } | |
502 | ||
503 | /* | |
504 | * Set focus into the first available control. | |
505 | */ | |
506 | { | |
507 | int i; | |
508 | struct winctrl *c; | |
509 | ||
510 | for (i = 0; (c = winctrl_findbyindex(&ctrls_panel, i)) != NULL; | |
511 | i++) { | |
512 | if (c->ctrl) { | |
513 | dlg_set_focus(c->ctrl, &dp); | |
514 | break; | |
515 | } | |
516 | } | |
517 | } | |
518 | ||
519 | SetWindowLongPtr(hwnd, GWLP_USERDATA, 1); | |
520 | return 0; | |
521 | case WM_LBUTTONUP: | |
522 | /* | |
523 | * Button release should trigger WM_OK if there was a | |
524 | * previous double click on the session list. | |
525 | */ | |
526 | ReleaseCapture(); | |
527 | if (dp.ended) | |
528 | SaneEndDialog(hwnd, dp.endresult ? 1 : 0); | |
529 | break; | |
530 | case WM_NOTIFY: | |
531 | if (LOWORD(wParam) == IDCX_TREEVIEW && | |
532 | ((LPNMHDR) lParam)->code == TVN_SELCHANGED) { | |
533 | HTREEITEM i = | |
534 | TreeView_GetSelection(((LPNMHDR) lParam)->hwndFrom); | |
535 | TVITEM item; | |
536 | char buffer[64]; | |
537 | ||
538 | SendMessage (hwnd, WM_SETREDRAW, FALSE, 0); | |
539 | ||
540 | item.hItem = i; | |
541 | item.pszText = buffer; | |
542 | item.cchTextMax = sizeof(buffer); | |
543 | item.mask = TVIF_TEXT | TVIF_PARAM; | |
544 | TreeView_GetItem(((LPNMHDR) lParam)->hwndFrom, &item); | |
545 | { | |
546 | /* Destroy all controls in the currently visible panel. */ | |
547 | int k; | |
548 | HWND item; | |
549 | struct winctrl *c; | |
550 | ||
551 | while ((c = winctrl_findbyindex(&ctrls_panel, 0)) != NULL) { | |
552 | for (k = 0; k < c->num_ids; k++) { | |
553 | item = GetDlgItem(hwnd, c->base_id + k); | |
554 | if (item) | |
555 | DestroyWindow(item); | |
556 | } | |
557 | winctrl_rem_shortcuts(&dp, c); | |
558 | winctrl_remove(&ctrls_panel, c); | |
559 | sfree(c->data); | |
560 | sfree(c); | |
561 | } | |
562 | } | |
563 | create_controls(hwnd, (char *)item.lParam); | |
564 | ||
565 | dlg_refresh(NULL, &dp); /* set up control values */ | |
566 | ||
567 | SendMessage (hwnd, WM_SETREDRAW, TRUE, 0); | |
568 | InvalidateRect (hwnd, NULL, TRUE); | |
569 | ||
570 | SetFocus(((LPNMHDR) lParam)->hwndFrom); /* ensure focus stays */ | |
571 | return 0; | |
572 | } | |
573 | break; | |
574 | case WM_COMMAND: | |
575 | case WM_DRAWITEM: | |
576 | default: /* also handle drag list msg here */ | |
577 | /* | |
578 | * Only process WM_COMMAND once the dialog is fully formed. | |
579 | */ | |
580 | if (GetWindowLongPtr(hwnd, GWLP_USERDATA) == 1) { | |
581 | ret = winctrl_handle_command(&dp, msg, wParam, lParam); | |
582 | if (dp.ended && GetCapture() != hwnd) | |
583 | SaneEndDialog(hwnd, dp.endresult ? 1 : 0); | |
584 | } else | |
585 | ret = 0; | |
586 | return ret; | |
587 | case WM_HELP: | |
588 | if (!winctrl_context_help(&dp, hwnd, | |
589 | ((LPHELPINFO)lParam)->iCtrlId)) | |
590 | MessageBeep(0); | |
591 | break; | |
592 | case WM_CLOSE: | |
593 | quit_help(hwnd); | |
594 | SaneEndDialog(hwnd, 0); | |
595 | return 0; | |
596 | ||
597 | /* Grrr Explorer will maximize Dialogs! */ | |
598 | case WM_SIZE: | |
599 | if (wParam == SIZE_MAXIMIZED) | |
600 | force_normal(hwnd); | |
601 | return 0; | |
602 | ||
603 | } | |
604 | return 0; | |
605 | } | |
606 | ||
607 | void modal_about_box(HWND hwnd) | |
608 | { | |
609 | EnableWindow(hwnd, 0); | |
610 | DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc); | |
611 | EnableWindow(hwnd, 1); | |
612 | SetActiveWindow(hwnd); | |
613 | } | |
614 | ||
615 | void show_help(HWND hwnd) | |
616 | { | |
617 | launch_help(hwnd, NULL); | |
618 | } | |
619 | ||
620 | void defuse_showwindow(void) | |
621 | { | |
622 | /* | |
623 | * Work around the fact that the app's first call to ShowWindow | |
624 | * will ignore the default in favour of the shell-provided | |
625 | * setting. | |
626 | */ | |
627 | { | |
628 | HWND hwnd; | |
629 | hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), | |
630 | NULL, NullDlgProc); | |
631 | ShowWindow(hwnd, SW_HIDE); | |
632 | SetActiveWindow(hwnd); | |
633 | DestroyWindow(hwnd); | |
634 | } | |
635 | } | |
636 | ||
637 | int do_config(void) | |
638 | { | |
639 | int ret; | |
640 | ||
641 | ctrlbox = ctrl_new_box(); | |
642 | setup_config_box(ctrlbox, FALSE, 0, 0); | |
643 | win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), FALSE, 0); | |
644 | dp_init(&dp); | |
645 | winctrl_init(&ctrls_base); | |
646 | winctrl_init(&ctrls_panel); | |
647 | dp_add_tree(&dp, &ctrls_base); | |
648 | dp_add_tree(&dp, &ctrls_panel); | |
649 | dp.wintitle = dupprintf("%s Configuration", appname); | |
650 | dp.errtitle = dupprintf("%s Error", appname); | |
651 | dp.data = &cfg; | |
652 | dlg_auto_set_fixed_pitch_flag(&dp); | |
653 | dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */ | |
654 | ||
655 | ret = | |
656 | SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, | |
657 | GenericMainDlgProc); | |
658 | ||
659 | ctrl_free_box(ctrlbox); | |
660 | winctrl_cleanup(&ctrls_panel); | |
661 | winctrl_cleanup(&ctrls_base); | |
662 | dp_cleanup(&dp); | |
663 | ||
664 | return ret; | |
665 | } | |
666 | ||
667 | int do_reconfig(HWND hwnd, int protcfginfo) | |
668 | { | |
669 | Config backup_cfg; | |
670 | int ret; | |
671 | ||
672 | backup_cfg = cfg; /* structure copy */ | |
673 | ||
674 | ctrlbox = ctrl_new_box(); | |
675 | setup_config_box(ctrlbox, TRUE, cfg.protocol, protcfginfo); | |
676 | win_setup_config_box(ctrlbox, &dp.hwnd, has_help(), TRUE, | |
677 | cfg.protocol); | |
678 | dp_init(&dp); | |
679 | winctrl_init(&ctrls_base); | |
680 | winctrl_init(&ctrls_panel); | |
681 | dp_add_tree(&dp, &ctrls_base); | |
682 | dp_add_tree(&dp, &ctrls_panel); | |
683 | dp.wintitle = dupprintf("%s Reconfiguration", appname); | |
684 | dp.errtitle = dupprintf("%s Error", appname); | |
685 | dp.data = &cfg; | |
686 | dlg_auto_set_fixed_pitch_flag(&dp); | |
687 | dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */ | |
688 | ||
689 | ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL, | |
690 | GenericMainDlgProc); | |
691 | ||
692 | ctrl_free_box(ctrlbox); | |
693 | winctrl_cleanup(&ctrls_base); | |
694 | winctrl_cleanup(&ctrls_panel); | |
695 | dp_cleanup(&dp); | |
696 | ||
697 | if (!ret) | |
698 | cfg = backup_cfg; /* structure copy */ | |
699 | ||
700 | return ret; | |
701 | } | |
702 | ||
703 | void logevent(void *frontend, const char *string) | |
704 | { | |
705 | char timebuf[40]; | |
706 | struct tm tm; | |
707 | ||
708 | log_eventlog(logctx, string); | |
709 | ||
710 | if (nevents >= negsize) { | |
711 | negsize += 64; | |
712 | events = sresize(events, negsize, char *); | |
713 | } | |
714 | ||
715 | tm=ltime(); | |
716 | strftime(timebuf, sizeof(timebuf), "%Y-%m-%d %H:%M:%S\t", &tm); | |
717 | ||
718 | events[nevents] = snewn(strlen(timebuf) + strlen(string) + 1, char); | |
719 | strcpy(events[nevents], timebuf); | |
720 | strcat(events[nevents], string); | |
721 | if (logbox) { | |
722 | int count; | |
723 | SendDlgItemMessage(logbox, IDN_LIST, LB_ADDSTRING, | |
724 | 0, (LPARAM) events[nevents]); | |
725 | count = SendDlgItemMessage(logbox, IDN_LIST, LB_GETCOUNT, 0, 0); | |
726 | SendDlgItemMessage(logbox, IDN_LIST, LB_SETTOPINDEX, count - 1, 0); | |
727 | } | |
728 | nevents++; | |
729 | } | |
730 | ||
731 | void showeventlog(HWND hwnd) | |
732 | { | |
733 | if (!logbox) { | |
734 | logbox = CreateDialog(hinst, MAKEINTRESOURCE(IDD_LOGBOX), | |
735 | hwnd, LogProc); | |
736 | ShowWindow(logbox, SW_SHOWNORMAL); | |
737 | } | |
738 | SetActiveWindow(logbox); | |
739 | } | |
740 | ||
741 | void showabout(HWND hwnd) | |
742 | { | |
743 | DialogBox(hinst, MAKEINTRESOURCE(IDD_ABOUTBOX), hwnd, AboutProc); | |
744 | } | |
745 | ||
746 | int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype, | |
747 | char *keystr, char *fingerprint, | |
748 | void (*callback)(void *ctx, int result), void *ctx) | |
749 | { | |
750 | int ret; | |
751 | ||
752 | static const char absentmsg[] = | |
753 | "The server's host key is not cached in the registry. You\n" | |
754 | "have no guarantee that the server is the computer you\n" | |
755 | "think it is.\n" | |
756 | "The server's %s key fingerprint is:\n" | |
757 | "%s\n" | |
758 | "If you trust this host, hit Yes to add the key to\n" | |
759 | "%s's cache and carry on connecting.\n" | |
760 | "If you want to carry on connecting just once, without\n" | |
761 | "adding the key to the cache, hit No.\n" | |
762 | "If you do not trust this host, hit Cancel to abandon the\n" | |
763 | "connection.\n"; | |
764 | ||
765 | static const char wrongmsg[] = | |
766 | "WARNING - POTENTIAL SECURITY BREACH!\n" | |
767 | "\n" | |
768 | "The server's host key does not match the one %s has\n" | |
769 | "cached in the registry. This means that either the\n" | |
770 | "server administrator has changed the host key, or you\n" | |
771 | "have actually connected to another computer pretending\n" | |
772 | "to be the server.\n" | |
773 | "The new %s key fingerprint is:\n" | |
774 | "%s\n" | |
775 | "If you were expecting this change and trust the new key,\n" | |
776 | "hit Yes to update %s's cache and continue connecting.\n" | |
777 | "If you want to carry on connecting but without updating\n" | |
778 | "the cache, hit No.\n" | |
779 | "If you want to abandon the connection completely, hit\n" | |
780 | "Cancel. Hitting Cancel is the ONLY guaranteed safe\n" "choice.\n"; | |
781 | ||
782 | static const char mbtitle[] = "%s Security Alert"; | |
783 | ||
784 | /* | |
785 | * Verify the key against the registry. | |
786 | */ | |
787 | ret = verify_host_key(host, port, keytype, keystr); | |
788 | ||
789 | if (ret == 0) /* success - key matched OK */ | |
790 | return 1; | |
791 | else if (ret == 2) { /* key was different */ | |
792 | int mbret; | |
793 | char *text = dupprintf(wrongmsg, appname, keytype, fingerprint, | |
794 | appname); | |
795 | char *caption = dupprintf(mbtitle, appname); | |
796 | mbret = message_box(text, caption, | |
797 | MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3, | |
798 | HELPCTXID(errors_hostkey_changed)); | |
799 | assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL); | |
800 | sfree(text); | |
801 | sfree(caption); | |
802 | if (mbret == IDYES) { | |
803 | store_host_key(host, port, keytype, keystr); | |
804 | return 1; | |
805 | } else if (mbret == IDNO) | |
806 | return 1; | |
807 | } else if (ret == 1) { /* key was absent */ | |
808 | int mbret; | |
809 | char *text = dupprintf(absentmsg, keytype, fingerprint, appname); | |
810 | char *caption = dupprintf(mbtitle, appname); | |
811 | mbret = message_box(text, caption, | |
812 | MB_ICONWARNING | MB_YESNOCANCEL | MB_DEFBUTTON3, | |
813 | HELPCTXID(errors_hostkey_absent)); | |
814 | assert(mbret==IDYES || mbret==IDNO || mbret==IDCANCEL); | |
815 | sfree(text); | |
816 | sfree(caption); | |
817 | if (mbret == IDYES) { | |
818 | store_host_key(host, port, keytype, keystr); | |
819 | return 1; | |
820 | } else if (mbret == IDNO) | |
821 | return 1; | |
822 | } | |
823 | return 0; /* abandon the connection */ | |
824 | } | |
825 | ||
826 | /* | |
827 | * Ask whether the selected algorithm is acceptable (since it was | |
828 | * below the configured 'warn' threshold). | |
829 | */ | |
830 | int askalg(void *frontend, const char *algtype, const char *algname, | |
831 | void (*callback)(void *ctx, int result), void *ctx) | |
832 | { | |
833 | static const char mbtitle[] = "%s Security Alert"; | |
834 | static const char msg[] = | |
835 | "The first %s supported by the server\n" | |
836 | "is %.64s, which is below the configured\n" | |
837 | "warning threshold.\n" | |
838 | "Do you want to continue with this connection?\n"; | |
839 | char *message, *title; | |
840 | int mbret; | |
841 | ||
842 | message = dupprintf(msg, algtype, algname); | |
843 | title = dupprintf(mbtitle, appname); | |
844 | mbret = MessageBox(NULL, message, title, | |
845 | MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON2); | |
846 | socket_reselect_all(); | |
847 | sfree(message); | |
848 | sfree(title); | |
849 | if (mbret == IDYES) | |
850 | return 1; | |
851 | else | |
852 | return 0; | |
853 | } | |
854 | ||
855 | /* | |
856 | * Ask whether to wipe a session log file before writing to it. | |
857 | * Returns 2 for wipe, 1 for append, 0 for cancel (don't log). | |
858 | */ | |
859 | int askappend(void *frontend, Filename filename, | |
860 | void (*callback)(void *ctx, int result), void *ctx) | |
861 | { | |
862 | static const char msgtemplate[] = | |
863 | "The session log file \"%.*s\" already exists.\n" | |
864 | "You can overwrite it with a new session log,\n" | |
865 | "append your session log to the end of it,\n" | |
866 | "or disable session logging for this session.\n" | |
867 | "Hit Yes to wipe the file, No to append to it,\n" | |
868 | "or Cancel to disable logging."; | |
869 | char *message; | |
870 | char *mbtitle; | |
871 | int mbret; | |
872 | ||
873 | message = dupprintf(msgtemplate, FILENAME_MAX, filename.path); | |
874 | mbtitle = dupprintf("%s Log to File", appname); | |
875 | ||
876 | mbret = MessageBox(NULL, message, mbtitle, | |
877 | MB_ICONQUESTION | MB_YESNOCANCEL | MB_DEFBUTTON3); | |
878 | ||
879 | socket_reselect_all(); | |
880 | ||
881 | sfree(message); | |
882 | sfree(mbtitle); | |
883 | ||
884 | if (mbret == IDYES) | |
885 | return 2; | |
886 | else if (mbret == IDNO) | |
887 | return 1; | |
888 | else | |
889 | return 0; | |
890 | } | |
891 | ||
892 | /* | |
893 | * Warn about the obsolescent key file format. | |
894 | * | |
895 | * Uniquely among these functions, this one does _not_ expect a | |
896 | * frontend handle. This means that if PuTTY is ported to a | |
897 | * platform which requires frontend handles, this function will be | |
898 | * an anomaly. Fortunately, the problem it addresses will not have | |
899 | * been present on that platform, so it can plausibly be | |
900 | * implemented as an empty function. | |
901 | */ | |
902 | void old_keyfile_warning(void) | |
903 | { | |
904 | static const char mbtitle[] = "%s Key File Warning"; | |
905 | static const char message[] = | |
906 | "You are loading an SSH-2 private key which has an\n" | |
907 | "old version of the file format. This means your key\n" | |
908 | "file is not fully tamperproof. Future versions of\n" | |
909 | "%s may stop supporting this private key format,\n" | |
910 | "so we recommend you convert your key to the new\n" | |
911 | "format.\n" | |
912 | "\n" | |
913 | "You can perform this conversion by loading the key\n" | |
914 | "into PuTTYgen and then saving it again."; | |
915 | ||
916 | char *msg, *title; | |
917 | msg = dupprintf(message, appname); | |
918 | title = dupprintf(mbtitle, appname); | |
919 | ||
920 | MessageBox(NULL, msg, title, MB_OK); | |
921 | ||
922 | socket_reselect_all(); | |
923 | ||
924 | sfree(msg); | |
925 | sfree(title); | |
926 | } |