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