/*
 * osxwin.m: code to manage a session window in Mac OS X PuTTY.
 */

#import <Cocoa/Cocoa.h>
#include "putty.h"
#include "terminal.h"
#include "osxclass.h"

/* Colours come in two flavours: configurable, and xterm-extended. */
#define NCFGCOLOURS (lenof(((Config *)0)->colours))
#define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */
#define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS)

/*
 * The key component of the per-session data is the SessionWindow
 * class. A pointer to this is used as the frontend handle, to be
 * passed to all the platform-independent subsystems that require
 * one.
 */

@interface TerminalView : NSImageView
{
    NSFont *font;
    NSImage *image;
    Terminal *term;
    Config cfg;
    NSColor *colours[NALLCOLOURS];
    float fw, fasc, fdesc, fh;
}
- (void)drawStartFinish:(BOOL)start;
- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b;
- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
    attr:(unsigned long)attr lattr:(int)lattr;
@end

@implementation TerminalView
- (BOOL)isFlipped
{
    return YES;
}
- (id)initWithTerminal:(Terminal *)aTerm config:(Config)aCfg
{
    float w, h;

    self = [self initWithFrame:NSMakeRect(0,0,100,100)];

    term = aTerm;
    cfg = aCfg;

    /*
     * Initialise the fonts we're going to use.
     * 
     * FIXME: for the moment I'm sticking with exactly one default font.
     */
    font = [NSFont userFixedPitchFontOfSize:0];

    /*
     * Now determine the size of the primary font.
     * 
     * FIXME: If we have multiple fonts, we may need to set fasc
     * and fdesc to the _maximum_ asc and desc out of all the
     * fonts, _before_ adding them together to get fh.
     */
    fw = [font widthOfString:@"A"];
    fasc = [font ascender];
    fdesc = -[font descender];
    fh = fasc + fdesc;
    fh = (int)fh + (fh > (int)fh);     /* round up, ickily */

    /*
     * Use this to figure out the size of the terminal view.
     */
    w = fw * term->cols;
    h = fh * term->rows;

    /*
     * And set our size and subimage.
     */
    image = [[NSImage alloc] initWithSize:NSMakeSize(w,h)];
    [image setFlipped:YES];
    [self setImage:image];
    [self setFrame:NSMakeRect(0,0,w,h)];

    term_invalidate(term);

    return self;
}
- (void)drawStartFinish:(BOOL)start
{
    if (start)
	[image lockFocus];
    else
	[image unlockFocus];
}
- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
    attr:(unsigned long)attr lattr:(int)lattr
{
    int nfg, nbg, rlen, widefactor;
    float ox, oy, tw, th;
    NSDictionary *attrdict;

    /* FIXME: TATTR_COMBINING */

    nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
    nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
    if (attr & ATTR_REVERSE) {
	int t = nfg;
	nfg = nbg;
	nbg = t;
    }
    if (cfg.bold_colour && (attr & ATTR_BOLD)) {
	if (nfg < 16) nfg |= 8;
	else if (nfg >= 256) nfg |= 1;
    }
    if (cfg.bold_colour && (attr & ATTR_BLINK)) {
	if (nbg < 16) nbg |= 8;
	else if (nbg >= 256) nbg |= 1;
    }
    if (attr & TATTR_ACTCURS) {
	nfg = 260;
	nbg = 261;
    }

    if (attr & ATTR_WIDE) {
	widefactor = 2;
	/* FIXME: what do we actually have to do about wide characters? */
    } else {
	widefactor = 1;
    }

    /* FIXME: ATTR_BOLD without cfg.bold_colour */

    if ((lattr & LATTR_MODE) != LATTR_NORM) {
	x *= 2;
	if (x >= term->cols)
	    return;
	if (x + len*2*widefactor > term->cols)
	    len = (term->cols-x)/2/widefactor;/* trim to LH half */
	rlen = len * 2;
    } else
	rlen = len;

    /* FIXME: how do we actually implement double-{width,height} lattrs? */

    ox = x * fw;
    oy = y * fh;
    tw = rlen * widefactor * fw;
    th = fh;

    /*
     * Set the clipping rectangle.
     */
    [[NSGraphicsContext currentContext] saveGraphicsState];
    [NSBezierPath clipRect:NSMakeRect(ox, oy, tw, th)];

    attrdict = [NSDictionary dictionaryWithObjectsAndKeys:
		colours[nfg], NSForegroundColorAttributeName,
		colours[nbg], NSBackgroundColorAttributeName,
		font, NSFontAttributeName, nil];

    /*
     * Create an NSString and draw it.
     * 
     * Annoyingly, although our input is wchar_t which is four
     * bytes wide on OS X and terminal.c supports 32-bit Unicode,
     * we must convert into the two-byte type `unichar' to store in
     * NSString, so we lose display capability for extra-BMP stuff
     * at this point.
     */
    {
	NSString *string;
	unichar *utext;
	int i;

	utext = snewn(len, unichar);
	for (i = 0; i < len; i++)
	    utext[i] = (text[i] >= 0x10000 ? 0xFFFD : text[i]);

	string = [NSString stringWithCharacters:utext length:len];
	[string drawAtPoint:NSMakePoint(ox, oy) withAttributes:attrdict];

	sfree(utext);
    }

    /*
     * Restore the graphics state from before the clipRect: call.
     */
    [[NSGraphicsContext currentContext] restoreGraphicsState];

    /*
     * And flag this area as needing display.
     */
    [self setNeedsDisplayInRect:NSMakeRect(ox, oy, tw, th)];
}

- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
{
    assert(n >= 0 && n < lenof(colours));
    colours[n] = [[NSColor colorWithDeviceRed:r green:g blue:b alpha:1.0]
		  retain];
}
@end

@implementation SessionWindow
- (id)initWithConfig:(Config)aCfg
{
    NSRect rect = { {0,0}, {0,0} };

    alert_ctx = NULL;

    cfg = aCfg;			       /* structure copy */

    init_ucs(&ucsdata, cfg.line_codepage, cfg.utf8_override,
	     CS_UTF8, cfg.vtmode);
    term = term_init(&cfg, &ucsdata, self);
    logctx = log_init(self, &cfg);
    term_provide_logctx(term, logctx);
    term_size(term, cfg.height, cfg.width, cfg.savelines);

    termview = [[[TerminalView alloc] initWithTerminal:term config:cfg]
		autorelease];

    /*
     * Now work out the size of the window.
     */
    rect = [termview frame];
    rect.origin = NSMakePoint(0,0);
    rect.size.width += 2 * cfg.window_border;
    rect.size.height += 2 * cfg.window_border;

    /*
     * Set up a backend.
     */
    back = backend_from_proto(cfg.protocol);
    if (!back)
	back = &pty_backend;

    {
	const char *error;
	char *realhost = NULL;
	error = back->init(self, &backhandle, &cfg, cfg.host, cfg.port,
			   &realhost, cfg.tcp_nodelay, cfg.tcp_keepalives);
	if (error) {
	    fatalbox("%s\n", error);   /* FIXME: connection_fatal at worst */
	}

	if (realhost)
	    sfree(realhost);	       /* FIXME: do something with this */
    }
    back->provide_logctx(backhandle, logctx);

    /*
     * Create a line discipline. (This must be done after creating
     * the terminal _and_ the backend, since it needs to be passed
     * pointers to both.)
     */
    ldisc = ldisc_create(&cfg, term, back, backhandle, self);

    /*
     * FIXME: Set up a scrollbar.
     */

    self = [super initWithContentRect:rect
	    styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
		       NSClosableWindowMask)
	    backing:NSBackingStoreBuffered
	    defer:YES];
    [self setTitle:@"PuTTY"];

    [self setIgnoresMouseEvents:NO];

    /*
     * Put the terminal view in the window.
     */
    rect = [termview frame];
    rect.origin = NSMakePoint(cfg.window_border, cfg.window_border);
    [termview setFrame:rect];
    [[self contentView] addSubview:termview];

    /*
     * Set up the colour palette.
     */
    palette_reset(self);

    /*
     * FIXME: Only the _first_ document window should be centred.
     * The subsequent ones should appear down and to the right of
     * it, probably using the cascade function provided by Cocoa.
     * Also we're apparently required by the HIG to remember and
     * reuse previous positions of windows, although I'm not sure
     * how that works if the user opens more than one of the same
     * session type.
     */
    [self center];		       /* :-) */

    exited = FALSE;

    return self;
}

