2 * osxdlg.m: various PuTTY dialog boxes for OS X.
\r
5 #import <Cocoa/Cocoa.h>
\r
9 #include "osxclass.h"
\r
12 * The `ConfigWindow' class is used to start up a new PuTTY
\r
17 @interface ConfigTree : NSObject
\r
21 int nitems, itemsize;
\r
23 - (void)addPath:(char *)path;
\r
26 @implementation ConfigTree
\r
29 self = [super init];
\r
32 nitems = itemsize = 0;
\r
35 - (void)addPath:(char *)path
\r
37 if (nitems >= itemsize) {
\r
39 paths = sresize(paths, itemsize, NSString *);
\r
40 levels = sresize(levels, itemsize, int);
\r
42 paths[nitems] = [[NSString stringWithCString:path] retain];
\r
43 levels[nitems] = ctrl_path_elements(path) - 1;
\r
50 for (i = 0; i < nitems; i++)
\r
58 - (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count
\r
63 for (i = 0; i < nitems; i++)
\r
64 if (paths[i] == item)
\r
78 if (i >= nitems || levels[i] != plevel+1)
\r
84 } while (i < nitems && levels[i] > plevel+1);
\r
90 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
\r
92 return [self iterateChildren:index ofItem:item count:NULL];
\r
94 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
\r
97 /* pass nitems+1 to ensure we run off the end */
\r
98 [self iterateChildren:nitems+1 ofItem:item count:&count];
\r
101 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
\r
103 return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
\r
105 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
\r
108 * Trim off all path elements except the last one.
\r
110 NSArray *components = [item componentsSeparatedByString:@"/"];
\r
111 return [components objectAtIndex:[components count]-1];
\r
115 @implementation ConfigWindow
\r
116 - (id)initWithConfig:(Config)aCfg
\r
118 NSScrollView *scrollview;
\r
119 NSTableColumn *col;
\r
120 ConfigTree *treedata;
\r
121 int by = 0, mby = 0;
\r
126 ctrlbox = ctrl_new_box();
\r
127 setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol,
\r
128 0 /* protcfginfo */);
\r
129 unix_setup_config_box(ctrlbox, FALSE /*midsession*/, aCfg.protocol);
\r
131 cfg = aCfg; /* structure copy */
\r
133 self = [super initWithContentRect:NSMakeRect(0,0,300,300)
\r
134 styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
\r
135 NSClosableWindowMask)
\r
136 backing:NSBackingStoreBuffered
\r
138 [self setTitle:@"PuTTY Configuration"];
\r
140 [self setIgnoresMouseEvents:NO];
\r
142 dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));
\r
144 scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];
\r
145 treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];
\r
146 [scrollview setBorderType:NSLineBorder];
\r
147 [scrollview setDocumentView:treeview];
\r
148 [[self contentView] addSubview:scrollview];
\r
149 [scrollview setHasVerticalScroller:YES];
\r
150 [scrollview setAutohidesScrollers:YES];
\r
151 /* FIXME: the below is untested. Test it then remove this notice. */
\r
152 [treeview setAllowsColumnReordering:NO];
\r
153 [treeview setAllowsColumnResizing:NO];
\r
154 [treeview setAllowsMultipleSelection:NO];
\r
155 [treeview setAllowsEmptySelection:NO];
\r
156 [treeview setAllowsColumnSelection:YES];
\r
158 treedata = [[[ConfigTree alloc] init] retain];
\r
160 col = [[NSTableColumn alloc] initWithIdentifier:nil];
\r
161 [treeview addTableColumn:col];
\r
162 [treeview setOutlineTableColumn:col];
\r
164 [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];
\r
167 * Create the controls.
\r
173 for (i = 0; i < ctrlbox->nctrlsets; i++) {
\r
174 struct controlset *s = ctrlbox->ctrlsets[i];
\r
177 if (!*s->pathname) {
\r
179 create_ctrls(dv, [self contentView], s, &mw, &mh);
\r
183 if (wmin < mw + 40)
\r
186 int j = path ? ctrl_path_compare(s->pathname, path) : 0;
\r
188 if (j != INT_MAX) { /* add to treeview, start new panel */
\r
192 * We expect never to find an implicit path
\r
193 * component. For example, we expect never to
\r
194 * see A/B/C followed by A/D/E, because that
\r
195 * would _implicitly_ create A/D. All our path
\r
196 * prefixes are expected to contain actual
\r
197 * controls and be selectable in the treeview;
\r
198 * so we would expect to see A/D _explicitly_
\r
199 * before encountering A/D/E.
\r
201 assert(j == ctrl_path_elements(s->pathname) - 1);
\r
203 c = strrchr(s->pathname, '/');
\r
209 [treedata addPath:s->pathname];
\r
210 path = s->pathname;
\r
215 create_ctrls(dv, [self contentView], s, &mw, &mh);
\r
216 if (wmin < mw + 3*20+150)
\r
217 wmin = mw + 3*20+150;
\r
218 panelht += mh + 20;
\r
219 if (hmin < panelht - 20)
\r
220 hmin = panelht - 20;
\r
229 [treeview setDataSource:treedata];
\r
230 for (i = [treeview numberOfRows]; i-- ;)
\r
231 [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];
\r
233 [treeview sizeToFit];
\r
234 r = [treeview frame];
\r
235 if (hmin < r.size.height)
\r
236 hmin = r.size.height;
\r
239 [self setContentSize:NSMakeSize(wmin, hmin+60+by)];
\r
240 [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];
\r
241 [treeview setDelegate:self];
\r
245 * Now place the controls.
\r
252 for (i = 0; i < ctrlbox->nctrlsets; i++) {
\r
253 struct controlset *s = ctrlbox->ctrlsets[i];
\r
255 if (!*s->pathname) {
\r
256 by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);
\r
258 if (!path || strcmp(s->pathname, path))
\r
261 panelht += VSPACING + place_ctrls(dv, s, 2*20+150,
\r
262 40+mby+hmin-panelht,
\r
263 wmin - (3*20+150));
\r
265 path = s->pathname;
\r
270 select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);
\r
272 [treeview reloadData];
\r
274 dlg_refresh(NULL, dv);
\r
276 [self center]; /* :-) */
\r
280 - (void)configBoxFinished:(id)object
\r
282 int ret = [object intValue]; /* it'll be an NSNumber */
\r
284 [controller performSelectorOnMainThread:
\r
285 @selector(newSessionWithConfig:)
\r
286 withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]
\r
291 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
\r
293 const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];
\r
294 select_panel(dv, ctrlbox, path);
\r
296 - (BOOL)outlineView:(NSOutlineView *)outlineView
\r
297 shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
\r
299 return NO; /* no editing! */
\r
303 /* ----------------------------------------------------------------------
\r
304 * Various special-purpose dialog boxes.
\r
307 struct appendstate {
\r
308 void (*callback)(void *ctx, int result);
\r
312 static void askappend_callback(void *ctx, int result)
\r
314 struct appendstate *state = (struct appendstate *)ctx;
\r
316 state->callback(state->ctx, (result == NSAlertFirstButtonReturn ? 2 :
\r
317 result == NSAlertSecondButtonReturn ? 1 : 0));
\r
321 int askappend(void *frontend, Filename filename,
\r
322 void (*callback)(void *ctx, int result), void *ctx)
\r
324 static const char msgtemplate[] =
\r
325 "The session log file \"%s\" already exists. "
\r
326 "You can overwrite it with a new session log, "
\r
327 "append your session log to the end of it, "
\r
328 "or disable session logging for this session.";
\r
331 SessionWindow *win = (SessionWindow *)frontend;
\r
332 struct appendstate *state;
\r
335 text = dupprintf(msgtemplate, filename.path);
\r
337 state = snew(struct appendstate);
\r
338 state->callback = callback;
\r
341 alert = [[NSAlert alloc] init];
\r
342 [alert setInformativeText:[NSString stringWithCString:text]];
\r
343 [alert addButtonWithTitle:@"Overwrite"];
\r
344 [alert addButtonWithTitle:@"Append"];
\r
345 [alert addButtonWithTitle:@"Disable"];
\r
346 [win startAlert:alert withCallback:askappend_callback andCtx:state];
\r
352 void (*callback)(void *ctx, int result);
\r
356 static void askalg_callback(void *ctx, int result)
\r
358 struct algstate *state = (struct algstate *)ctx;
\r
360 state->callback(state->ctx, result == NSAlertFirstButtonReturn);
\r
364 int askalg(void *frontend, const char *algtype, const char *algname,
\r
365 void (*callback)(void *ctx, int result), void *ctx)
\r
367 static const char msg[] =
\r
368 "The first %s supported by the server is "
\r
369 "%s, which is below the configured warning threshold.\n"
\r
370 "Continue with connection?";
\r
373 SessionWindow *win = (SessionWindow *)frontend;
\r
374 struct algstate *state;
\r
377 text = dupprintf(msg, algtype, algname);
\r
379 state = snew(struct algstate);
\r
380 state->callback = callback;
\r
383 alert = [[NSAlert alloc] init];
\r
384 [alert setInformativeText:[NSString stringWithCString:text]];
\r
385 [alert addButtonWithTitle:@"Yes"];
\r
386 [alert addButtonWithTitle:@"No"];
\r
387 [win startAlert:alert withCallback:askalg_callback andCtx:state];
\r
392 struct hostkeystate {
\r
393 char *host, *keytype, *keystr;
\r
395 void (*callback)(void *ctx, int result);
\r
399 static void verify_ssh_host_key_callback(void *ctx, int result)
\r
401 struct hostkeystate *state = (struct hostkeystate *)ctx;
\r
403 if (result == NSAlertThirdButtonReturn) /* `Accept' */
\r
404 store_host_key(state->host, state->port,
\r
405 state->keytype, state->keystr);
\r
406 state->callback(state->ctx, result != NSAlertFirstButtonReturn);
\r
407 sfree(state->host);
\r
408 sfree(state->keytype);
\r
409 sfree(state->keystr);
\r
413 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
\r
414 char *keystr, char *fingerprint,
\r
415 void (*callback)(void *ctx, int result), void *ctx)
\r
417 static const char absenttxt[] =
\r
418 "The server's host key is not cached. You have no guarantee "
\r
419 "that the server is the computer you think it is.\n"
\r
420 "The server's %s key fingerprint is:\n"
\r
422 "If you trust this host, press \"Accept\" to add the key to "
\r
423 "PuTTY's cache and carry on connecting.\n"
\r
424 "If you want to carry on connecting just once, without "
\r
425 "adding the key to the cache, press \"Connect Once\".\n"
\r
426 "If you do not trust this host, press \"Cancel\" to abandon the "
\r
428 static const char wrongtxt[] =
\r
429 "WARNING - POTENTIAL SECURITY BREACH!\n"
\r
430 "The server's host key does not match the one PuTTY has "
\r
431 "cached. This means that either the server administrator "
\r
432 "has changed the host key, or you have actually connected "
\r
433 "to another computer pretending to be the server.\n"
\r
434 "The new %s key fingerprint is:\n"
\r
436 "If you were expecting this change and trust the new key, "
\r
437 "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
\r
438 "If you want to carry on connecting but without updating "
\r
439 "the cache, press \"Connect Once\".\n"
\r
440 "If you want to abandon the connection completely, press "
\r
441 "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "
\r
446 SessionWindow *win = (SessionWindow *)frontend;
\r
447 struct hostkeystate *state;
\r
453 ret = verify_host_key(host, port, keytype, keystr);
\r
458 text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);
\r
460 state = snew(struct hostkeystate);
\r
461 state->callback = callback;
\r
463 state->host = dupstr(host);
\r
464 state->port = port;
\r
465 state->keytype = dupstr(keytype);
\r
466 state->keystr = dupstr(keystr);
\r
468 alert = [[NSAlert alloc] init];
\r
469 [alert setInformativeText:[NSString stringWithCString:text]];
\r
470 [alert addButtonWithTitle:@"Cancel"];
\r
471 [alert addButtonWithTitle:@"Connect Once"];
\r
472 [alert addButtonWithTitle:@"Accept"];
\r
473 [win startAlert:alert withCallback:verify_ssh_host_key_callback
\r
479 void old_keyfile_warning(void)
\r
482 * This should never happen on OS X. We hope.
\r
486 static void connection_fatal_callback(void *ctx, int result)
\r
488 SessionWindow *win = (SessionWindow *)ctx;
\r
490 [win endSession:FALSE];
\r
493 void connection_fatal(void *frontend, char *p, ...)
\r
495 SessionWindow *win = (SessionWindow *)frontend;
\r
501 msg = dupvprintf(p, ap);
\r
504 alert = [[NSAlert alloc] init];
\r
505 [alert setInformativeText:[NSString stringWithCString:msg]];
\r
506 [alert addButtonWithTitle:@"Proceed"];
\r
507 [win startAlert:alert withCallback:connection_fatal_callback
\r