]>
Commit | Line | Data |
---|---|---|
1c1af145 | 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 | 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 | ||
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, ...) | |
89 | { | |
90 | va_list ap; | |
91 | va_start(ap, p); | |
92 | commonfatalbox(p, ap); | |
93 | va_end(ap); | |
94 | } | |
95 | ||
96 | void modalfatalbox(char *p, ...) | |
97 | { | |
98 | va_list ap; | |
99 | va_start(ap, p); | |
100 | commonfatalbox(p, ap); | |
101 | va_end(ap); | |
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 | } |