- (void)dealloc
{
    /*
     * FIXME: Here we must deallocate all sorts of stuff: the
     * terminal, the backend, the ldisc, the logctx, you name it.
     * Do so.
     */
    sfree(alert_ctx);
    if (back)
	back->free(backhandle);
    if (ldisc)
	ldisc_free(ldisc);
    /* ldisc must be freed before term, since ldisc_free expects term
     * still to be around. */
    if (logctx)
	log_free(logctx);
    if (term)
	term_free(term);
    [super dealloc];
}

- (void)drawStartFinish:(BOOL)start
{
    [termview drawStartFinish:start];
}

- (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
{
    [termview setColour:n r:r g:g b:b];
}

- (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
    attr:(unsigned long)attr lattr:(int)lattr
{
    /* Pass this straight on to the TerminalView. */
    [termview doText:text len:len x:x y:y attr:attr lattr:lattr];
}

- (Config *)cfg
{
    return &cfg;
}

- (void)keyDown:(NSEvent *)ev
{
    NSString *s = [ev characters];
    int i;
    int n = [s length], c = [s characterAtIndex:0], m = [ev modifierFlags];
    int cm = [[ev charactersIgnoringModifiers] characterAtIndex:0];
    wchar_t output[32];
    char coutput[32];
    int use_coutput = FALSE, special = FALSE, start, end;

//printf("n=%d c=U+%04x cm=U+%04x m=%08x\n", n, c, cm, m);

    /*
     * FIXME: Alt+numberpad codes.
     */

    /*
     * Shift and Ctrl with PageUp/PageDown for scrollback.
     */
    if (n == 1 && c == NSPageUpFunctionKey && (m & NSShiftKeyMask)) {
	term_scroll(term, 0, -term->rows/2);
	return;
    }
    if (n == 1 && c == NSPageUpFunctionKey && (m & NSControlKeyMask)) {
	term_scroll(term, 0, -1);
	return;
    }
    if (n == 1 && c == NSPageDownFunctionKey && (m & NSShiftKeyMask)) {
	term_scroll(term, 0, +term->rows/2);
	return;
    }
    if (n == 1 && c == NSPageDownFunctionKey && (m & NSControlKeyMask)) {
	term_scroll(term, 0, +1);
	return;
    }

    /*
     * FIXME: Shift-Ins for paste? Or is that not Maccy enough?
     */

    /*
     * FIXME: Alt (Option? Command?) prefix in general.
     * 
     * (Note that Alt-Shift-thing will work just by looking at
     * charactersIgnoringModifiers; but Alt-Ctrl-thing will need
     * processing properly, and Alt-as-in-Option won't happen at
     * all. Hmmm.)
     * 
     * (Note also that we need to be able to override menu key
     * equivalents before this is particularly useful.)
     */
    start = 1;
    end = start;

    /*
     * Ctrl-` is the same as Ctrl-\, unless we already have a
     * better idea.
     */
    if ((m & NSControlKeyMask) && n == 1 && cm == '`' && c == '`') {
	output[1] = '\x1c';
	end = 2;
    }

    /* We handle Return ourselves, because it needs to be flagged as
     * special to ldisc. */
    if (n == 1 && c == '\015') {
	coutput[1] = '\015';
	use_coutput = TRUE;
	end = 2;
	special = TRUE;
    }

    /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
    if (n == 1 && (m & NSControlKeyMask) && (m & NSShiftKeyMask) &&
	cm == ' ') {
	output[1] = '\240';
	end = 2;
    }

    /* Control-2, Control-Space and Control-@ are all NUL. */
    if ((m & NSControlKeyMask) && n == 1 &&
	(cm == '2' || cm == '@' || cm == ' ') && c == cm) {
	output[1] = '\0';
	end = 2;
    }

    /* We don't let MacOS tell us what Backspace is! We know better. */
    if (cm == 0x7F && !(m & NSShiftKeyMask)) {
	coutput[1] = cfg.bksp_is_delete ? '\x7F' : '\x08';
	end = 2;
	use_coutput = special = TRUE;
    }
    /* For Shift Backspace, do opposite of what is configured. */
    if (cm == 0x7F && (m & NSShiftKeyMask)) {
	coutput[1] = cfg.bksp_is_delete ? '\x08' : '\x7F';
	end = 2;
	use_coutput = special = TRUE;
    }

    /* Shift-Tab is ESC [ Z. Oddly, this combination generates ^Y by
     * default on MacOS! */
    if (cm == 0x19 && (m & NSShiftKeyMask) && !(m & NSControlKeyMask)) {
	end = 1;
	output[end++] = '\033';
	output[end++] = '[';
	output[end++] = 'Z';
    }

    /*
     * NetHack keypad mode.
     */
    if (cfg.nethack_keypad && (m & NSNumericPadKeyMask)) {
	wchar_t *keys = NULL;
	switch (cm) {
	  case '1': keys = L"bB"; break;
	  case '2': keys = L"jJ"; break;
	  case '3': keys = L"nN"; break;
	  case '4': keys = L"hH"; break;
	  case '5': keys = L".."; break;
	  case '6': keys = L"lL"; break;
	  case '7': keys = L"yY"; break;
	  case '8': keys = L"kK"; break;
	  case '9': keys = L"uU"; break;
	}
	if (keys) {
	    end = 2;
	    if (m & NSShiftKeyMask)
		output[1] = keys[1];
	    else
		output[1] = keys[0];
	    goto done;
	}
    }

    /*
     * Application keypad mode.
     */
    if (term->app_keypad_keys && !cfg.no_applic_k &&
	(m & NSNumericPadKeyMask)) {
	int xkey = 0;
	switch (cm) {
	  case NSClearLineFunctionKey: xkey = 'P'; break;
	  case '=': xkey = 'Q'; break;
	  case '/': xkey = 'R'; break;
	  case '*': xkey = 'S'; break;
	    /*
	     * FIXME: keypad - and + need to be mapped to ESC O l
	     * and ESC O k, or ESC O l and ESC O m, depending on
	     * xterm function key mode, and I can't remember which
	     * goes where.
	     */
	  case '\003': xkey = 'M'; break;
	  case '0': xkey = 'p'; break;
	  case '1': xkey = 'q'; break;
	  case '2': xkey = 'r'; break;
	  case '3': xkey = 's'; break;
	  case '4': xkey = 't'; break;
	  case '5': xkey = 'u'; break;
	  case '6': xkey = 'v'; break;
	  case '7': xkey = 'w'; break;
	  case '8': xkey = 'x'; break;
	  case '9': xkey = 'y'; break;
	  case '.': xkey = 'n'; break;
	}
	if (xkey) {
	    if (term->vt52_mode) {
		if (xkey >= 'P' && xkey <= 'S') {
		    output[end++] = '\033';
		    output[end++] = xkey;
		} else {
		    output[end++] = '\033';
		    output[end++] = '?';
		    output[end++] = xkey;
		}
	    } else {
		output[end++] = '\033';
		output[end++] = 'O';
		output[end++] = xkey;
	    }
	    goto done;
	}
    }

    /*
     * Next, all the keys that do tilde codes. (ESC '[' nn '~',
     * for integer decimal nn.)
     *
     * We also deal with the weird ones here. Linux VCs replace F1
     * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
     * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
     * respectively.
     */
    {
	int code = 0;
	switch (cm) {
	  case NSF1FunctionKey:
	    code = (m & NSShiftKeyMask ? 23 : 11);
	    break;
	  case NSF2FunctionKey:
	    code = (m & NSShiftKeyMask ? 24 : 12);
	    break;
	  case NSF3FunctionKey:
	    code = (m & NSShiftKeyMask ? 25 : 13);
	    break;
	  case NSF4FunctionKey:
	    code = (m & NSShiftKeyMask ? 26 : 14);
	    break;
	  case NSF5FunctionKey:
	    code = (m & NSShiftKeyMask ? 28 : 15);
	    break;
	  case NSF6FunctionKey:
	    code = (m & NSShiftKeyMask ? 29 : 17);
	    break;
	  case NSF7FunctionKey:
	    code = (m & NSShiftKeyMask ? 31 : 18);
	    break;
	  case NSF8FunctionKey:
	    code = (m & NSShiftKeyMask ? 32 : 19);
	    break;
	  case NSF9FunctionKey:
	    code = (m & NSShiftKeyMask ? 33 : 20);
	    break;
	  case NSF10FunctionKey:
	    code = (m & NSShiftKeyMask ? 34 : 21);
	    break;
	  case NSF11FunctionKey:
	    code = 23;
	    break;
	  case NSF12FunctionKey:
	    code = 24;
	    break;
	  case NSF13FunctionKey:
	    code = 25;
	    break;
	  case NSF14FunctionKey:
	    code = 26;
	    break;
	  case NSF15FunctionKey:
	    code = 28;
	    break;
	  case NSF16FunctionKey:
	    code = 29;
	    break;
	  case NSF17FunctionKey:
	    code = 31;
	    break;
	  case NSF18FunctionKey:
	    code = 32;
	    break;
	  case NSF19FunctionKey:
	    code = 33;
	    break;
	  case NSF20FunctionKey:
	    code = 34;
	    break;
	}
	if (!(m & NSControlKeyMask)) switch (cm) {
	  case NSHomeFunctionKey:
	    code = 1;
	    break;
#ifdef FIXME
	  case GDK_Insert: case GDK_KP_Insert:
	    code = 2;
	    break;
#endif
	  case NSDeleteFunctionKey:
	    code = 3;
	    break;
	  case NSEndFunctionKey:
	    code = 4;
	    break;
	  case NSPageUpFunctionKey:
	    code = 5;
	    break;
	  case NSPageDownFunctionKey:
	    code = 6;
	    break;
	}
	/* Reorder edit keys to physical order */
	if (cfg.funky_type == FUNKY_VT400 && code <= 6)
	    code = "\0\2\1\4\5\3\6"[code];

	if (term->vt52_mode && code > 0 && code <= 6) {
	    output[end++] = '\033';
	    output[end++] = " HLMEIG"[code];
	    goto done;
	}

	if (cfg.funky_type == FUNKY_SCO &&     /* SCO function keys */
	    code >= 11 && code <= 34) {
	    char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
	    int index = 0;
	    switch (cm) {
	      case NSF1FunctionKey: index = 0; break;
	      case NSF2FunctionKey: index = 1; break;
	      case NSF3FunctionKey: index = 2; break;
	      case NSF4FunctionKey: index = 3; break;
	      case NSF5FunctionKey: index = 4; break;
	      case NSF6FunctionKey: index = 5; break;
	      case NSF7FunctionKey: index = 6; break;
	      case NSF8FunctionKey: index = 7; break;
	      case NSF9FunctionKey: index = 8; break;
	      case NSF10FunctionKey: index = 9; break;
	      case NSF11FunctionKey: index = 10; break;
	      case NSF12FunctionKey: index = 11; break;
	    }
	    if (m & NSShiftKeyMask) index += 12;
	    if (m & NSControlKeyMask) index += 24;
	    output[end++] = '\033';
	    output[end++] = '[';
	    output[end++] = codes[index];
	    goto done;
	}
	if (cfg.funky_type == FUNKY_SCO &&     /* SCO small keypad */
	    code >= 1 && code <= 6) {
	    char codes[] = "HL.FIG";
	    if (code == 3) {
		output[1] = '\x7F';
		end = 2;
	    } else {
		output[end++] = '\033';
		output[end++] = '[';
		output[end++] = codes[code-1];
	    }
	    goto done;
	}
	if ((term->vt52_mode || cfg.funky_type == FUNKY_VT100P) &&
	    code >= 11 && code <= 24) {
	    int offt = 0;
	    if (code > 15)
		offt++;
	    if (code > 21)
		offt++;
	    if (term->vt52_mode) {
		output[end++] = '\033';
		output[end++] = code + 'P' - 11 - offt;
	    } else {
		output[end++] = '\033';
		output[end++] = 'O';
		output[end++] = code + 'P' - 11 - offt;
	    }
	    goto done;
	}
	if (cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
	    output[end++] = '\033';
	    output[end++] = '[';
	    output[end++] = '[';	
	    output[end++] = code + 'A' - 11;
	    goto done;
	}
	if (cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {
	    if (term->vt52_mode) {
		output[end++] = '\033';
		output[end++] = code + 'P' - 11;
	    } else {
		output[end++] = '\033';
		output[end++] = 'O';
		output[end++] = code + 'P' - 11;
	    }
	    goto done;
	}
	if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
	    if (code == 1) {
		output[end++] = '\033';
		output[end++] = '[';
		output[end++] = 'H';
	    } else {
		output[end++] = '\033';
		output[end++] = 'O';
		output[end++] = 'w';
	    }
	    goto done;
	}
	if (code) {
	    char buf[20];
	    sprintf(buf, "\x1B[%d~", code);
	    for (i = 0; buf[i]; i++)
		output[end++] = buf[i];
	    goto done;
	}
    }

    /*
     * Cursor keys. (This includes the numberpad cursor keys,
     * if we haven't already done them due to app keypad mode.)
     */
    {
	int xkey = 0;
	switch (cm) {
	  case NSUpArrowFunctionKey: xkey = 'A'; break;
	  case NSDownArrowFunctionKey: xkey = 'B'; break;
	  case NSRightArrowFunctionKey: xkey = 'C'; break;
	  case NSLeftArrowFunctionKey: xkey = 'D'; break;
	}
	if (xkey) {
	    end += format_arrow_key(output+end, term, xkey,
				    m & NSControlKeyMask);
	    goto done;
	}
    }

    done:

    /*
     * Failing everything else, send the exact Unicode we got from
     * OS X.
     */
    if (end == start) {
	if (n > lenof(output)-start)
	    n = lenof(output)-start;   /* _shouldn't_ happen! */
	for (i = 0; i < n; i++) {
	    output[i+start] = [s characterAtIndex:i];
	}
	end = n+start;
    }

    if (use_coutput) {
	assert(special);
	assert(end < lenof(coutput));
	coutput[end] = '\0';
	ldisc_send(ldisc, coutput+start, -2, TRUE);
    } else {
	luni_send(ldisc, output+start, end-start, TRUE);
    }
}

- (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr
{
    return term_data(term, is_stderr, data, len);
}

- (int)fromBackendUntrusted:(const char *)data len:(int)len
{
    return term_data_untrusted(term, data, len);
}

- (void)startAlert:(NSAlert *)alert
    withCallback:(void (*)(void *, int))callback andCtx:(void *)ctx
{
    if (alert_ctx || alert_qhead) {
	/*
	 * Queue this alert to be shown later.
	 */
	struct alert_queue *qitem = snew(struct alert_queue);
	qitem->next = NULL;
	qitem->alert = alert;
	qitem->callback = callback;
	qitem->ctx = ctx;
	if (alert_qtail)
	    alert_qtail->next = qitem;
	else
	    alert_qhead = qitem;
	alert_qtail = qitem;
    } else {
	alert_callback = callback;
	alert_ctx = ctx;	       /* NB this is assumed to need freeing! */
	[alert beginSheetModalForWindow:self modalDelegate:self
	 didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:)
	 contextInfo:NULL];
    }
}

- (void)alertSheetDidEnd:(NSAlert *)alert returnCode:(int)returnCode
    contextInfo:(void *)contextInfo
{
    [self performSelectorOnMainThread:
     @selector(alertSheetDidFinishEnding:)
     withObject:[NSNumber numberWithInt:returnCode]
     waitUntilDone:NO];
}

- (void)alertSheetDidFinishEnding:(id)object
{
    int returnCode = [object intValue];

    alert_callback(alert_ctx, returnCode);   /* transfers ownership of ctx */

    /*
     * If there's an alert in our queue (either already or because
     * the callback just queued it), start it.
     */
    if (alert_qhead) {
	struct alert_queue *qnext;

	alert_callback = alert_qhead->callback;
	alert_ctx = alert_qhead->ctx;
	[alert_qhead->alert beginSheetModalForWindow:self modalDelegate:self
	 didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:)
	 contextInfo:NULL];

	qnext = alert_qhead->next;
	sfree(alert_qhead);
	alert_qhead = qnext;
	if (!qnext)
	    alert_qtail = NULL;
    } else {
	alert_ctx = NULL;
    }
}

- (void)notifyRemoteExit
{
    int exitcode;

    if (!exited && (exitcode = back->exitcode(backhandle)) >= 0)
	[self endSession:(exitcode == 0)];
}

- (void)endSession:(int)clean
{
    exited = TRUE;
    if (ldisc) {
	ldisc_free(ldisc);
	ldisc = NULL;
    }
    if (back) {
	back->free(backhandle);
	backhandle = NULL;
	back = NULL;
	//FIXME: update specials menu;
    }
    if (cfg.close_on_exit == FORCE_ON ||
	(cfg.close_on_exit == AUTO && clean))
	[self close];
    // FIXME: else show restart menu item
}

- (Terminal *)term
{
    return term;
}

@end

int from_backend(void *frontend, int is_stderr, const char *data, int len)
{
    SessionWindow *win = (SessionWindow *)frontend;
    return [win fromBackend:data len:len isStderr:is_stderr];
}

int from_backend_untrusted(void *frontend, const char *data, int len)
{
    SessionWindow *win = (SessionWindow *)frontend;
    return [win fromBackendUntrusted:data len:len];
}

int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
{
    SessionWindow *win = (SessionWindow *)p->frontend;
    Terminal *term = [win term];
    return term_get_userpass_input(term, p, in, inlen);
}

void frontend_keypress(void *handle)
{
    /* FIXME */
}

void notify_remote_exit(void *frontend)
{
    SessionWindow *win = (SessionWindow *)frontend;

    [win notifyRemoteExit];
}

void ldisc_update(void *frontend, int echo, int edit)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /*
     * In a GUI front end, this need do nothing.
     */
}

