Support for Windows PuTTY connecting straight to a local serial port
[u/mdw/putty] / macosx / osxmain.m
CommitLineData
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
16AppController *controller;
17
18/* ----------------------------------------------------------------------
19 * Miscellaneous elements of the interface to the cross-platform
20 * and Unix PuTTY code.
21 */
22
23const char platform_x11_best_transport[] = "unix";
24
25char *platform_get_x_display(void) {
26 return NULL;
27}
28
29FontSpec platform_default_fontspec(const char *name)
30{
31 FontSpec ret;
32 /* FIXME */
33 return ret;
34}
35
36Filename 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
46char *platform_default_s(const char *name)
47{
48 return NULL;
49}
50
51int 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
58char *x_get_default(const char *key)
59{
60 return NULL; /* this is a stub */
61}
62
ec5da310 63static 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
90void 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 98void 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
106void 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 */
120void 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
157NSMenu *newmenu(const char *title)
158{
159 return [[[NSMenu allocWithZone:[NSMenu menuZone]]
160 initWithTitle:[NSString stringWithCString:title]]
161 autorelease];
162}
163
164NSMenu *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
181id 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
217NSMenuItem *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
322void 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 */
349int 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}