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