char *get_ttymode(void *frontend, const char *mode)
{
    SessionWindow *win = (SessionWindow *)frontend;
    Terminal *term = [win term];
    return term_get_ttymode(term, mode);
}

void update_specials_menu(void *frontend)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

/*
 * This is still called when mode==BELL_VISUAL, even though the
 * visual bell is handled entirely within terminal.c, because we
 * may want to perform additional actions on any kind of bell (for
 * example, taskbar flashing in Windows).
 */
void do_beep(void *frontend, int mode)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    if (mode != BELL_VISUAL)
	NSBeep();
}

int char_width(Context ctx, int uc)
{
    /*
     * Under X, any fixed-width font really _is_ fixed-width.
     * Double-width characters will be dealt with using a separate
     * font. For the moment we can simply return 1.
     */
    return 1;
}

void palette_set(void *frontend, int n, int r, int g, int b)
{
    SessionWindow *win = (SessionWindow *)frontend;

    if (n >= 16)
	n += 256 - 16;
    if (n > NALLCOLOURS)
	return;
    [win setColour:n r:r/255.0 g:g/255.0 b:b/255.0];

    /*
     * FIXME: do we need an OS X equivalent of set_window_background?
     */
}

void palette_reset(void *frontend)
{
    SessionWindow *win = (SessionWindow *)frontend;
    Config *cfg = [win cfg];

    /* This maps colour indices in cfg to those used in colours[]. */
    static const int ww[] = {
	256, 257, 258, 259, 260, 261,
	0, 8, 1, 9, 2, 10, 3, 11,
	4, 12, 5, 13, 6, 14, 7, 15
    };

    int i;

    for (i = 0; i < NCFGCOLOURS; i++) {
	[win setColour:ww[i] r:cfg->colours[i][0]/255.0
	 g:cfg->colours[i][1]/255.0 b:cfg->colours[i][2]/255.0];
    }

    for (i = 0; i < NEXTCOLOURS; i++) {
	if (i < 216) {
	    int r = i / 36, g = (i / 6) % 6, b = i % 6;
	    r = r ? r*40+55 : 0; g = g ? b*40+55 : 0; b = b ? b*40+55 : 0;
	    [win setColour:i+16 r:r/255.0 g:g/255.0 b:b/255.0];
	} else {
	    int shade = i - 216;
	    float fshade = (shade * 10 + 8) / 255.0;
	    [win setColour:i+16 r:fshade g:fshade b:fshade];
	}
    }

    /*
     * FIXME: do we need an OS X equivalent of set_window_background?
     */
}

