]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - macosx/osxwin.m
Revamp interface to verify_ssh_host_key() and askalg(). Each of them
[PuTTY.git] / macosx / osxwin.m
1 /*
2  * osxwin.m: code to manage a session window in Mac OS X PuTTY.
3  */
4
5 #import <Cocoa/Cocoa.h>
6 #include "putty.h"
7 #include "terminal.h"
8 #include "osxclass.h"
9
10 /* Colours come in two flavours: configurable, and xterm-extended. */
11 #define NCFGCOLOURS (lenof(((Config *)0)->colours))
12 #define NEXTCOLOURS 240 /* 216 colour-cube plus 24 shades of grey */
13 #define NALLCOLOURS (NCFGCOLOURS + NEXTCOLOURS)
14
15 /*
16  * The key component of the per-session data is the SessionWindow
17  * class. A pointer to this is used as the frontend handle, to be
18  * passed to all the platform-independent subsystems that require
19  * one.
20  */
21
22 @interface TerminalView : NSImageView
23 {
24     NSFont *font;
25     NSImage *image;
26     Terminal *term;
27     Config cfg;
28     NSColor *colours[NALLCOLOURS];
29     float fw, fasc, fdesc, fh;
30 }
31 - (void)drawStartFinish:(BOOL)start;
32 - (void)setColour:(int)n r:(float)r g:(float)g b:(float)b;
33 - (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
34     attr:(unsigned long)attr lattr:(int)lattr;
35 @end
36
37 @implementation TerminalView
38 - (BOOL)isFlipped
39 {
40     return YES;
41 }
42 - (id)initWithTerminal:(Terminal *)aTerm config:(Config)aCfg
43 {
44     float w, h;
45
46     self = [self initWithFrame:NSMakeRect(0,0,100,100)];
47
48     term = aTerm;
49     cfg = aCfg;
50
51     /*
52      * Initialise the fonts we're going to use.
53      * 
54      * FIXME: for the moment I'm sticking with exactly one default font.
55      */
56     font = [NSFont userFixedPitchFontOfSize:0];
57
58     /*
59      * Now determine the size of the primary font.
60      * 
61      * FIXME: If we have multiple fonts, we may need to set fasc
62      * and fdesc to the _maximum_ asc and desc out of all the
63      * fonts, _before_ adding them together to get fh.
64      */
65     fw = [font widthOfString:@"A"];
66     fasc = [font ascender];
67     fdesc = -[font descender];
68     fh = fasc + fdesc;
69     fh = (int)fh + (fh > (int)fh);     /* round up, ickily */
70
71     /*
72      * Use this to figure out the size of the terminal view.
73      */
74     w = fw * term->cols;
75     h = fh * term->rows;
76
77     /*
78      * And set our size and subimage.
79      */
80     image = [[NSImage alloc] initWithSize:NSMakeSize(w,h)];
81     [image setFlipped:YES];
82     [self setImage:image];
83     [self setFrame:NSMakeRect(0,0,w,h)];
84
85     term_invalidate(term);
86
87     return self;
88 }
89 - (void)drawStartFinish:(BOOL)start
90 {
91     if (start)
92         [image lockFocus];
93     else
94         [image unlockFocus];
95 }
96 - (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
97     attr:(unsigned long)attr lattr:(int)lattr
98 {
99     int nfg, nbg, rlen, widefactor;
100     float ox, oy, tw, th;
101     NSDictionary *attrdict;
102
103     /* FIXME: TATTR_COMBINING */
104
105     nfg = ((attr & ATTR_FGMASK) >> ATTR_FGSHIFT);
106     nbg = ((attr & ATTR_BGMASK) >> ATTR_BGSHIFT);
107     if (attr & ATTR_REVERSE) {
108         int t = nfg;
109         nfg = nbg;
110         nbg = t;
111     }
112     if (cfg.bold_colour && (attr & ATTR_BOLD)) {
113         if (nfg < 16) nfg |= 8;
114         else if (nfg >= 256) nfg |= 1;
115     }
116     if (cfg.bold_colour && (attr & ATTR_BLINK)) {
117         if (nbg < 16) nbg |= 8;
118         else if (nbg >= 256) nbg |= 1;
119     }
120     if (attr & TATTR_ACTCURS) {
121         nfg = 260;
122         nbg = 261;
123     }
124
125     if (attr & ATTR_WIDE) {
126         widefactor = 2;
127         /* FIXME: what do we actually have to do about wide characters? */
128     } else {
129         widefactor = 1;
130     }
131
132     /* FIXME: ATTR_BOLD without cfg.bold_colour */
133
134     if ((lattr & LATTR_MODE) != LATTR_NORM) {
135         x *= 2;
136         if (x >= term->cols)
137             return;
138         if (x + len*2*widefactor > term->cols)
139             len = (term->cols-x)/2/widefactor;/* trim to LH half */
140         rlen = len * 2;
141     } else
142         rlen = len;
143
144     /* FIXME: how do we actually implement double-{width,height} lattrs? */
145
146     ox = x * fw;
147     oy = y * fh;
148     tw = rlen * widefactor * fw;
149     th = fh;
150
151     /*
152      * Set the clipping rectangle.
153      */
154     [[NSGraphicsContext currentContext] saveGraphicsState];
155     [NSBezierPath clipRect:NSMakeRect(ox, oy, tw, th)];
156
157     attrdict = [NSDictionary dictionaryWithObjectsAndKeys:
158                 colours[nfg], NSForegroundColorAttributeName,
159                 colours[nbg], NSBackgroundColorAttributeName,
160                 font, NSFontAttributeName, nil];
161
162     /*
163      * Create an NSString and draw it.
164      * 
165      * Annoyingly, although our input is wchar_t which is four
166      * bytes wide on OS X and terminal.c supports 32-bit Unicode,
167      * we must convert into the two-byte type `unichar' to store in
168      * NSString, so we lose display capability for extra-BMP stuff
169      * at this point.
170      */
171     {
172         NSString *string;
173         unichar *utext;
174         int i;
175
176         utext = snewn(len, unichar);
177         for (i = 0; i < len; i++)
178             utext[i] = (text[i] >= 0x10000 ? 0xFFFD : text[i]);
179
180         string = [NSString stringWithCharacters:utext length:len];
181         [string drawAtPoint:NSMakePoint(ox, oy) withAttributes:attrdict];
182
183         sfree(utext);
184     }
185
186     /*
187      * Restore the graphics state from before the clipRect: call.
188      */
189     [[NSGraphicsContext currentContext] restoreGraphicsState];
190
191     /*
192      * And flag this area as needing display.
193      */
194     [self setNeedsDisplayInRect:NSMakeRect(ox, oy, tw, th)];
195 }
196
197 - (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
198 {
199     assert(n >= 0 && n < lenof(colours));
200     colours[n] = [[NSColor colorWithDeviceRed:r green:g blue:b alpha:1.0]
201                   retain];
202 }
203 @end
204
205 @implementation SessionWindow
206 - (id)initWithConfig:(Config)aCfg
207 {
208     NSRect rect = { {0,0}, {0,0} };
209
210     alert_ctx = NULL;
211
212     cfg = aCfg;                        /* structure copy */
213
214     init_ucs(&ucsdata, cfg.line_codepage, cfg.utf8_override,
215              CS_UTF8, cfg.vtmode);
216     term = term_init(&cfg, &ucsdata, self);
217     logctx = log_init(self, &cfg);
218     term_provide_logctx(term, logctx);
219     term_size(term, cfg.height, cfg.width, cfg.savelines);
220
221     termview = [[[TerminalView alloc] initWithTerminal:term config:cfg]
222                 autorelease];
223
224     /*
225      * Now work out the size of the window.
226      */
227     rect = [termview frame];
228     rect.origin = NSMakePoint(0,0);
229     rect.size.width += 2 * cfg.window_border;
230     rect.size.height += 2 * cfg.window_border;
231
232     /*
233      * Set up a backend.
234      */
235     {
236         int i;
237         back = &pty_backend;
238         for (i = 0; backends[i].backend != NULL; i++)
239             if (backends[i].protocol == cfg.protocol) {
240                 back = backends[i].backend;
241                 break;
242             }
243     }
244
245     {
246         const char *error;
247         char *realhost = NULL;
248         error = back->init(self, &backhandle, &cfg, cfg.host, cfg.port,
249                            &realhost, cfg.tcp_nodelay, cfg.tcp_keepalives);
250         if (error) {
251             fatalbox("%s\n", error);   /* FIXME: connection_fatal at worst */
252         }
253
254         if (realhost)
255             sfree(realhost);           /* FIXME: do something with this */
256     }
257
258     /*
259      * Create a line discipline. (This must be done after creating
260      * the terminal _and_ the backend, since it needs to be passed
261      * pointers to both.)
262      */
263     ldisc = ldisc_create(&cfg, term, back, backhandle, self);
264
265     /*
266      * FIXME: Set up a scrollbar.
267      */
268
269     self = [super initWithContentRect:rect
270             styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
271                        NSClosableWindowMask)
272             backing:NSBackingStoreBuffered
273             defer:YES];
274     [self setTitle:@"PuTTY"];
275
276     [self setIgnoresMouseEvents:NO];
277
278     /*
279      * Put the terminal view in the window.
280      */
281     rect = [termview frame];
282     rect.origin = NSMakePoint(cfg.window_border, cfg.window_border);
283     [termview setFrame:rect];
284     [[self contentView] addSubview:termview];
285
286     /*
287      * Set up the colour palette.
288      */
289     palette_reset(self);
290
291     /*
292      * FIXME: Only the _first_ document window should be centred.
293      * The subsequent ones should appear down and to the right of
294      * it, probably using the cascade function provided by Cocoa.
295      * Also we're apparently required by the HIG to remember and
296      * reuse previous positions of windows, although I'm not sure
297      * how that works if the user opens more than one of the same
298      * session type.
299      */
300     [self center];                     /* :-) */
301
302     return self;
303 }
304
305 - (void)dealloc
306 {
307     /*
308      * FIXME: Here we must deallocate all sorts of stuff: the
309      * terminal, the backend, the ldisc, the logctx, you name it.
310      * Do so.
311      */
312     sfree(alert_ctx);
313     [super dealloc];
314 }
315
316 - (void)drawStartFinish:(BOOL)start
317 {
318     [termview drawStartFinish:start];
319 }
320
321 - (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
322 {
323     [termview setColour:n r:r g:g b:b];
324 }
325
326 - (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
327     attr:(unsigned long)attr lattr:(int)lattr
328 {
329     /* Pass this straight on to the TerminalView. */
330     [termview doText:text len:len x:x y:y attr:attr lattr:lattr];
331 }
332
333 - (Config *)cfg
334 {
335     return &cfg;
336 }
337
338 - (void)keyDown:(NSEvent *)ev
339 {
340     NSString *s = [ev characters];
341     int i;
342     int n = [s length], c = [s characterAtIndex:0], m = [ev modifierFlags];
343     int cm = [[ev charactersIgnoringModifiers] characterAtIndex:0];
344     wchar_t output[32];
345     char coutput[32];
346     int use_coutput = FALSE, special = FALSE, start, end;
347
348 printf("n=%d c=U+%04x cm=U+%04x m=%08x\n", n, c, cm, m);
349
350     /*
351      * FIXME: Alt+numberpad codes.
352      */
353
354     /*
355      * Shift and Ctrl with PageUp/PageDown for scrollback.
356      */
357     if (n == 1 && c == NSPageUpFunctionKey && (m & NSShiftKeyMask)) {
358         term_scroll(term, 0, -term->rows/2);
359         return;
360     }
361     if (n == 1 && c == NSPageUpFunctionKey && (m & NSControlKeyMask)) {
362         term_scroll(term, 0, -1);
363         return;
364     }
365     if (n == 1 && c == NSPageDownFunctionKey && (m & NSShiftKeyMask)) {
366         term_scroll(term, 0, +term->rows/2);
367         return;
368     }
369     if (n == 1 && c == NSPageDownFunctionKey && (m & NSControlKeyMask)) {
370         term_scroll(term, 0, +1);
371         return;
372     }
373
374     /*
375      * FIXME: Shift-Ins for paste? Or is that not Maccy enough?
376      */
377
378     /*
379      * FIXME: Alt (Option? Command?) prefix in general.
380      * 
381      * (Note that Alt-Shift-thing will work just by looking at
382      * charactersIgnoringModifiers; but Alt-Ctrl-thing will need
383      * processing properly, and Alt-as-in-Option won't happen at
384      * all. Hmmm.)
385      * 
386      * (Note also that we need to be able to override menu key
387      * equivalents before this is particularly useful.)
388      */
389     start = 1;
390     end = start;
391
392     /*
393      * Ctrl-` is the same as Ctrl-\, unless we already have a
394      * better idea.
395      */
396     if ((m & NSControlKeyMask) && n == 1 && cm == '`' && c == '`') {
397         output[1] = '\x1c';
398         end = 2;
399     }
400
401     /* We handle Return ourselves, because it needs to be flagged as
402      * special to ldisc. */
403     if (n == 1 && c == '\015') {
404         coutput[1] = '\015';
405         use_coutput = TRUE;
406         end = 2;
407         special = TRUE;
408     }
409
410     /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
411     if (n == 1 && (m & NSControlKeyMask) && (m & NSShiftKeyMask) &&
412         cm == ' ') {
413         output[1] = '\240';
414         end = 2;
415     }
416
417     /* Control-2, Control-Space and Control-@ are all NUL. */
418     if ((m & NSControlKeyMask) && n == 1 &&
419         (cm == '2' || cm == '@' || cm == ' ') && c == cm) {
420         output[1] = '\0';
421         end = 2;
422     }
423
424     /* We don't let MacOS tell us what Backspace is! We know better. */
425     if (cm == 0x7F && !(m & NSShiftKeyMask)) {
426         coutput[1] = cfg.bksp_is_delete ? '\x7F' : '\x08';
427         end = 2;
428         use_coutput = special = TRUE;
429     }
430     /* For Shift Backspace, do opposite of what is configured. */
431     if (cm == 0x7F && (m & NSShiftKeyMask)) {
432         coutput[1] = cfg.bksp_is_delete ? '\x08' : '\x7F';
433         end = 2;
434         use_coutput = special = TRUE;
435     }
436
437     /* Shift-Tab is ESC [ Z. Oddly, this combination generates ^Y by
438      * default on MacOS! */
439     if (cm == 0x19 && (m & NSShiftKeyMask) && !(m & NSControlKeyMask)) {
440         end = 1;
441         output[end++] = '\033';
442         output[end++] = '[';
443         output[end++] = 'Z';
444     }
445
446     /*
447      * NetHack keypad mode.
448      */
449     if (cfg.nethack_keypad && (m & NSNumericPadKeyMask)) {
450         wchar_t *keys = NULL;
451         switch (cm) {
452           case '1': keys = L"bB"; break;
453           case '2': keys = L"jJ"; break;
454           case '3': keys = L"nN"; break;
455           case '4': keys = L"hH"; break;
456           case '5': keys = L".."; break;
457           case '6': keys = L"lL"; break;
458           case '7': keys = L"yY"; break;
459           case '8': keys = L"kK"; break;
460           case '9': keys = L"uU"; break;
461         }
462         if (keys) {
463             end = 2;
464             if (m & NSShiftKeyMask)
465                 output[1] = keys[1];
466             else
467                 output[1] = keys[0];
468             goto done;
469         }
470     }
471
472     /*
473      * Application keypad mode.
474      */
475     if (term->app_keypad_keys && !cfg.no_applic_k &&
476         (m & NSNumericPadKeyMask)) {
477         int xkey = 0;
478         switch (cm) {
479           case NSClearLineFunctionKey: xkey = 'P'; break;
480           case '=': xkey = 'Q'; break;
481           case '/': xkey = 'R'; break;
482           case '*': xkey = 'S'; break;
483             /*
484              * FIXME: keypad - and + need to be mapped to ESC O l
485              * and ESC O k, or ESC O l and ESC O m, depending on
486              * xterm function key mode, and I can't remember which
487              * goes where.
488              */
489           case '\003': xkey = 'M'; break;
490           case '0': xkey = 'p'; break;
491           case '1': xkey = 'q'; break;
492           case '2': xkey = 'r'; break;
493           case '3': xkey = 's'; break;
494           case '4': xkey = 't'; break;
495           case '5': xkey = 'u'; break;
496           case '6': xkey = 'v'; break;
497           case '7': xkey = 'w'; break;
498           case '8': xkey = 'x'; break;
499           case '9': xkey = 'y'; break;
500           case '.': xkey = 'n'; break;
501         }
502         if (xkey) {
503             if (term->vt52_mode) {
504                 if (xkey >= 'P' && xkey <= 'S') {
505                     output[end++] = '\033';
506                     output[end++] = xkey;
507                 } else {
508                     output[end++] = '\033';
509                     output[end++] = '?';
510                     output[end++] = xkey;
511                 }
512             } else {
513                 output[end++] = '\033';
514                 output[end++] = 'O';
515                 output[end++] = xkey;
516             }
517             goto done;
518         }
519     }
520
521     /*
522      * Next, all the keys that do tilde codes. (ESC '[' nn '~',
523      * for integer decimal nn.)
524      *
525      * We also deal with the weird ones here. Linux VCs replace F1
526      * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
527      * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
528      * respectively.
529      */
530     {
531         int code = 0;
532         switch (cm) {
533           case NSF1FunctionKey:
534             code = (m & NSShiftKeyMask ? 23 : 11);
535             break;
536           case NSF2FunctionKey:
537             code = (m & NSShiftKeyMask ? 24 : 12);
538             break;
539           case NSF3FunctionKey:
540             code = (m & NSShiftKeyMask ? 25 : 13);
541             break;
542           case NSF4FunctionKey:
543             code = (m & NSShiftKeyMask ? 26 : 14);
544             break;
545           case NSF5FunctionKey:
546             code = (m & NSShiftKeyMask ? 28 : 15);
547             break;
548           case NSF6FunctionKey:
549             code = (m & NSShiftKeyMask ? 29 : 17);
550             break;
551           case NSF7FunctionKey:
552             code = (m & NSShiftKeyMask ? 31 : 18);
553             break;
554           case NSF8FunctionKey:
555             code = (m & NSShiftKeyMask ? 32 : 19);
556             break;
557           case NSF9FunctionKey:
558             code = (m & NSShiftKeyMask ? 33 : 20);
559             break;
560           case NSF10FunctionKey:
561             code = (m & NSShiftKeyMask ? 34 : 21);
562             break;
563           case NSF11FunctionKey:
564             code = 23;
565             break;
566           case NSF12FunctionKey:
567             code = 24;
568             break;
569           case NSF13FunctionKey:
570             code = 25;
571             break;
572           case NSF14FunctionKey:
573             code = 26;
574             break;
575           case NSF15FunctionKey:
576             code = 28;
577             break;
578           case NSF16FunctionKey:
579             code = 29;
580             break;
581           case NSF17FunctionKey:
582             code = 31;
583             break;
584           case NSF18FunctionKey:
585             code = 32;
586             break;
587           case NSF19FunctionKey:
588             code = 33;
589             break;
590           case NSF20FunctionKey:
591             code = 34;
592             break;
593         }
594         if (!(m & NSControlKeyMask)) switch (cm) {
595           case NSHomeFunctionKey:
596             code = 1;
597             break;
598 #ifdef FIXME
599           case GDK_Insert: case GDK_KP_Insert:
600             code = 2;
601             break;
602 #endif
603           case NSDeleteFunctionKey:
604             code = 3;
605             break;
606           case NSEndFunctionKey:
607             code = 4;
608             break;
609           case NSPageUpFunctionKey:
610             code = 5;
611             break;
612           case NSPageDownFunctionKey:
613             code = 6;
614             break;
615         }
616         /* Reorder edit keys to physical order */
617         if (cfg.funky_type == FUNKY_VT400 && code <= 6)
618             code = "\0\2\1\4\5\3\6"[code];
619
620         if (term->vt52_mode && code > 0 && code <= 6) {
621             output[end++] = '\033';
622             output[end++] = " HLMEIG"[code];
623             goto done;
624         }
625
626         if (cfg.funky_type == FUNKY_SCO &&     /* SCO function keys */
627             code >= 11 && code <= 34) {
628             char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
629             int index = 0;
630             switch (cm) {
631               case NSF1FunctionKey: index = 0; break;
632               case NSF2FunctionKey: index = 1; break;
633               case NSF3FunctionKey: index = 2; break;
634               case NSF4FunctionKey: index = 3; break;
635               case NSF5FunctionKey: index = 4; break;
636               case NSF6FunctionKey: index = 5; break;
637               case NSF7FunctionKey: index = 6; break;
638               case NSF8FunctionKey: index = 7; break;
639               case NSF9FunctionKey: index = 8; break;
640               case NSF10FunctionKey: index = 9; break;
641               case NSF11FunctionKey: index = 10; break;
642               case NSF12FunctionKey: index = 11; break;
643             }
644             if (m & NSShiftKeyMask) index += 12;
645             if (m & NSControlKeyMask) index += 24;
646             output[end++] = '\033';
647             output[end++] = '[';
648             output[end++] = codes[index];
649             goto done;
650         }
651         if (cfg.funky_type == FUNKY_SCO &&     /* SCO small keypad */
652             code >= 1 && code <= 6) {
653             char codes[] = "HL.FIG";
654             if (code == 3) {
655                 output[1] = '\x7F';
656                 end = 2;
657             } else {
658                 output[end++] = '\033';
659                 output[end++] = '[';
660                 output[end++] = codes[code-1];
661             }
662             goto done;
663         }
664         if ((term->vt52_mode || cfg.funky_type == FUNKY_VT100P) &&
665             code >= 11 && code <= 24) {
666             int offt = 0;
667             if (code > 15)
668                 offt++;
669             if (code > 21)
670                 offt++;
671             if (term->vt52_mode) {
672                 output[end++] = '\033';
673                 output[end++] = code + 'P' - 11 - offt;
674             } else {
675                 output[end++] = '\033';
676                 output[end++] = 'O';
677                 output[end++] = code + 'P' - 11 - offt;
678             }
679             goto done;
680         }
681         if (cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
682             output[end++] = '\033';
683             output[end++] = '[';
684             output[end++] = '[';        
685             output[end++] = code + 'A' - 11;
686             goto done;
687         }
688         if (cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {
689             if (term->vt52_mode) {
690                 output[end++] = '\033';
691                 output[end++] = code + 'P' - 11;
692             } else {
693                 output[end++] = '\033';
694                 output[end++] = 'O';
695                 output[end++] = code + 'P' - 11;
696             }
697             goto done;
698         }
699         if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
700             if (code == 1) {
701                 output[end++] = '\033';
702                 output[end++] = '[';
703                 output[end++] = 'H';
704             } else {
705                 output[end++] = '\033';
706                 output[end++] = 'O';
707                 output[end++] = 'w';
708             }
709             goto done;
710         }
711         if (code) {
712             char buf[20];
713             sprintf(buf, "\x1B[%d~", code);
714             for (i = 0; buf[i]; i++)
715                 output[end++] = buf[i];
716             goto done;
717         }
718     }
719
720     /*
721      * Cursor keys. (This includes the numberpad cursor keys,
722      * if we haven't already done them due to app keypad mode.)
723      */
724     {
725         int xkey = 0;
726         switch (cm) {
727           case NSUpArrowFunctionKey: xkey = 'A'; break;
728           case NSDownArrowFunctionKey: xkey = 'B'; break;
729           case NSRightArrowFunctionKey: xkey = 'C'; break;
730           case NSLeftArrowFunctionKey: xkey = 'D'; break;
731         }
732         if (xkey) {
733             /*
734              * The arrow keys normally do ESC [ A and so on. In
735              * app cursor keys mode they do ESC O A instead.
736              * Ctrl toggles the two modes.
737              */
738             if (term->vt52_mode) {
739                 output[end++] = '\033';
740                 output[end++] = xkey;
741             } else if (!term->app_cursor_keys ^ !(m & NSControlKeyMask)) {
742                 output[end++] = '\033';
743                 output[end++] = 'O';
744                 output[end++] = xkey;
745             } else {
746                 output[end++] = '\033';
747                 output[end++] = '[';
748                 output[end++] = xkey;
749             }
750             goto done;
751         }
752     }
753
754     done:
755
756     /*
757      * Failing everything else, send the exact Unicode we got from
758      * OS X.
759      */
760     if (end == start) {
761         if (n > lenof(output)-start)
762             n = lenof(output)-start;   /* _shouldn't_ happen! */
763         for (i = 0; i < n; i++) {
764             output[i+start] = [s characterAtIndex:i];
765         }
766         end = n+start;
767     }
768
769     if (use_coutput) {
770         assert(special);
771         assert(end < lenof(coutput));
772         coutput[end] = '\0';
773         ldisc_send(ldisc, coutput+start, -2, TRUE);
774     } else {
775         luni_send(ldisc, output+start, end-start, TRUE);
776     }
777 }
778
779 - (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr
780 {
781     return term_data(term, is_stderr, data, len);
782 }
783
784 - (void)startAlert:(NSAlert *)alert
785     withCallback:(void (*)(void *, int))callback andCtx:(void *)ctx
786 {
787     alert_callback = callback;
788     alert_ctx = ctx;                 /* NB this is assumed to need freeing! */
789     [alert beginSheetModalForWindow:self modalDelegate:self
790      didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:)
791      contextInfo:NULL];
792 }
793
794 - (void)alertSheetDidEnd:(NSAlert *)alert returnCode:(int)returnCode
795     contextInfo:(void *)contextInfo
796 {
797     alert_callback(alert_ctx, returnCode);   /* transfers ownership of ctx */
798     alert_ctx = NULL;
799 }
800
801 @end
802
803 int from_backend(void *frontend, int is_stderr, const char *data, int len)
804 {
805     SessionWindow *win = (SessionWindow *)frontend;
806     return [win fromBackend:data len:len isStderr:is_stderr];
807 }
808
809 void frontend_keypress(void *handle)
810 {
811     /* FIXME */
812 }
813
814 void connection_fatal(void *frontend, char *p, ...)
815 {
816     //SessionWindow *win = (SessionWindow *)frontend;
817     /* FIXME: proper OS X GUI stuff */
818     va_list ap;
819     fprintf(stderr, "FATAL ERROR: ");
820     va_start(ap, p);
821     vfprintf(stderr, p, ap);
822     va_end(ap);
823     fputc('\n', stderr);
824     exit(1);
825 }
826
827 void notify_remote_exit(void *frontend)
828 {
829     //SessionWindow *win = (SessionWindow *)frontend;
830     /* FIXME */
831 }
832
833 void ldisc_update(void *frontend, int echo, int edit)
834 {
835     //SessionWindow *win = (SessionWindow *)frontend;
836     /*
837      * In a GUI front end, this need do nothing.
838      */
839 }
840
841 void update_specials_menu(void *frontend)
842 {
843     //SessionWindow *win = (SessionWindow *)frontend;
844     /* FIXME */
845 }
846
847 /*
848  * This is still called when mode==BELL_VISUAL, even though the
849  * visual bell is handled entirely within terminal.c, because we
850  * may want to perform additional actions on any kind of bell (for
851  * example, taskbar flashing in Windows).
852  */
853 void beep(void *frontend, int mode)
854 {
855     //SessionWindow *win = (SessionWindow *)frontend;
856     if (mode != BELL_VISUAL)
857         NSBeep();
858 }
859
860 int char_width(Context ctx, int uc)
861 {
862     /*
863      * Under X, any fixed-width font really _is_ fixed-width.
864      * Double-width characters will be dealt with using a separate
865      * font. For the moment we can simply return 1.
866      */
867     return 1;
868 }
869
870 void palette_set(void *frontend, int n, int r, int g, int b)
871 {
872     SessionWindow *win = (SessionWindow *)frontend;
873
874     if (n >= 16)
875         n += 256 - 16;
876     if (n > NALLCOLOURS)
877         return;
878     [win setColour:n r:r/255.0 g:g/255.0 b:b/255.0];
879
880     /*
881      * FIXME: do we need an OS X equivalent of set_window_background?
882      */
883 }
884
885 void palette_reset(void *frontend)
886 {
887     SessionWindow *win = (SessionWindow *)frontend;
888     Config *cfg = [win cfg];
889
890     /* This maps colour indices in cfg to those used in colours[]. */
891     static const int ww[] = {
892         256, 257, 258, 259, 260, 261,
893         0, 8, 1, 9, 2, 10, 3, 11,
894         4, 12, 5, 13, 6, 14, 7, 15
895     };
896
897     int i;
898
899     for (i = 0; i < NCFGCOLOURS; i++) {
900         [win setColour:ww[i] r:cfg->colours[i][0]/255.0
901          g:cfg->colours[i][1]/255.0 b:cfg->colours[i][2]/255.0];
902     }
903
904     for (i = 0; i < NEXTCOLOURS; i++) {
905         if (i < 216) {
906             int r = i / 36, g = (i / 6) % 6, b = i % 6;
907             [win setColour:i+16 r:r/5.0 g:g/5.0 b:b/5.0];
908         } else {
909             int shade = i - 216;
910             float fshade = (shade + 1) / (float)(NEXTCOLOURS - 216 + 1);
911             [win setColour:i+16 r:fshade g:fshade b:fshade];
912         }
913     }
914
915     /*
916      * FIXME: do we need an OS X equivalent of set_window_background?
917      */
918 }
919
920 Context get_ctx(void *frontend)
921 {
922     SessionWindow *win = (SessionWindow *)frontend;
923
924     /*
925      * Lock the drawing focus on the image inside the TerminalView.
926      */
927     [win drawStartFinish:YES];
928
929     [[NSGraphicsContext currentContext] setShouldAntialias:YES];
930
931     /*
932      * Cocoa drawing functions don't take a graphics context: that
933      * parameter is implicit. Therefore, we'll use the frontend
934      * handle itself as the context, on the grounds that it's as
935      * good a thing to use as any.
936      */
937     return frontend;
938 }
939
940 void free_ctx(Context ctx)
941 {
942     SessionWindow *win = (SessionWindow *)ctx;
943
944     [win drawStartFinish:NO];
945 }
946
947 void do_text(Context ctx, int x, int y, wchar_t *text, int len,
948              unsigned long attr, int lattr)
949 {
950     SessionWindow *win = (SessionWindow *)ctx;
951
952     [win doText:text len:len x:x y:y attr:attr lattr:lattr];
953 }
954
955 void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
956                unsigned long attr, int lattr)
957 {
958     SessionWindow *win = (SessionWindow *)ctx;
959     Config *cfg = [win cfg];
960     int active, passive;
961
962     if (attr & TATTR_PASCURS) {
963         attr &= ~TATTR_PASCURS;
964         passive = 1;
965     } else
966         passive = 0;
967     if ((attr & TATTR_ACTCURS) && cfg->cursor_type != 0) {
968         attr &= ~TATTR_ACTCURS;
969         active = 1;
970     } else
971         active = 0;
972
973     [win doText:text len:len x:x y:y attr:attr lattr:lattr];
974
975     /*
976      * FIXME: now draw the various cursor types (both passive and
977      * active underlines and vertical lines, plus passive blocks).
978      */
979 }
980
981 /*
982  * Minimise or restore the window in response to a server-side
983  * request.
984  */
985 void set_iconic(void *frontend, int iconic)
986 {
987     //SessionWindow *win = (SessionWindow *)frontend;
988     /* FIXME */
989 }
990
991 /*
992  * Move the window in response to a server-side request.
993  */
994 void move_window(void *frontend, int x, int y)
995 {
996     //SessionWindow *win = (SessionWindow *)frontend; 
997     /* FIXME */
998 }
999
1000 /*
1001  * Move the window to the top or bottom of the z-order in response
1002  * to a server-side request.
1003  */
1004 void set_zorder(void *frontend, int top)
1005 {
1006     //SessionWindow *win = (SessionWindow *)frontend;
1007     /* FIXME */
1008 }
1009
1010 /*
1011  * Refresh the window in response to a server-side request.
1012  */
1013 void refresh_window(void *frontend)
1014 {
1015     //SessionWindow *win = (SessionWindow *)frontend;
1016     /* FIXME */
1017 }
1018
1019 /*
1020  * Maximise or restore the window in response to a server-side
1021  * request.
1022  */
1023 void set_zoomed(void *frontend, int zoomed)
1024 {
1025     //SessionWindow *win = (SessionWindow *)frontend;
1026     /* FIXME */
1027 }
1028
1029 /*
1030  * Report whether the window is iconic, for terminal reports.
1031  */
1032 int is_iconic(void *frontend)
1033 {
1034     //SessionWindow *win = (SessionWindow *)frontend;
1035     return NO;                         /* FIXME */
1036 }
1037
1038 /*
1039  * Report the window's position, for terminal reports.
1040  */
1041 void get_window_pos(void *frontend, int *x, int *y)
1042 {
1043     //SessionWindow *win = (SessionWindow *)frontend;
1044     /* FIXME */
1045 }
1046
1047 /*
1048  * Report the window's pixel size, for terminal reports.
1049  */
1050 void get_window_pixels(void *frontend, int *x, int *y)
1051 {
1052     //SessionWindow *win = (SessionWindow *)frontend;
1053     /* FIXME */
1054 }
1055
1056 /*
1057  * Return the window or icon title.
1058  */
1059 char *get_window_title(void *frontend, int icon)
1060 {
1061     //SessionWindow *win = (SessionWindow *)frontend;
1062     return NULL; /* FIXME */
1063 }
1064
1065 void set_title(void *frontend, char *title)
1066 {
1067     //SessionWindow *win = (SessionWindow *)frontend;
1068     /* FIXME */
1069 }
1070
1071 void set_icon(void *frontend, char *title)
1072 {
1073     //SessionWindow *win = (SessionWindow *)frontend;
1074     /* FIXME */
1075 }
1076
1077 void set_sbar(void *frontend, int total, int start, int page)
1078 {
1079     //SessionWindow *win = (SessionWindow *)frontend;
1080     /* FIXME */
1081 }
1082
1083 void get_clip(void *frontend, wchar_t ** p, int *len)
1084 {
1085     //SessionWindow *win = (SessionWindow *)frontend;
1086     /* FIXME */
1087 }
1088
1089 void write_clip(void *frontend, wchar_t * data, int len, int must_deselect)
1090 {
1091     //SessionWindow *win = (SessionWindow *)frontend;
1092     /* FIXME */
1093 }
1094
1095 void request_paste(void *frontend)
1096 {
1097     //SessionWindow *win = (SessionWindow *)frontend;
1098     /* FIXME */
1099 }
1100
1101 void set_raw_mouse_mode(void *frontend, int activate)
1102 {
1103     //SessionWindow *win = (SessionWindow *)frontend;
1104     /* FIXME */
1105 }
1106
1107 void request_resize(void *frontend, int w, int h)
1108 {
1109     //SessionWindow *win = (SessionWindow *)frontend;
1110     /* FIXME */
1111 }
1112
1113 void sys_cursor(void *frontend, int x, int y)
1114 {
1115     //SessionWindow *win = (SessionWindow *)frontend;
1116     /*
1117      * This is probably meaningless under OS X. FIXME: find out for
1118      * sure.
1119      */
1120 }
1121
1122 void logevent(void *frontend, const char *string)
1123 {
1124     //SessionWindow *win = (SessionWindow *)frontend;
1125     /* FIXME */
1126 printf("logevent: %s\n", string);
1127 }
1128
1129 int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */
1130 {
1131     //SessionWindow *win = (SessionWindow *)frontend;
1132     return 1; /* FIXME */
1133 }
1134
1135 void set_busy_status(void *frontend, int status)
1136 {
1137     /*
1138      * We need do nothing here: the OS X `application is busy'
1139      * beachball pointer appears _automatically_ when the
1140      * application isn't responding to GUI messages.
1141      */
1142 }