]> asedeno.scripts.mit.edu Git - PuTTY.git/blob - macosx/osxwin.m
Merge out from trunk, to keep this branch viable. We are now up to
[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     back = backend_from_proto(cfg.protocol);
236     if (!back)
237         back = &pty_backend;
238
239     {
240         const char *error;
241         char *realhost = NULL;
242         error = back->init(self, &backhandle, &cfg, cfg.host, cfg.port,
243                            &realhost, cfg.tcp_nodelay, cfg.tcp_keepalives);
244         if (error) {
245             fatalbox("%s\n", error);   /* FIXME: connection_fatal at worst */
246         }
247
248         if (realhost)
249             sfree(realhost);           /* FIXME: do something with this */
250     }
251     back->provide_logctx(backhandle, logctx);
252
253     /*
254      * Create a line discipline. (This must be done after creating
255      * the terminal _and_ the backend, since it needs to be passed
256      * pointers to both.)
257      */
258     ldisc = ldisc_create(&cfg, term, back, backhandle, self);
259
260     /*
261      * FIXME: Set up a scrollbar.
262      */
263
264     self = [super initWithContentRect:rect
265             styleMask:(NSTitledWindowMask | NSMiniaturizableWindowMask |
266                        NSClosableWindowMask)
267             backing:NSBackingStoreBuffered
268             defer:YES];
269     [self setTitle:@"PuTTY"];
270
271     [self setIgnoresMouseEvents:NO];
272
273     /*
274      * Put the terminal view in the window.
275      */
276     rect = [termview frame];
277     rect.origin = NSMakePoint(cfg.window_border, cfg.window_border);
278     [termview setFrame:rect];
279     [[self contentView] addSubview:termview];
280
281     /*
282      * Set up the colour palette.
283      */
284     palette_reset(self);
285
286     /*
287      * FIXME: Only the _first_ document window should be centred.
288      * The subsequent ones should appear down and to the right of
289      * it, probably using the cascade function provided by Cocoa.
290      * Also we're apparently required by the HIG to remember and
291      * reuse previous positions of windows, although I'm not sure
292      * how that works if the user opens more than one of the same
293      * session type.
294      */
295     [self center];                     /* :-) */
296
297     exited = FALSE;
298
299     return self;
300 }
301
302 - (void)dealloc
303 {
304     /*
305      * FIXME: Here we must deallocate all sorts of stuff: the
306      * terminal, the backend, the ldisc, the logctx, you name it.
307      * Do so.
308      */
309     sfree(alert_ctx);
310     if (back)
311         back->free(backhandle);
312     if (ldisc)
313         ldisc_free(ldisc);
314     /* ldisc must be freed before term, since ldisc_free expects term
315      * still to be around. */
316     if (logctx)
317         log_free(logctx);
318     if (term)
319         term_free(term);
320     [super dealloc];
321 }
322
323 - (void)drawStartFinish:(BOOL)start
324 {
325     [termview drawStartFinish:start];
326 }
327
328 - (void)setColour:(int)n r:(float)r g:(float)g b:(float)b
329 {
330     [termview setColour:n r:r g:g b:b];
331 }
332
333 - (void)doText:(wchar_t *)text len:(int)len x:(int)x y:(int)y
334     attr:(unsigned long)attr lattr:(int)lattr
335 {
336     /* Pass this straight on to the TerminalView. */
337     [termview doText:text len:len x:x y:y attr:attr lattr:lattr];
338 }
339
340 - (Config *)cfg
341 {
342     return &cfg;
343 }
344
345 - (void)keyDown:(NSEvent *)ev
346 {
347     NSString *s = [ev characters];
348     int i;
349     int n = [s length], c = [s characterAtIndex:0], m = [ev modifierFlags];
350     int cm = [[ev charactersIgnoringModifiers] characterAtIndex:0];
351     wchar_t output[32];
352     char coutput[32];
353     int use_coutput = FALSE, special = FALSE, start, end;
354
355 //printf("n=%d c=U+%04x cm=U+%04x m=%08x\n", n, c, cm, m);
356
357     /*
358      * FIXME: Alt+numberpad codes.
359      */
360
361     /*
362      * Shift and Ctrl with PageUp/PageDown for scrollback.
363      */
364     if (n == 1 && c == NSPageUpFunctionKey && (m & NSShiftKeyMask)) {
365         term_scroll(term, 0, -term->rows/2);
366         return;
367     }
368     if (n == 1 && c == NSPageUpFunctionKey && (m & NSControlKeyMask)) {
369         term_scroll(term, 0, -1);
370         return;
371     }
372     if (n == 1 && c == NSPageDownFunctionKey && (m & NSShiftKeyMask)) {
373         term_scroll(term, 0, +term->rows/2);
374         return;
375     }
376     if (n == 1 && c == NSPageDownFunctionKey && (m & NSControlKeyMask)) {
377         term_scroll(term, 0, +1);
378         return;
379     }
380
381     /*
382      * FIXME: Shift-Ins for paste? Or is that not Maccy enough?
383      */
384
385     /*
386      * FIXME: Alt (Option? Command?) prefix in general.
387      * 
388      * (Note that Alt-Shift-thing will work just by looking at
389      * charactersIgnoringModifiers; but Alt-Ctrl-thing will need
390      * processing properly, and Alt-as-in-Option won't happen at
391      * all. Hmmm.)
392      * 
393      * (Note also that we need to be able to override menu key
394      * equivalents before this is particularly useful.)
395      */
396     start = 1;
397     end = start;
398
399     /*
400      * Ctrl-` is the same as Ctrl-\, unless we already have a
401      * better idea.
402      */
403     if ((m & NSControlKeyMask) && n == 1 && cm == '`' && c == '`') {
404         output[1] = '\x1c';
405         end = 2;
406     }
407
408     /* We handle Return ourselves, because it needs to be flagged as
409      * special to ldisc. */
410     if (n == 1 && c == '\015') {
411         coutput[1] = '\015';
412         use_coutput = TRUE;
413         end = 2;
414         special = TRUE;
415     }
416
417     /* Control-Shift-Space is 160 (ISO8859 nonbreaking space) */
418     if (n == 1 && (m & NSControlKeyMask) && (m & NSShiftKeyMask) &&
419         cm == ' ') {
420         output[1] = '\240';
421         end = 2;
422     }
423
424     /* Control-2, Control-Space and Control-@ are all NUL. */
425     if ((m & NSControlKeyMask) && n == 1 &&
426         (cm == '2' || cm == '@' || cm == ' ') && c == cm) {
427         output[1] = '\0';
428         end = 2;
429     }
430
431     /* We don't let MacOS tell us what Backspace is! We know better. */
432     if (cm == 0x7F && !(m & NSShiftKeyMask)) {
433         coutput[1] = cfg.bksp_is_delete ? '\x7F' : '\x08';
434         end = 2;
435         use_coutput = special = TRUE;
436     }
437     /* For Shift Backspace, do opposite of what is configured. */
438     if (cm == 0x7F && (m & NSShiftKeyMask)) {
439         coutput[1] = cfg.bksp_is_delete ? '\x08' : '\x7F';
440         end = 2;
441         use_coutput = special = TRUE;
442     }
443
444     /* Shift-Tab is ESC [ Z. Oddly, this combination generates ^Y by
445      * default on MacOS! */
446     if (cm == 0x19 && (m & NSShiftKeyMask) && !(m & NSControlKeyMask)) {
447         end = 1;
448         output[end++] = '\033';
449         output[end++] = '[';
450         output[end++] = 'Z';
451     }
452
453     /*
454      * NetHack keypad mode.
455      */
456     if (cfg.nethack_keypad && (m & NSNumericPadKeyMask)) {
457         wchar_t *keys = NULL;
458         switch (cm) {
459           case '1': keys = L"bB"; break;
460           case '2': keys = L"jJ"; break;
461           case '3': keys = L"nN"; break;
462           case '4': keys = L"hH"; break;
463           case '5': keys = L".."; break;
464           case '6': keys = L"lL"; break;
465           case '7': keys = L"yY"; break;
466           case '8': keys = L"kK"; break;
467           case '9': keys = L"uU"; break;
468         }
469         if (keys) {
470             end = 2;
471             if (m & NSShiftKeyMask)
472                 output[1] = keys[1];
473             else
474                 output[1] = keys[0];
475             goto done;
476         }
477     }
478
479     /*
480      * Application keypad mode.
481      */
482     if (term->app_keypad_keys && !cfg.no_applic_k &&
483         (m & NSNumericPadKeyMask)) {
484         int xkey = 0;
485         switch (cm) {
486           case NSClearLineFunctionKey: xkey = 'P'; break;
487           case '=': xkey = 'Q'; break;
488           case '/': xkey = 'R'; break;
489           case '*': xkey = 'S'; break;
490             /*
491              * FIXME: keypad - and + need to be mapped to ESC O l
492              * and ESC O k, or ESC O l and ESC O m, depending on
493              * xterm function key mode, and I can't remember which
494              * goes where.
495              */
496           case '\003': xkey = 'M'; break;
497           case '0': xkey = 'p'; break;
498           case '1': xkey = 'q'; break;
499           case '2': xkey = 'r'; break;
500           case '3': xkey = 's'; break;
501           case '4': xkey = 't'; break;
502           case '5': xkey = 'u'; break;
503           case '6': xkey = 'v'; break;
504           case '7': xkey = 'w'; break;
505           case '8': xkey = 'x'; break;
506           case '9': xkey = 'y'; break;
507           case '.': xkey = 'n'; break;
508         }
509         if (xkey) {
510             if (term->vt52_mode) {
511                 if (xkey >= 'P' && xkey <= 'S') {
512                     output[end++] = '\033';
513                     output[end++] = xkey;
514                 } else {
515                     output[end++] = '\033';
516                     output[end++] = '?';
517                     output[end++] = xkey;
518                 }
519             } else {
520                 output[end++] = '\033';
521                 output[end++] = 'O';
522                 output[end++] = xkey;
523             }
524             goto done;
525         }
526     }
527
528     /*
529      * Next, all the keys that do tilde codes. (ESC '[' nn '~',
530      * for integer decimal nn.)
531      *
532      * We also deal with the weird ones here. Linux VCs replace F1
533      * to F5 by ESC [ [ A to ESC [ [ E. rxvt doesn't do _that_, but
534      * does replace Home and End (1~ and 4~) by ESC [ H and ESC O w
535      * respectively.
536      */
537     {
538         int code = 0;
539         switch (cm) {
540           case NSF1FunctionKey:
541             code = (m & NSShiftKeyMask ? 23 : 11);
542             break;
543           case NSF2FunctionKey:
544             code = (m & NSShiftKeyMask ? 24 : 12);
545             break;
546           case NSF3FunctionKey:
547             code = (m & NSShiftKeyMask ? 25 : 13);
548             break;
549           case NSF4FunctionKey:
550             code = (m & NSShiftKeyMask ? 26 : 14);
551             break;
552           case NSF5FunctionKey:
553             code = (m & NSShiftKeyMask ? 28 : 15);
554             break;
555           case NSF6FunctionKey:
556             code = (m & NSShiftKeyMask ? 29 : 17);
557             break;
558           case NSF7FunctionKey:
559             code = (m & NSShiftKeyMask ? 31 : 18);
560             break;
561           case NSF8FunctionKey:
562             code = (m & NSShiftKeyMask ? 32 : 19);
563             break;
564           case NSF9FunctionKey:
565             code = (m & NSShiftKeyMask ? 33 : 20);
566             break;
567           case NSF10FunctionKey:
568             code = (m & NSShiftKeyMask ? 34 : 21);
569             break;
570           case NSF11FunctionKey:
571             code = 23;
572             break;
573           case NSF12FunctionKey:
574             code = 24;
575             break;
576           case NSF13FunctionKey:
577             code = 25;
578             break;
579           case NSF14FunctionKey:
580             code = 26;
581             break;
582           case NSF15FunctionKey:
583             code = 28;
584             break;
585           case NSF16FunctionKey:
586             code = 29;
587             break;
588           case NSF17FunctionKey:
589             code = 31;
590             break;
591           case NSF18FunctionKey:
592             code = 32;
593             break;
594           case NSF19FunctionKey:
595             code = 33;
596             break;
597           case NSF20FunctionKey:
598             code = 34;
599             break;
600         }
601         if (!(m & NSControlKeyMask)) switch (cm) {
602           case NSHomeFunctionKey:
603             code = 1;
604             break;
605 #ifdef FIXME
606           case GDK_Insert: case GDK_KP_Insert:
607             code = 2;
608             break;
609 #endif
610           case NSDeleteFunctionKey:
611             code = 3;
612             break;
613           case NSEndFunctionKey:
614             code = 4;
615             break;
616           case NSPageUpFunctionKey:
617             code = 5;
618             break;
619           case NSPageDownFunctionKey:
620             code = 6;
621             break;
622         }
623         /* Reorder edit keys to physical order */
624         if (cfg.funky_type == FUNKY_VT400 && code <= 6)
625             code = "\0\2\1\4\5\3\6"[code];
626
627         if (term->vt52_mode && code > 0 && code <= 6) {
628             output[end++] = '\033';
629             output[end++] = " HLMEIG"[code];
630             goto done;
631         }
632
633         if (cfg.funky_type == FUNKY_SCO &&     /* SCO function keys */
634             code >= 11 && code <= 34) {
635             char codes[] = "MNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@[\\]^_`{";
636             int index = 0;
637             switch (cm) {
638               case NSF1FunctionKey: index = 0; break;
639               case NSF2FunctionKey: index = 1; break;
640               case NSF3FunctionKey: index = 2; break;
641               case NSF4FunctionKey: index = 3; break;
642               case NSF5FunctionKey: index = 4; break;
643               case NSF6FunctionKey: index = 5; break;
644               case NSF7FunctionKey: index = 6; break;
645               case NSF8FunctionKey: index = 7; break;
646               case NSF9FunctionKey: index = 8; break;
647               case NSF10FunctionKey: index = 9; break;
648               case NSF11FunctionKey: index = 10; break;
649               case NSF12FunctionKey: index = 11; break;
650             }
651             if (m & NSShiftKeyMask) index += 12;
652             if (m & NSControlKeyMask) index += 24;
653             output[end++] = '\033';
654             output[end++] = '[';
655             output[end++] = codes[index];
656             goto done;
657         }
658         if (cfg.funky_type == FUNKY_SCO &&     /* SCO small keypad */
659             code >= 1 && code <= 6) {
660             char codes[] = "HL.FIG";
661             if (code == 3) {
662                 output[1] = '\x7F';
663                 end = 2;
664             } else {
665                 output[end++] = '\033';
666                 output[end++] = '[';
667                 output[end++] = codes[code-1];
668             }
669             goto done;
670         }
671         if ((term->vt52_mode || cfg.funky_type == FUNKY_VT100P) &&
672             code >= 11 && code <= 24) {
673             int offt = 0;
674             if (code > 15)
675                 offt++;
676             if (code > 21)
677                 offt++;
678             if (term->vt52_mode) {
679                 output[end++] = '\033';
680                 output[end++] = code + 'P' - 11 - offt;
681             } else {
682                 output[end++] = '\033';
683                 output[end++] = 'O';
684                 output[end++] = code + 'P' - 11 - offt;
685             }
686             goto done;
687         }
688         if (cfg.funky_type == FUNKY_LINUX && code >= 11 && code <= 15) {
689             output[end++] = '\033';
690             output[end++] = '[';
691             output[end++] = '[';        
692             output[end++] = code + 'A' - 11;
693             goto done;
694         }
695         if (cfg.funky_type == FUNKY_XTERM && code >= 11 && code <= 14) {
696             if (term->vt52_mode) {
697                 output[end++] = '\033';
698                 output[end++] = code + 'P' - 11;
699             } else {
700                 output[end++] = '\033';
701                 output[end++] = 'O';
702                 output[end++] = code + 'P' - 11;
703             }
704             goto done;
705         }
706         if (cfg.rxvt_homeend && (code == 1 || code == 4)) {
707             if (code == 1) {
708                 output[end++] = '\033';
709                 output[end++] = '[';
710                 output[end++] = 'H';
711             } else {
712                 output[end++] = '\033';
713                 output[end++] = 'O';
714                 output[end++] = 'w';
715             }
716             goto done;
717         }
718         if (code) {
719             char buf[20];
720             sprintf(buf, "\x1B[%d~", code);
721             for (i = 0; buf[i]; i++)
722                 output[end++] = buf[i];
723             goto done;
724         }
725     }
726
727     /*
728      * Cursor keys. (This includes the numberpad cursor keys,
729      * if we haven't already done them due to app keypad mode.)
730      */
731     {
732         int xkey = 0;
733         switch (cm) {
734           case NSUpArrowFunctionKey: xkey = 'A'; break;
735           case NSDownArrowFunctionKey: xkey = 'B'; break;
736           case NSRightArrowFunctionKey: xkey = 'C'; break;
737           case NSLeftArrowFunctionKey: xkey = 'D'; break;
738         }
739         if (xkey) {
740             /*
741              * The arrow keys normally do ESC [ A and so on. In
742              * app cursor keys mode they do ESC O A instead.
743              * Ctrl toggles the two modes.
744              */
745             if (term->vt52_mode) {
746                 output[end++] = '\033';
747                 output[end++] = xkey;
748             } else if (!term->app_cursor_keys ^ !(m & NSControlKeyMask)) {
749                 output[end++] = '\033';
750                 output[end++] = 'O';
751                 output[end++] = xkey;
752             } else {
753                 output[end++] = '\033';
754                 output[end++] = '[';
755                 output[end++] = xkey;
756             }
757             goto done;
758         }
759     }
760
761     done:
762
763     /*
764      * Failing everything else, send the exact Unicode we got from
765      * OS X.
766      */
767     if (end == start) {
768         if (n > lenof(output)-start)
769             n = lenof(output)-start;   /* _shouldn't_ happen! */
770         for (i = 0; i < n; i++) {
771             output[i+start] = [s characterAtIndex:i];
772         }
773         end = n+start;
774     }
775
776     if (use_coutput) {
777         assert(special);
778         assert(end < lenof(coutput));
779         coutput[end] = '\0';
780         ldisc_send(ldisc, coutput+start, -2, TRUE);
781     } else {
782         luni_send(ldisc, output+start, end-start, TRUE);
783     }
784 }
785
786 - (int)fromBackend:(const char *)data len:(int)len isStderr:(int)is_stderr
787 {
788     return term_data(term, is_stderr, data, len);
789 }
790
791 - (int)fromBackendUntrusted:(const char *)data len:(int)len
792 {
793     return term_data_untrusted(term, data, len);
794 }
795
796 - (void)startAlert:(NSAlert *)alert
797     withCallback:(void (*)(void *, int))callback andCtx:(void *)ctx
798 {
799     if (alert_ctx || alert_qhead) {
800         /*
801          * Queue this alert to be shown later.
802          */
803         struct alert_queue *qitem = snew(struct alert_queue);
804         qitem->next = NULL;
805         qitem->alert = alert;
806         qitem->callback = callback;
807         qitem->ctx = ctx;
808         if (alert_qtail)
809             alert_qtail->next = qitem;
810         else
811             alert_qhead = qitem;
812         alert_qtail = qitem;
813     } else {
814         alert_callback = callback;
815         alert_ctx = ctx;               /* NB this is assumed to need freeing! */
816         [alert beginSheetModalForWindow:self modalDelegate:self
817          didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:)
818          contextInfo:NULL];
819     }
820 }
821
822 - (void)alertSheetDidEnd:(NSAlert *)alert returnCode:(int)returnCode
823     contextInfo:(void *)contextInfo
824 {
825     [self performSelectorOnMainThread:
826      @selector(alertSheetDidFinishEnding:)
827      withObject:[NSNumber numberWithInt:returnCode]
828      waitUntilDone:NO];
829 }
830
831 - (void)alertSheetDidFinishEnding:(id)object
832 {
833     int returnCode = [object intValue];
834
835     alert_callback(alert_ctx, returnCode);   /* transfers ownership of ctx */
836
837     /*
838      * If there's an alert in our queue (either already or because
839      * the callback just queued it), start it.
840      */
841     if (alert_qhead) {
842         struct alert_queue *qnext;
843
844         alert_callback = alert_qhead->callback;
845         alert_ctx = alert_qhead->ctx;
846         [alert_qhead->alert beginSheetModalForWindow:self modalDelegate:self
847          didEndSelector:@selector(alertSheetDidEnd:returnCode:contextInfo:)
848          contextInfo:NULL];
849
850         qnext = alert_qhead->next;
851         sfree(alert_qhead);
852         alert_qhead = qnext;
853         if (!qnext)
854             alert_qtail = NULL;
855     } else {
856         alert_ctx = NULL;
857     }
858 }
859
860 - (void)notifyRemoteExit
861 {
862     int exitcode;
863
864     if (!exited && (exitcode = back->exitcode(backhandle)) >= 0)
865         [self endSession:(exitcode == 0)];
866 }
867
868 - (void)endSession:(int)clean
869 {
870     exited = TRUE;
871     if (ldisc) {
872         ldisc_free(ldisc);
873         ldisc = NULL;
874     }
875     if (back) {
876         back->free(backhandle);
877         backhandle = NULL;
878         back = NULL;
879         //FIXME: update specials menu;
880     }
881     if (cfg.close_on_exit == FORCE_ON ||
882         (cfg.close_on_exit == AUTO && clean))
883         [self close];
884     // FIXME: else show restart menu item
885 }
886
887 - (Terminal *)term
888 {
889     return term;
890 }
891
892 @end
893
894 int from_backend(void *frontend, int is_stderr, const char *data, int len)
895 {
896     SessionWindow *win = (SessionWindow *)frontend;
897     return [win fromBackend:data len:len isStderr:is_stderr];
898 }
899
900 int from_backend_untrusted(void *frontend, const char *data, int len)
901 {
902     SessionWindow *win = (SessionWindow *)frontend;
903     return [win fromBackendUntrusted:data len:len];
904 }
905
906 int get_userpass_input(prompts_t *p, unsigned char *in, int inlen)
907 {
908     SessionWindow *win = (SessionWindow *)p->frontend;
909     Terminal *term = [win term];
910     return term_get_userpass_input(term, p, in, inlen);
911 }
912
913 void frontend_keypress(void *handle)
914 {
915     /* FIXME */
916 }
917
918 void notify_remote_exit(void *frontend)
919 {
920     SessionWindow *win = (SessionWindow *)frontend;
921
922     [win notifyRemoteExit];
923 }
924
925 void ldisc_update(void *frontend, int echo, int edit)
926 {
927     //SessionWindow *win = (SessionWindow *)frontend;
928     /*
929      * In a GUI front end, this need do nothing.
930      */
931 }
932
933 char *get_ttymode(void *frontend, const char *mode)
934 {
935     SessionWindow *win = (SessionWindow *)frontend;
936     Terminal *term = [win term];
937     return term_get_ttymode(term, mode);
938 }
939
940 void update_specials_menu(void *frontend)
941 {
942     //SessionWindow *win = (SessionWindow *)frontend;
943     /* FIXME */
944 }
945
946 /*
947  * This is still called when mode==BELL_VISUAL, even though the
948  * visual bell is handled entirely within terminal.c, because we
949  * may want to perform additional actions on any kind of bell (for
950  * example, taskbar flashing in Windows).
951  */
952 void do_beep(void *frontend, int mode)
953 {
954     //SessionWindow *win = (SessionWindow *)frontend;
955     if (mode != BELL_VISUAL)
956         NSBeep();
957 }
958
959 int char_width(Context ctx, int uc)
960 {
961     /*
962      * Under X, any fixed-width font really _is_ fixed-width.
963      * Double-width characters will be dealt with using a separate
964      * font. For the moment we can simply return 1.
965      */
966     return 1;
967 }
968
969 void palette_set(void *frontend, int n, int r, int g, int b)
970 {
971     SessionWindow *win = (SessionWindow *)frontend;
972
973     if (n >= 16)
974         n += 256 - 16;
975     if (n > NALLCOLOURS)
976         return;
977     [win setColour:n r:r/255.0 g:g/255.0 b:b/255.0];
978
979     /*
980      * FIXME: do we need an OS X equivalent of set_window_background?
981      */
982 }
983
984 void palette_reset(void *frontend)
985 {
986     SessionWindow *win = (SessionWindow *)frontend;
987     Config *cfg = [win cfg];
988
989     /* This maps colour indices in cfg to those used in colours[]. */
990     static const int ww[] = {
991         256, 257, 258, 259, 260, 261,
992         0, 8, 1, 9, 2, 10, 3, 11,
993         4, 12, 5, 13, 6, 14, 7, 15
994     };
995
996     int i;
997
998     for (i = 0; i < NCFGCOLOURS; i++) {
999         [win setColour:ww[i] r:cfg->colours[i][0]/255.0
1000          g:cfg->colours[i][1]/255.0 b:cfg->colours[i][2]/255.0];
1001     }
1002
1003     for (i = 0; i < NEXTCOLOURS; i++) {
1004         if (i < 216) {
1005             int r = i / 36, g = (i / 6) % 6, b = i % 6;
1006             r = r ? r*40+55 : 0; g = g ? b*40+55 : 0; b = b ? b*40+55 : 0;
1007             [win setColour:i+16 r:r/255.0 g:g/255.0 b:b/255.0];
1008         } else {
1009             int shade = i - 216;
1010             float fshade = (shade * 10 + 8) / 255.0;
1011             [win setColour:i+16 r:fshade g:fshade b:fshade];
1012         }
1013     }
1014
1015     /*
1016      * FIXME: do we need an OS X equivalent of set_window_background?
1017      */
1018 }
1019
1020 Context get_ctx(void *frontend)
1021 {
1022     SessionWindow *win = (SessionWindow *)frontend;
1023
1024     /*
1025      * Lock the drawing focus on the image inside the TerminalView.
1026      */
1027     [win drawStartFinish:YES];
1028
1029     [[NSGraphicsContext currentContext] setShouldAntialias:YES];
1030
1031     /*
1032      * Cocoa drawing functions don't take a graphics context: that
1033      * parameter is implicit. Therefore, we'll use the frontend
1034      * handle itself as the context, on the grounds that it's as
1035      * good a thing to use as any.
1036      */
1037     return frontend;
1038 }
1039
1040 void free_ctx(Context ctx)
1041 {
1042     SessionWindow *win = (SessionWindow *)ctx;
1043
1044     [win drawStartFinish:NO];
1045 }
1046
1047 void do_text(Context ctx, int x, int y, wchar_t *text, int len,
1048              unsigned long attr, int lattr)
1049 {
1050     SessionWindow *win = (SessionWindow *)ctx;
1051
1052     [win doText:text len:len x:x y:y attr:attr lattr:lattr];
1053 }
1054
1055 void do_cursor(Context ctx, int x, int y, wchar_t *text, int len,
1056                unsigned long attr, int lattr)
1057 {
1058     SessionWindow *win = (SessionWindow *)ctx;
1059     Config *cfg = [win cfg];
1060     int active, passive;
1061
1062     if (attr & TATTR_PASCURS) {
1063         attr &= ~TATTR_PASCURS;
1064         passive = 1;
1065     } else
1066         passive = 0;
1067     if ((attr & TATTR_ACTCURS) && cfg->cursor_type != 0) {
1068         attr &= ~TATTR_ACTCURS;
1069         active = 1;
1070     } else
1071         active = 0;
1072
1073     [win doText:text len:len x:x y:y attr:attr lattr:lattr];
1074
1075     /*
1076      * FIXME: now draw the various cursor types (both passive and
1077      * active underlines and vertical lines, plus passive blocks).
1078      */
1079 }
1080
1081 /*
1082  * Minimise or restore the window in response to a server-side
1083  * request.
1084  */
1085 void set_iconic(void *frontend, int iconic)
1086 {
1087     //SessionWindow *win = (SessionWindow *)frontend;
1088     /* FIXME */
1089 }
1090
1091 /*
1092  * Move the window in response to a server-side request.
1093  */
1094 void move_window(void *frontend, int x, int y)
1095 {
1096     //SessionWindow *win = (SessionWindow *)frontend; 
1097     /* FIXME */
1098 }
1099
1100 /*
1101  * Move the window to the top or bottom of the z-order in response
1102  * to a server-side request.
1103  */
1104 void set_zorder(void *frontend, int top)
1105 {
1106     //SessionWindow *win = (SessionWindow *)frontend;
1107     /* FIXME */
1108 }
1109
1110 /*
1111  * Refresh the window in response to a server-side request.
1112  */
1113 void refresh_window(void *frontend)
1114 {
1115     //SessionWindow *win = (SessionWindow *)frontend;
1116     /* FIXME */
1117 }
1118
1119 /*
1120  * Maximise or restore the window in response to a server-side
1121  * request.
1122  */
1123 void set_zoomed(void *frontend, int zoomed)
1124 {
1125     //SessionWindow *win = (SessionWindow *)frontend;
1126     /* FIXME */
1127 }
1128
1129 /*
1130  * Report whether the window is iconic, for terminal reports.
1131  */
1132 int is_iconic(void *frontend)
1133 {
1134     //SessionWindow *win = (SessionWindow *)frontend;
1135     return NO;                         /* FIXME */
1136 }
1137
1138 /*
1139  * Report the window's position, for terminal reports.
1140  */
1141 void get_window_pos(void *frontend, int *x, int *y)
1142 {
1143     //SessionWindow *win = (SessionWindow *)frontend;
1144     /* FIXME */
1145 }
1146
1147 /*
1148  * Report the window's pixel size, for terminal reports.
1149  */
1150 void get_window_pixels(void *frontend, int *x, int *y)
1151 {
1152     //SessionWindow *win = (SessionWindow *)frontend;
1153     /* FIXME */
1154 }
1155
1156 /*
1157  * Return the window or icon title.
1158  */
1159 char *get_window_title(void *frontend, int icon)
1160 {
1161     //SessionWindow *win = (SessionWindow *)frontend;
1162     return NULL; /* FIXME */
1163 }
1164
1165 void set_title(void *frontend, char *title)
1166 {
1167     //SessionWindow *win = (SessionWindow *)frontend;
1168     /* FIXME */
1169 }
1170
1171 void set_icon(void *frontend, char *title)
1172 {
1173     //SessionWindow *win = (SessionWindow *)frontend;
1174     /* FIXME */
1175 }
1176
1177 void set_sbar(void *frontend, int total, int start, int page)
1178 {
1179     //SessionWindow *win = (SessionWindow *)frontend;
1180     /* FIXME */
1181 }
1182
1183 void get_clip(void *frontend, wchar_t ** p, int *len)
1184 {
1185     //SessionWindow *win = (SessionWindow *)frontend;
1186     /* FIXME */
1187 }
1188
1189 void write_clip(void *frontend, wchar_t *data, int *attr, int len, int must_deselect)
1190 {
1191     //SessionWindow *win = (SessionWindow *)frontend;
1192     /* FIXME */
1193 }
1194
1195 void request_paste(void *frontend)
1196 {
1197     //SessionWindow *win = (SessionWindow *)frontend;
1198     /* FIXME */
1199 }
1200
1201 void set_raw_mouse_mode(void *frontend, int activate)
1202 {
1203     //SessionWindow *win = (SessionWindow *)frontend;
1204     /* FIXME */
1205 }
1206
1207 void request_resize(void *frontend, int w, int h)
1208 {
1209     //SessionWindow *win = (SessionWindow *)frontend;
1210     /* FIXME */
1211 }
1212
1213 void sys_cursor(void *frontend, int x, int y)
1214 {
1215     //SessionWindow *win = (SessionWindow *)frontend;
1216     /*
1217      * This is probably meaningless under OS X. FIXME: find out for
1218      * sure.
1219      */
1220 }
1221
1222 void logevent(void *frontend, const char *string)
1223 {
1224     //SessionWindow *win = (SessionWindow *)frontend;
1225     /* FIXME */
1226 printf("logevent: %s\n", string);
1227 }
1228
1229 int font_dimension(void *frontend, int which)/* 0 for width, 1 for height */
1230 {
1231     //SessionWindow *win = (SessionWindow *)frontend;
1232     return 1; /* FIXME */
1233 }
1234
1235 void set_busy_status(void *frontend, int status)
1236 {
1237     /*
1238      * We need do nothing here: the OS X `application is busy'
1239      * beachball pointer appears _automatically_ when the
1240      * application isn't responding to GUI messages.
1241      */
1242 }