Context get_ctx(void *frontend)
{
    SessionWindow *win = (SessionWindow *)frontend;

    /*
     * Lock the drawing focus on the image inside the TerminalView.
     */
    [win drawStartFinish:YES];

    [[NSGraphicsContext currentContext] setShouldAntialias:YES];

    /*
     * Cocoa drawing functions don't take a graphics context: that
     * parameter is implicit. Therefore, we'll use the frontend
     * handle itself as the context, on the grounds that it's as
     * good a thing to use as any.
     */
    return frontend;
}

void free_ctx(Context ctx)
{
    SessionWindow *win = (SessionWindow *)ctx;

    [win drawStartFinish:NO];
}

void do_text(Context ctx, int x, int y, wchar_t *text, int len,
	     unsigned long attr, int lattr)
{
    SessionWindow *win = (SessionWindow *)ctx;

    [win doText:text len:len x:x y:y attr:attr lattr:lattr];
}

void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
	       unsigned long attr, int lattr)
{
    SessionWindow *win = (SessionWindow *)ctx;
    Config *cfg = [win cfg];
    int active, passive;

    if (attr & TATTR_PASCURS) {
	attr &= ~TATTR_PASCURS;
	passive = 1;
    } else
	passive = 0;
    if ((attr & TATTR_ACTCURS) && cfg->cursor_type != 0) {
	attr &= ~TATTR_ACTCURS;
        active = 1;
    } else
        active = 0;

    [win doText:text len:len x:x y:y attr:attr lattr:lattr];

    /*
     * FIXME: now draw the various cursor types (both passive and
     * active underlines and vertical lines, plus passive blocks).
     */
}

