Initial checkin of a native Mac OS X port, sharing most of its code
[sgt/putty] / macosx / osxmain.m
diff --git a/macosx/osxmain.m b/macosx/osxmain.m
new file mode 100644 (file)
index 0000000..bd57985
--- /dev/null
@@ -0,0 +1,391 @@
+/*
+ * osxmain.m: main-program file of Mac OS X PuTTY.
+ */
+
+#import <Cocoa/Cocoa.h>
+
+#define PUTTY_DO_GLOBALS              /* actually _define_ globals */
+
+#include "putty.h"
+#include "osxclass.h"
+
+/* ----------------------------------------------------------------------
+ * Global variables.
+ */
+
+AppController *controller;
+
+/* ----------------------------------------------------------------------
+ * Miscellaneous elements of the interface to the cross-platform
+ * and Unix PuTTY code.
+ */
+
+const char platform_x11_best_transport[] = "unix";
+
+char *platform_get_x_display(void) {
+    return NULL;
+}
+
+FontSpec platform_default_fontspec(const char *name)
+{
+    FontSpec ret;
+    /* FIXME */
+    return ret;
+}
+
+Filename platform_default_filename(const char *name)
+{
+    Filename ret;
+    if (!strcmp(name, "LogFileName"))
+       strcpy(ret.path, "putty.log");
+    else
+       *ret.path = '\0';
+    return ret;
+}
+
+char *platform_default_s(const char *name)
+{
+    return NULL;
+}
+
+int platform_default_i(const char *name, int def)
+{
+    if (!strcmp(name, "CloseOnExit"))
+       return 2;  /* maps to FORCE_ON after painful rearrangement :-( */
+    return def;
+}
+
+char *x_get_default(const char *key)
+{
+    return NULL;                      /* this is a stub */
+}
+
+void modalfatalbox(char *p, ...)
+{
+    /* FIXME: proper OS X GUI stuff */
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+void fatalbox(char *p, ...)
+{
+    /* FIXME: proper OS X GUI stuff */
+    va_list ap;
+    fprintf(stderr, "FATAL ERROR: ");
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+void cmdline_error(char *p, ...)
+{
+    va_list ap;
+    fprintf(stderr, "%s: ", appname);
+    va_start(ap, p);
+    vfprintf(stderr, p, ap);
+    va_end(ap);
+    fputc('\n', stderr);
+    exit(1);
+}
+
+/*
+ * Clean up and exit.
+ */
+void cleanup_exit(int code)
+{
+    /*
+     * Clean up.
+     */
+    sk_cleanup();
+    random_save_seed();
+    exit(code);
+}
+
+/* ----------------------------------------------------------------------
+ * Tiny extension to NSMenuItem which carries a payload of a `void
+ * *', allowing several menu items to invoke the same message but
+ * pass different data through it.
+ */
+@interface DataMenuItem : NSMenuItem
+{
+    void *payload;
+}
+- (void)setPayload:(void *)d;
+- (void *)getPayload;
+@end
+@implementation DataMenuItem
+- (void)setPayload:(void *)d
+{
+    payload = d;
+}
+- (void *)getPayload
+{
+    return payload;
+}
+@end
+
+/* ----------------------------------------------------------------------
+ * Utility routines for constructing OS X menus.
+ */
+
+NSMenu *newmenu(const char *title)
+{
+    return [[[NSMenu allocWithZone:[NSMenu menuZone]]
+            initWithTitle:[NSString stringWithCString:title]]
+           autorelease];
+}
+
+NSMenu *newsubmenu(NSMenu *parent, const char *title)
+{
+    NSMenuItem *item;
+    NSMenu *child;
+
+    item = [[[NSMenuItem allocWithZone:[NSMenu menuZone]]
+            initWithTitle:[NSString stringWithCString:title]
+            action:NULL
+            keyEquivalent:@""]
+           autorelease];
+    child = newmenu(title);
+    [item setEnabled:YES];
+    [item setSubmenu:child];
+    [parent addItem:item];
+    return child;
+}
+
+id initnewitem(NSMenuItem *item, NSMenu *parent, const char *title,
+              const char *key, id target, SEL action)
+{
+    unsigned mask = NSCommandKeyMask;
+
+    if (key[strcspn(key, "-")]) {
+       while (*key && *key != '-') {
+           int c = tolower((unsigned char)*key);
+           if (c == 's') {
+               mask |= NSShiftKeyMask;
+           } else if (c == 'o' || c == 'a') {
+               mask |= NSAlternateKeyMask;
+           }
+           key++;
+       }
+       if (*key)
+           key++;
+    }
+
+    item = [[item initWithTitle:[NSString stringWithCString:title]
+            action:NULL
+            keyEquivalent:[NSString stringWithCString:key]]
+           autorelease];
+
+    if (*key)
+       [item setKeyEquivalentModifierMask: mask];
+
+    [item setEnabled:YES];
+    [item setTarget:target];
+    [item setAction:action];
+
+    [parent addItem:item];
+
+    return item;
+}
+
+NSMenuItem *newitem(NSMenu *parent, char *title, char *key,
+                   id target, SEL action)
+{
+    return initnewitem([NSMenuItem allocWithZone:[NSMenu menuZone]],
+                      parent, title, key, target, action);
+}
+
+/* ----------------------------------------------------------------------
+ * AppController: the object which receives the messages from all
+ * menu selections that aren't standard OS X functions.
+ */
+@implementation AppController
+
+- (id)init
+{
+    self = [super init];
+    timer = NULL;
+    return self;
+}
+
+- (void)newTerminal:(id)sender
+{
+    id win;
+    Config cfg;
+
+    do_defaults(NULL, &cfg);
+
+    cfg.protocol = -1;                /* PROT_TERMINAL */
+
+    win = [[SessionWindow alloc] initWithConfig:cfg];
+    [win makeKeyAndOrderFront:self];
+}
+
+- (void)newSessionConfig:(id)sender
+{
+    id win;
+    Config cfg;
+
+    do_defaults(NULL, &cfg);
+
+    win = [[ConfigWindow alloc] initWithConfig:cfg];
+    [win makeKeyAndOrderFront:self];
+}
+
+- (void)newSessionWithConfig:(id)vdata
+{
+    id win;
+    Config cfg;
+    NSData *data = (NSData *)vdata;
+
+    assert([data length] == sizeof(cfg));
+    [data getBytes:&cfg];
+
+    win = [[SessionWindow alloc] initWithConfig:cfg];
+    [win makeKeyAndOrderFront:self];
+}
+
+- (NSMenu *)applicationDockMenu:(NSApplication *)sender
+{
+    NSMenu *menu = newmenu("Dock Menu");
+    /*
+     * FIXME: Add some useful things to this, probably including
+     * the saved session list.
+     */
+    return menu;
+}
+
+- (void)timerFired:(id)sender
+{
+    long now, next;
+
+    assert(sender == timer);
+
+    /* `sender' is the timer itself, so its userInfo is an NSNumber. */
+    now = [(NSNumber *)[sender userInfo] longValue];
+
+    [sender invalidate];
+
+    timer = NULL;
+
+    if (run_timers(now, &next))
+       [self setTimer:next];
+}
+
+- (void)setTimer:(long)next
+{
+    long interval = next - GETTICKCOUNT();
+    float finterval;
+
+    if (interval <= 0)
+       interval = 1;                  /* just in case */
+
+    finterval = interval / (float)TICKSPERSEC;
+
+    if (timer) {
+       [timer invalidate];
+    }
+
+    timer = [NSTimer scheduledTimerWithTimeInterval:finterval
+            target:self selector:@selector(timerFired:)
+            userInfo:[NSNumber numberWithLong:next] repeats:NO];
+}
+
+@end
+
+void timer_change_notify(long next)
+{
+    [controller setTimer:next];
+}
+
+/* ----------------------------------------------------------------------
+ * Annoyingly, it looks as if I have to actually subclass
+ * NSApplication if I want to catch NSApplicationDefined events. So
+ * here goes.
+ */
+@interface MyApplication : NSApplication
+{
+}
+@end
+@implementation MyApplication
+- (void)sendEvent:(NSEvent *)ev
+{
+    if ([ev type] == NSApplicationDefined)
+       osxsel_process_results();
+
+    [super sendEvent:ev];
+}    
+@end
+
+/* ----------------------------------------------------------------------
+ * Main program. Constructs the menus and runs the application.
+ */
+int main(int argc, char **argv)
+{
+    NSAutoreleasePool *pool;
+    NSMenu *menu;
+    NSMenuItem *item;
+    NSImage *icon;
+
+    pool = [[NSAutoreleasePool alloc] init];
+
+    icon = [NSImage imageNamed:@"NSApplicationIcon"];
+    [MyApplication sharedApplication];
+    [NSApp setApplicationIconImage:icon];
+
+    controller = [[[AppController alloc] init] autorelease];
+    [NSApp setDelegate:controller];
+
+    [NSApp setMainMenu: newmenu("Main Menu")];
+
+    menu = newsubmenu([NSApp mainMenu], "Apple Menu");
+    [NSApp setServicesMenu:newsubmenu(menu, "Services")];
+    [menu addItem:[NSMenuItem separatorItem]];
+    item = newitem(menu, "Hide PuTTY", "h", NSApp, @selector(hide:));
+    item = newitem(menu, "Hide Others", "o-h", NSApp, @selector(hideOtherApplications:));
+    item = newitem(menu, "Show All", "", NSApp, @selector(unhideAllApplications:));
+    [menu addItem:[NSMenuItem separatorItem]];
+    item = newitem(menu, "Quit", "q", NSApp, @selector(terminate:));
+    [NSApp setAppleMenu: menu];
+
+    menu = newsubmenu([NSApp mainMenu], "File");
+    item = newitem(menu, "New", "n", NULL, @selector(newSessionConfig:));
+    item = newitem(menu, "New Terminal", "t", NULL, @selector(newTerminal:));
+    item = newitem(menu, "Close", "w", NULL, @selector(performClose:));
+
+    menu = newsubmenu([NSApp mainMenu], "Window");
+    [NSApp setWindowsMenu: menu];
+    item = newitem(menu, "Minimise Window", "m", NULL, @selector(performMiniaturize:));
+
+//    menu = newsubmenu([NSApp mainMenu], "Help");
+//    item = newitem(menu, "PuTTY Help", "?", NSApp, @selector(showHelp:));
+
+    /*
+     * Start up the sub-thread doing select().
+     */
+    osxsel_init();
+
+    /*
+     * Start up networking.
+     */
+    sk_init();
+
+    /*
+     * FIXME: To make initial debugging more convenient I'm going
+     * to start by opening a session window unconditionally. This
+     * will probably change later on.
+     */
+    [controller newSessionConfig:nil];
+
+    [NSApp run];
+    [pool release];
+
+    return 0;
+}