2 * osxdlg.m: various PuTTY dialog boxes for OS X.
5 #import <Cocoa/Cocoa.h>
12 * The `ConfigWindow' class is used to start up a new PuTTY
17 @interface ConfigTree : NSObject
23 - (void)addPath:(char *)path;
26 @implementation ConfigTree
32 nitems = itemsize = 0;
35 - (void)addPath:(char *)path
37 if (nitems >= itemsize) {
39 paths = sresize(paths, itemsize, NSString *);
40 levels = sresize(levels, itemsize, int);
42 paths[nitems] = [[NSString stringWithCString:path] retain];
43 levels[nitems] = ctrl_path_elements(path) - 1;
50 for (i = 0; i < nitems; i++)
58 - (id)iterateChildren:(int)index ofItem:(id)item count:(int *)count
63 for (i = 0; i < nitems; i++)
78 if (i >= nitems || levels[i] != plevel+1)
84 } while (i < nitems && levels[i] > plevel+1);
90 - (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
92 return [self iterateChildren:index ofItem:item count:NULL];
94 - (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
97 /* pass nitems+1 to ensure we run off the end */
98 [self iterateChildren:nitems+1 ofItem:item count:&count];
101 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
103 return [self outlineView:outlineView numberOfChildrenOfItem:item] > 0;
105 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item
108 * Trim off all path elements except the last one.
110 NSArray *components = [item componentsSeparatedByString:@"/"];
111 return [components objectAtIndex:[components count]-1];
115 @implementation ConfigWindow
116 - (id)initWithConfig:(Config)aCfg
118 NSScrollView *scrollview;
120 ConfigTree *treedata;
126 get_sesslist(&sl, TRUE);
128 ctrlbox = ctrl_new_box();
129 setup_config_box(ctrlbox, &sl, FALSE /*midsession*/, aCfg.protocol,
130 0 /* protcfginfo */);
131 unix_setup_config_box(ctrlbox, FALSE /*midsession*/);
133 cfg = aCfg; /* structure copy */
135 self = [super initWithContentRect:NSMakeRect(0,0,300,300)
136 styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
137 NSClosableWindowMask)
138 backing:NSBackingStoreBuffered
140 [self setTitle:@"PuTTY Configuration"];
142 [self setIgnoresMouseEvents:NO];
144 dv = fe_dlg_init(&cfg, self, self, @selector(configBoxFinished:));
146 scrollview = [[NSScrollView alloc] initWithFrame:NSMakeRect(20,20,10,10)];
147 treeview = [[NSOutlineView alloc] initWithFrame:[scrollview frame]];
148 [scrollview setBorderType:NSLineBorder];
149 [scrollview setDocumentView:treeview];
150 [[self contentView] addSubview:scrollview];
151 [scrollview setHasVerticalScroller:YES];
152 [scrollview setAutohidesScrollers:YES];
153 /* FIXME: the below is untested. Test it then remove this notice. */
154 [treeview setAllowsColumnReordering:NO];
155 [treeview setAllowsColumnResizing:NO];
156 [treeview setAllowsMultipleSelection:NO];
157 [treeview setAllowsEmptySelection:NO];
158 [treeview setAllowsColumnSelection:YES];
160 treedata = [[[ConfigTree alloc] init] retain];
162 col = [[NSTableColumn alloc] initWithIdentifier:nil];
163 [treeview addTableColumn:col];
164 [treeview setOutlineTableColumn:col];
166 [[treeview headerView] setFrame:NSMakeRect(0,0,0,0)];
169 * Create the controls.
175 for (i = 0; i < ctrlbox->nctrlsets; i++) {
176 struct controlset *s = ctrlbox->ctrlsets[i];
181 create_ctrls(dv, [self contentView], s, &mw, &mh);
188 int j = path ? ctrl_path_compare(s->pathname, path) : 0;
190 if (j != INT_MAX) { /* add to treeview, start new panel */
194 * We expect never to find an implicit path
195 * component. For example, we expect never to
196 * see A/B/C followed by A/D/E, because that
197 * would _implicitly_ create A/D. All our path
198 * prefixes are expected to contain actual
199 * controls and be selectable in the treeview;
200 * so we would expect to see A/D _explicitly_
201 * before encountering A/D/E.
203 assert(j == ctrl_path_elements(s->pathname) - 1);
205 c = strrchr(s->pathname, '/');
211 [treedata addPath:s->pathname];
217 create_ctrls(dv, [self contentView], s, &mw, &mh);
218 if (wmin < mw + 3*20+150)
219 wmin = mw + 3*20+150;
221 if (hmin < panelht - 20)
231 [treeview setDataSource:treedata];
232 for (i = [treeview numberOfRows]; i-- ;)
233 [treeview expandItem:[treeview itemAtRow:i] expandChildren:YES];
235 [treeview sizeToFit];
236 r = [treeview frame];
237 if (hmin < r.size.height)
238 hmin = r.size.height;
241 [self setContentSize:NSMakeSize(wmin, hmin+60+by)];
242 [scrollview setFrame:NSMakeRect(20, 40+by, 150, hmin)];
243 [treeview setDelegate:self];
247 * Now place the controls.
254 for (i = 0; i < ctrlbox->nctrlsets; i++) {
255 struct controlset *s = ctrlbox->ctrlsets[i];
258 by -= VSPACING + place_ctrls(dv, s, 20, by, wmin-40);
260 if (!path || strcmp(s->pathname, path))
263 panelht += VSPACING + place_ctrls(dv, s, 2*20+150,
272 select_panel(dv, ctrlbox, [[treeview itemAtRow:0] cString]);
274 [treeview reloadData];
276 dlg_refresh(NULL, dv);
278 [self center]; /* :-) */
282 - (void)configBoxFinished:(id)object
284 int ret = [object intValue]; /* it'll be an NSNumber */
286 [controller performSelectorOnMainThread:
287 @selector(newSessionWithConfig:)
288 withObject:[NSData dataWithBytes:&cfg length:sizeof(cfg)]
293 - (void)outlineViewSelectionDidChange:(NSNotification *)notification
295 const char *path = [[treeview itemAtRow:[treeview selectedRow]] cString];
296 select_panel(dv, ctrlbox, path);
298 - (BOOL)outlineView:(NSOutlineView *)outlineView
299 shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item
301 return NO; /* no editing! */
305 /* ----------------------------------------------------------------------
306 * Various special-purpose dialog boxes.
309 int askappend(void *frontend, Filename filename)
311 return 0; /* FIXME */
315 void (*callback)(void *ctx, int result);
319 static void askalg_callback(void *ctx, int result)
321 struct algstate *state = (struct algstate *)ctx;
323 state->callback(state->ctx, result == 0);
327 int askalg(void *frontend, const char *algtype, const char *algname,
328 void (*callback)(void *ctx, int result), void *ctx)
330 static const char msg[] =
331 "The first %s supported by the server is "
332 "%s, which is below the configured warning threshold.\n"
333 "Continue with connection?";
336 SessionWindow *win = (SessionWindow *)frontend;
337 struct algstate *state;
340 text = dupprintf(msg, algtype, algname);
342 state = snew(struct algstate);
343 state->callback = callback;
346 alert = [NSAlert alloc];
347 [alert setInformativeText:[NSString stringWithCString:text]];
348 [alert addButtonWithTitle:@"Yes"];
349 [alert addButtonWithTitle:@"No"];
350 [win startAlert:alert withCallback:askalg_callback andCtx:state];
355 struct hostkeystate {
356 char *host, *keytype, *keystr;
358 void (*callback)(void *ctx, int result);
362 static void verify_ssh_host_key_callback(void *ctx, int result)
364 struct hostkeystate *state = (struct hostkeystate *)ctx;
366 if (result == NSAlertThirdButtonReturn) /* `Accept' */
367 store_host_key(state->host, state->port,
368 state->keytype, state->keystr);
369 state->callback(state->ctx, result != NSAlertFirstButtonReturn);
371 sfree(state->keytype);
372 sfree(state->keystr);
376 int verify_ssh_host_key(void *frontend, char *host, int port, char *keytype,
377 char *keystr, char *fingerprint,
378 void (*callback)(void *ctx, int result), void *ctx)
380 static const char absenttxt[] =
381 "The server's host key is not cached. You have no guarantee "
382 "that the server is the computer you think it is.\n"
383 "The server's %s key fingerprint is:\n"
385 "If you trust this host, press \"Accept\" to add the key to "
386 "PuTTY's cache and carry on connecting.\n"
387 "If you want to carry on connecting just once, without "
388 "adding the key to the cache, press \"Connect Once\".\n"
389 "If you do not trust this host, press \"Cancel\" to abandon the "
391 static const char wrongtxt[] =
392 "WARNING - POTENTIAL SECURITY BREACH!\n"
393 "The server's host key does not match the one PuTTY has "
394 "cached. This means that either the server administrator "
395 "has changed the host key, or you have actually connected "
396 "to another computer pretending to be the server.\n"
397 "The new %s key fingerprint is:\n"
399 "If you were expecting this change and trust the new key, "
400 "press \"Accept\" to update PuTTY's cache and continue connecting.\n"
401 "If you want to carry on connecting but without updating "
402 "the cache, press \"Connect Once\".\n"
403 "If you want to abandon the connection completely, press "
404 "\"Cancel\" to cancel. Pressing \"Cancel\" is the ONLY guaranteed "
409 SessionWindow *win = (SessionWindow *)frontend;
410 struct hostkeystate *state;
416 ret = verify_host_key(host, port, keytype, keystr);
421 text = dupprintf((ret == 2 ? wrongtxt : absenttxt), keytype, fingerprint);
423 state = snew(struct hostkeystate);
424 state->callback = callback;
426 state->host = dupstr(host);
428 state->keytype = dupstr(keytype);
429 state->keystr = dupstr(keystr);
431 alert = [[NSAlert alloc] init];
432 [alert setInformativeText:[NSString stringWithCString:text]];
433 [alert addButtonWithTitle:@"Cancel"];
434 [alert addButtonWithTitle:@"Connect Once"];
435 [alert addButtonWithTitle:@"Accept"];
436 [win startAlert:alert withCallback:verify_ssh_host_key_callback
442 void old_keyfile_warning(void)
445 * This should never happen on OS X. We hope.
449 void about_box(void *window)