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