1ddda1ca |
1 | /* |
2 | * osxmain.m: main-program file of Mac OS X PuTTY. |
3 | */ |
4 | |
5 | #import <Cocoa/Cocoa.h> |
6 | |
7 | #define PUTTY_DO_GLOBALS /* actually _define_ globals */ |
8 | |
9 | #include "putty.h" |
10 | #include "osxclass.h" |
11 | |
12 | /* ---------------------------------------------------------------------- |
13 | * Global variables. |
14 | */ |
15 | |
16 | AppController *controller; |
17 | |
18 | /* ---------------------------------------------------------------------- |
19 | * Miscellaneous elements of the interface to the cross-platform |
20 | * and Unix PuTTY code. |
21 | */ |
22 | |
1ddda1ca |
23 | char *platform_get_x_display(void) { |
24 | return NULL; |
25 | } |
26 | |
27 | FontSpec platform_default_fontspec(const char *name) |
28 | { |
29 | FontSpec ret; |
30 | /* FIXME */ |
31 | return ret; |
32 | } |
33 | |
34 | Filename platform_default_filename(const char *name) |
35 | { |
36 | Filename ret; |
37 | if (!strcmp(name, "LogFileName")) |
38 | strcpy(ret.path, "putty.log"); |
39 | else |
40 | *ret.path = '\0'; |
41 | return ret; |
42 | } |
43 | |
44 | char *platform_default_s(const char *name) |
45 | { |
46 | return NULL; |
47 | } |
48 | |
49 | int platform_default_i(const char *name, int def) |
50 | { |
51 | if (!strcmp(name, "CloseOnExit")) |
52 | return 2; /* maps to FORCE_ON after painful rearrangement :-( */ |
53 | return def; |
54 | } |
55 | |
56 | char *x_get_default(const char *key) |
57 | { |
58 | return NULL; /* this is a stub */ |
59 | } |
60 | |
ec5da310 |
61 | static void commonfatalbox(char *p, va_list ap) |
62 | { |
63 | char errorbuf[2048]; |
64 | NSAlert *alert; |
65 | |
66 | /* |
67 | * We may have come here because we ran out of memory, in which |
68 | * case it's entirely likely that that further memory |
69 | * allocations will fail. So (a) we use vsnprintf to format the |
70 | * error message rather than the usual dupvprintf; and (b) we |
71 | * have a fallback way to get the message out via stderr if |
72 | * even creating an NSAlert fails. |
73 | */ |
74 | vsnprintf(errorbuf, lenof(errorbuf), p, ap); |
75 | |
76 | alert = [NSAlert alloc]; |
77 | if (!alert) { |
78 | fprintf(stderr, "fatal error (and NSAlert failed): %s\n", errorbuf); |
79 | } else { |
80 | alert = [[alert init] autorelease]; |
81 | [alert addButtonWithTitle:@"Terminate"]; |
82 | [alert setInformativeText:[NSString stringWithCString:errorbuf]]; |
83 | [alert runModal]; |
84 | } |
85 | exit(1); |
86 | } |
87 | |
33f4bde2 |
88 | void nonfatal(void *frontend, char *p, ...) |
89 | { |
90 | char *errorbuf; |
91 | NSAlert *alert; |
92 | va_list ap; |
93 | |
94 | va_start(ap, p); |
95 | errorbuf = dupvprintf(p, ap); |
96 | va_end(ap); |
97 | |
98 | alert = [[[NSAlert alloc] init] autorelease]; |
99 | [alert addButtonWithTitle:@"Error"]; |
100 | [alert setInformativeText:[NSString stringWithCString:errorbuf]]; |
101 | [alert runModal]; |
102 | |
103 | sfree(errorbuf); |
104 | } |
105 | |
ec5da310 |
106 | void fatalbox(char *p, ...) |
1ddda1ca |
107 | { |
1ddda1ca |
108 | va_list ap; |
1ddda1ca |
109 | va_start(ap, p); |
ec5da310 |
110 | commonfatalbox(p, ap); |
1ddda1ca |
111 | va_end(ap); |
1ddda1ca |
112 | } |
113 | |
ec5da310 |
114 | void modalfatalbox(char *p, ...) |
1ddda1ca |
115 | { |
1ddda1ca |
116 | va_list ap; |
1ddda1ca |
117 | va_start(ap, p); |
ec5da310 |
118 | commonfatalbox(p, ap); |
1ddda1ca |
119 | va_end(ap); |
1ddda1ca |
120 | } |
121 | |
122 | void cmdline_error(char *p, ...) |
123 | { |
124 | va_list ap; |
125 | fprintf(stderr, "%s: ", appname); |
126 | va_start(ap, p); |
127 | vfprintf(stderr, p, ap); |
128 | va_end(ap); |
129 | fputc('\n', stderr); |
130 | exit(1); |
131 | } |
132 | |
133 | /* |
134 | * Clean up and exit. |
135 | */ |
136 | void cleanup_exit(int code) |
137 | { |
138 | /* |
139 | * Clean up. |
140 | */ |
141 | sk_cleanup(); |
142 | random_save_seed(); |
143 | exit(code); |
144 | } |
145 | |
146 | /* ---------------------------------------------------------------------- |
147 | * Tiny extension to NSMenuItem which carries a payload of a `void |
148 | * *', allowing several menu items to invoke the same message but |
149 | * pass different data through it. |
150 | */ |
151 | @interface DataMenuItem : NSMenuItem |
152 | { |
153 | void *payload; |
154 | } |
155 | - (void)setPayload:(void *)d; |
156 | - (void *)getPayload; |
157 | @end |
158 | @implementation DataMenuItem |
159 | - (void)setPayload:(void *)d |
160 | { |
161 | payload = d; |
162 | } |
163 | - (void *)getPayload |
164 | { |
165 | return payload; |
166 | } |
167 | @end |
168 | |
169 | /* ---------------------------------------------------------------------- |
170 | * Utility routines for constructing OS X menus. |
171 | */ |
172 | |
173 | NSMenu *newmenu(const char *title) |
174 | { |
175 | return [[[NSMenu allocWithZone:[NSMenu menuZone]] |
176 | initWithTitle:[NSString stringWithCString:title]] |
177 | autorelease]; |
178 | } |
179 | |
180 | NSMenu *newsubmenu(NSMenu *parent, const char *title) |
181 | { |
182 | NSMenuItem *item; |
183 | NSMenu *child; |
184 | |
185 | item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] |
186 | initWithTitle:[NSString stringWithCString:title] |
187 | action:NULL |
188 | keyEquivalent:@""] |
189 | autorelease]; |
190 | child = newmenu(title); |
191 | [item setEnabled:YES]; |
192 | [item setSubmenu:child]; |
193 | [parent addItem:item]; |
194 | return child; |
195 | } |
196 | |
197 | id initnewitem(NSMenuItem *item, NSMenu *parent, const char *title, |
198 | const char *key, id target, SEL action) |
199 | { |
200 | unsigned mask = NSCommandKeyMask; |
201 | |
202 | if (key[strcspn(key, "-")]) { |
203 | while (*key && *key != '-') { |
204 | int c = tolower((unsigned char)*key); |
205 | if (c == 's') { |
206 | mask |= NSShiftKeyMask; |
207 | } else if (c == 'o' || c == 'a') { |
208 | mask |= NSAlternateKeyMask; |
209 | } |
210 | key++; |
211 | } |
212 | if (*key) |
213 | key++; |
214 | } |
215 | |
216 | item = [[item initWithTitle:[NSString stringWithCString:title] |
217 | action:NULL |
218 | keyEquivalent:[NSString stringWithCString:key]] |
219 | autorelease]; |
220 | |
221 | if (*key) |
222 | [item setKeyEquivalentModifierMask: mask]; |
223 | |
224 | [item setEnabled:YES]; |
225 | [item setTarget:target]; |
226 | [item setAction:action]; |
227 | |
228 | [parent addItem:item]; |
229 | |
230 | return item; |
231 | } |
232 | |
233 | NSMenuItem *newitem(NSMenu *parent, char *title, char *key, |
234 | id target, SEL action) |
235 | { |
236 | return initnewitem([NSMenuItem allocWithZone:[NSMenu menuZone]], |
237 | parent, title, key, target, action); |
238 | } |
239 | |
240 | /* ---------------------------------------------------------------------- |
241 | * AppController: the object which receives the messages from all |
242 | * menu selections that aren't standard OS X functions. |
243 | */ |
244 | @implementation AppController |
245 | |
246 | - (id)init |
247 | { |
248 | self = [super init]; |
249 | timer = NULL; |
250 | return self; |
251 | } |
252 | |
253 | - (void)newTerminal:(id)sender |
254 | { |
255 | id win; |
256 | Config cfg; |
257 | |
258 | do_defaults(NULL, &cfg); |
259 | |
260 | cfg.protocol = -1; /* PROT_TERMINAL */ |
261 | |
262 | win = [[SessionWindow alloc] initWithConfig:cfg]; |
263 | [win makeKeyAndOrderFront:self]; |
264 | } |
265 | |
266 | - (void)newSessionConfig:(id)sender |
267 | { |
268 | id win; |
269 | Config cfg; |
270 | |
271 | do_defaults(NULL, &cfg); |
272 | |
273 | win = [[ConfigWindow alloc] initWithConfig:cfg]; |
274 | [win makeKeyAndOrderFront:self]; |
275 | } |
276 | |
277 | - (void)newSessionWithConfig:(id)vdata |
278 | { |
279 | id win; |
280 | Config cfg; |
281 | NSData *data = (NSData *)vdata; |
282 | |
283 | assert([data length] == sizeof(cfg)); |
284 | [data getBytes:&cfg]; |
285 | |
286 | win = [[SessionWindow alloc] initWithConfig:cfg]; |
287 | [win makeKeyAndOrderFront:self]; |
288 | } |
289 | |
290 | - (NSMenu *)applicationDockMenu:(NSApplication *)sender |
291 | { |
292 | NSMenu *menu = newmenu("Dock Menu"); |
293 | /* |
294 | * FIXME: Add some useful things to this, probably including |
295 | * the saved session list. |
296 | */ |
297 | return menu; |
298 | } |
299 | |
300 | - (void)timerFired:(id)sender |
301 | { |
302 | long now, next; |
303 | |
304 | assert(sender == timer); |
305 | |
306 | /* `sender' is the timer itself, so its userInfo is an NSNumber. */ |
307 | now = [(NSNumber *)[sender userInfo] longValue]; |
308 | |
309 | [sender invalidate]; |
310 | |
311 | timer = NULL; |
312 | |
313 | if (run_timers(now, &next)) |
314 | [self setTimer:next]; |
315 | } |
316 | |
317 | - (void)setTimer:(long)next |
318 | { |
319 | long interval = next - GETTICKCOUNT(); |
320 | float finterval; |
321 | |
322 | if (interval <= 0) |
323 | interval = 1; /* just in case */ |
324 | |
325 | finterval = interval / (float)TICKSPERSEC; |
326 | |
327 | if (timer) { |
328 | [timer invalidate]; |
329 | } |
330 | |
331 | timer = [NSTimer scheduledTimerWithTimeInterval:finterval |
332 | target:self selector:@selector(timerFired:) |
333 | userInfo:[NSNumber numberWithLong:next] repeats:NO]; |
334 | } |
335 | |
336 | @end |
337 | |
338 | void timer_change_notify(long next) |
339 | { |
340 | [controller setTimer:next]; |
341 | } |
342 | |
343 | /* ---------------------------------------------------------------------- |
344 | * Annoyingly, it looks as if I have to actually subclass |
345 | * NSApplication if I want to catch NSApplicationDefined events. So |
346 | * here goes. |
347 | */ |
348 | @interface MyApplication : NSApplication |
349 | { |
350 | } |
351 | @end |
352 | @implementation MyApplication |
353 | - (void)sendEvent:(NSEvent *)ev |
354 | { |
355 | if ([ev type] == NSApplicationDefined) |
356 | osxsel_process_results(); |
357 | |
358 | [super sendEvent:ev]; |
359 | } |
360 | @end |
361 | |
362 | /* ---------------------------------------------------------------------- |
363 | * Main program. Constructs the menus and runs the application. |
364 | */ |
365 | int main(int argc, char **argv) |
366 | { |
367 | NSAutoreleasePool *pool; |
368 | NSMenu *menu; |
369 | NSMenuItem *item; |
370 | NSImage *icon; |
371 | |
372 | pool = [[NSAutoreleasePool alloc] init]; |
373 | |
374 | icon = [NSImage imageNamed:@"NSApplicationIcon"]; |
375 | [MyApplication sharedApplication]; |
376 | [NSApp setApplicationIconImage:icon]; |
377 | |
378 | controller = [[[AppController alloc] init] autorelease]; |
379 | [NSApp setDelegate:controller]; |
380 | |
381 | [NSApp setMainMenu: newmenu("Main Menu")]; |
382 | |
383 | menu = newsubmenu([NSApp mainMenu], "Apple Menu"); |
384 | [NSApp setServicesMenu:newsubmenu(menu, "Services")]; |
385 | [menu addItem:[NSMenuItem separatorItem]]; |
386 | item = newitem(menu, "Hide PuTTY", "h", NSApp, @selector(hide:)); |
387 | item = newitem(menu, "Hide Others", "o-h", NSApp, @selector(hideOtherApplications:)); |
388 | item = newitem(menu, "Show All", "", NSApp, @selector(unhideAllApplications:)); |
389 | [menu addItem:[NSMenuItem separatorItem]]; |
390 | item = newitem(menu, "Quit", "q", NSApp, @selector(terminate:)); |
391 | [NSApp setAppleMenu: menu]; |
392 | |
393 | menu = newsubmenu([NSApp mainMenu], "File"); |
394 | item = newitem(menu, "New", "n", NULL, @selector(newSessionConfig:)); |
395 | item = newitem(menu, "New Terminal", "t", NULL, @selector(newTerminal:)); |
396 | item = newitem(menu, "Close", "w", NULL, @selector(performClose:)); |
397 | |
398 | menu = newsubmenu([NSApp mainMenu], "Window"); |
399 | [NSApp setWindowsMenu: menu]; |
400 | item = newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:)); |
401 | |
402 | // menu = newsubmenu([NSApp mainMenu], "Help"); |
403 | // item = newitem(menu, "PuTTY Help", "?", NSApp, @selector(showHelp:)); |
404 | |
405 | /* |
406 | * Start up the sub-thread doing select(). |
407 | */ |
408 | osxsel_init(); |
409 | |
410 | /* |
411 | * Start up networking. |
412 | */ |
413 | sk_init(); |
414 | |
415 | /* |
416 | * FIXME: To make initial debugging more convenient I'm going |
417 | * to start by opening a session window unconditionally. This |
418 | * will probably change later on. |
419 | */ |
420 | [controller newSessionConfig:nil]; |
421 | |
422 | [NSApp run]; |
423 | [pool release]; |
424 | |
425 | return 0; |
426 | } |