Initial checkin of a native Mac OS X port, sharing most of its code
[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
63void 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
75void 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
87void 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 */
101void 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
138NSMenu *newmenu(const char *title)
139{
140 return [[[NSMenu allocWithZone:[NSMenu menuZone]]
141 initWithTitle:[NSString stringWithCString:title]]
142 autorelease];
143}
144
145NSMenu *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
162id 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
198NSMenuItem *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
303void 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 */
330int 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}