/*
 * Minimise or restore the window in response to a server-side
 * request.
 */
void set_iconic(void *frontend, int iconic)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

/*
 * Move the window in response to a server-side request.
 */
void move_window(void *frontend, int x, int y)
{
    //SessionWindow *win = (SessionWindow *)frontend; 
    /* FIXME */
}

/*
 * Move the window to the top or bottom of the z-order in response
 * to a server-side request.
 */
void set_zorder(void *frontend, int top)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

/*
 * Refresh the window in response to a server-side request.
 */
void refresh_window(void *frontend)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

/*
 * Maximise or restore the window in response to a server-side
 * request.
 */
void set_zoomed(void *frontend, int zoomed)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

/*
 * Report whether the window is iconic, for terminal reports.
 */
int is_iconic(void *frontend)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    return NO; 			       /* FIXME */
}

/*
 * Report the window's position, for terminal reports.
 */
void get_window_pos(void *frontend, int *x, int *y)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

/*
 * Report the window's pixel size, for terminal reports.
 */
void get_window_pixels(void *frontend, int *x, int *y)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

/*
 * Return the window or icon title.
 */
char *get_window_title(void *frontend, int icon)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    return NULL; /* FIXME */
}

void set_title(void *frontend, char *title)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

void set_icon(void *frontend, char *title)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

void set_sbar(void *frontend, int total, int start, int page)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

void get_clip(void *frontend, wchar_t ** p, int *len)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

void write_clip(void *frontend, wchar_t *data, int *attr, int len, int must_deselect)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

void request_paste(void *frontend)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

void set_raw_mouse_mode(void *frontend, int activate)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

void request_resize(void *frontend, int w, int h)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
}

void sys_cursor(void *frontend, int x, int y)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /*
     * This is probably meaningless under OS X. FIXME: find out for
     * sure.
     */
}

void logevent(void *frontend, const char *string)
{
    //SessionWindow *win = (SessionWindow *)frontend;
    /* FIXME */
printf("logevent: %s\n", string);
}

int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */
{
    //SessionWindow *win = (SessionWindow *)frontend;
    return 1; /* FIXME */
}

void set_busy_status(void *frontend, int status)
{
    /*
     * We need do nothing here: the OS X `application is busy'
     * beachball pointer appears _automatically_ when the
     * application isn't responding to GUI messages.
     */
}
