chiark / gitweb /
*** empty log message ***
[sympathy.git] / src / ansi.c
1 /*
2  * ansi.c:
3  *
4  * Copyright (c) 2008 James McKenzie <james@fishsoup.dhs.org>,
5  * All rights reserved.
6  *
7  */
8
9 static char rcsid[] = "$Id$";
10
11 /*
12  * $Log$
13  * Revision 1.28  2008/02/22 17:07:00  james
14  * *** empty log message ***
15  *
16  * Revision 1.27  2008/02/20 22:54:22  staffcvs
17  * *** empty log message ***
18  *
19  * Revision 1.26  2008/02/20 20:16:07  james
20  * *** empty log message ***
21  *
22  * Revision 1.25  2008/02/20 19:44:37  james
23  * @@
24  *
25  * Revision 1.24  2008/02/20 19:36:06  james
26  * @@
27  *
28  * Revision 1.23  2008/02/20 19:25:09  james
29  * *** empty log message ***
30  *
31  * Revision 1.22  2008/02/15 03:32:07  james
32  * *** empty log message ***
33  *
34  * Revision 1.21  2008/02/14 02:46:44  james
35  * *** empty log message ***
36  *
37  * Revision 1.20  2008/02/14 01:55:57  james
38  * *** empty log message ***
39  *
40  * Revision 1.19  2008/02/13 16:57:29  james
41  * *** empty log message ***
42  *
43  * Revision 1.18  2008/02/13 09:12:21  james
44  * *** empty log message ***
45  *
46  * Revision 1.17  2008/02/13 01:08:18  james
47  * *** empty log message ***
48  *
49  * Revision 1.16  2008/02/07 13:22:51  james
50  * *** empty log message ***
51  *
52  * Revision 1.15  2008/02/07 13:19:48  james
53  * *** empty log message ***
54  *
55  * Revision 1.14  2008/02/07 12:21:16  james
56  * *** empty log message ***
57  *
58  * Revision 1.13  2008/02/07 12:16:04  james
59  * *** empty log message ***
60  *
61  * Revision 1.12  2008/02/07 11:32:41  james
62  * *** empty log message ***
63  *
64  * Revision 1.11  2008/02/07 11:11:14  staffcvs
65  * *** empty log message ***
66  *
67  * Revision 1.10  2008/02/07 01:02:52  james
68  * *** empty log message ***
69  *
70  * Revision 1.9  2008/02/07 00:43:27  james
71  * *** empty log message ***
72  *
73  * Revision 1.8  2008/02/07 00:39:13  james
74  * *** empty log message ***
75  *
76  * Revision 1.7  2008/02/06 20:26:57  james
77  * *** empty log message ***
78  *
79  * Revision 1.6  2008/02/06 17:53:28  james
80  * *** empty log message ***
81  *
82  * Revision 1.5  2008/02/06 15:53:22  james
83  * *** empty log message ***
84  *
85  * Revision 1.4  2008/02/04 20:23:55  james
86  * *** empty log message ***
87  *
88  * Revision 1.3  2008/02/04 05:45:55  james
89  * ::
90  *
91  * Revision 1.2  2008/02/04 02:05:06  james
92  * *** empty log message ***
93  *
94  * Revision 1.1  2008/02/03 23:31:25  james
95  * *** empty log message ***
96  *
97  */
98 #include "project.h"
99
100
101 static void
102 ansi_move (ANSI * a, CRT_Pos p)
103 {
104   char buf[16];
105   int n;
106   int dx = p.x - a->pos.x;
107   int dy = p.y - a->pos.y;
108
109 //  a->pos.x = ANSI_INVAL;
110
111   if (a->pos.x != ANSI_INVAL)
112     {
113
114       if ((!dx) && (!dy))
115         return;
116
117       if (!dy)
118         {
119           if (dx == 1)
120             {
121               a->terminal->xmit (a->terminal, "\033[C", 3);
122             }
123           else if (dx == -1)
124             {
125               a->terminal->xmit (a->terminal, "\033[D", 3);
126             }
127           else
128             {
129               n = snprintf (buf, sizeof (buf), "\033[%dG", p.x + 1);
130               a->terminal->xmit (a->terminal, buf, n);
131             }
132         }
133       else if (!dx)
134         {
135           if (dy == -1)
136             {
137               a->terminal->xmit (a->terminal, "\033[A", 3);
138             }
139           else if (dy == 1)
140             {
141               a->terminal->xmit (a->terminal, "\033[B", 3);
142             }
143           else if (dy < 0)
144             {
145               n = snprintf (buf, sizeof (buf), "\033[%dA", -dy);
146               a->terminal->xmit (a->terminal, buf, n);
147             }
148           else
149             {
150               n = snprintf (buf, sizeof (buf), "\033[%dB", dy);
151               a->terminal->xmit (a->terminal, buf, n);
152             }
153         }
154       else if (!p.x)
155         {
156           if (dy == 1)
157             {
158               a->terminal->xmit (a->terminal, "\033[E", 3);
159             }
160           else if (dy == -1)
161             {
162               a->terminal->xmit (a->terminal, "\033[F", 3);
163             }
164           else if (dy > 0)
165             {
166               n = snprintf (buf, sizeof (buf), "\033[%dE", dy);
167               a->terminal->xmit (a->terminal, buf, n);
168             }
169           else
170             {
171               n = snprintf (buf, sizeof (buf), "\033[%dF", -dy);
172               a->terminal->xmit (a->terminal, buf, n);
173             }
174         }
175       else
176         {
177           n = snprintf (buf, sizeof (buf), "\033[%d;%dH", p.y + 1, p.x + 1);
178           a->terminal->xmit (a->terminal, buf, n);
179         }
180     }
181   else
182     {
183       n = snprintf (buf, sizeof (buf), "\033[%d;%dH", p.y + 1, p.x + 1);
184       a->terminal->xmit (a->terminal, buf, n);
185     }
186
187   a->pos = p;
188 }
189
190
191 static void
192 ansi_showhide_cursor (ANSI * a, int hide)
193 {
194   if (a->hide_cursor == hide)
195     return;
196
197   if (hide)
198     {
199       a->terminal->xmit (a->terminal, "\033[?25l", 6);
200     }
201   else
202     {
203       a->terminal->xmit (a->terminal, "\033[?25h", 6);
204     }
205
206   a->hide_cursor = hide;
207 }
208
209
210 static void
211 ansi_force_attr_normal (ANSI * a)
212 {
213   a->terminal->xmit (a->terminal, "\033[0m", 4);
214   a->attr = CRT_ATTR_NORMAL;
215   a->color = ANSI_INVAL;
216 }
217
218 static void
219 ansi_set_color (ANSI * a, int color)
220 {
221   int dif;
222   char buf[16];
223   int i;
224   int fg, bg;
225
226   if ((a->color == ANSI_INVAL) || (color != a->color))
227     {
228       fg = CRT_COLOR_FG (color);
229       bg = CRT_COLOR_BG (color);
230
231       if (fg & CRT_COLOR_INTENSITY)
232         {
233           fg += 90;
234         }
235       else
236         {
237           fg += 30;
238         }
239
240       if (bg & CRT_COLOR_INTENSITY)
241         {
242           bg += 100;
243         }
244       else
245         {
246           bg += 40;
247         }
248
249       i = sprintf (buf, "\033[%d;%dm", fg, bg);
250 #if 0
251       fprintf (stderr, "Color set to %d %d %x\n", fg, bg, color);
252 #endif
253
254       a->terminal->xmit (a->terminal, buf, i);
255       a->color = color;
256     }
257 }
258
259 static void
260 ansi_set_attr (ANSI * a, int attr)
261 {
262   int dif;
263
264   dif = attr ^ a->attr;
265
266   if (!dif)
267     return;
268
269   a->attr = attr;
270
271 #if 0
272   if (attr == CRT_ATTR_NORMAL)
273     {
274       ansi_force_attr_normal (a);
275       return;
276     }
277 #endif
278
279   if (dif & CRT_ATTR_UNDERLINE)
280     {
281       if (attr & CRT_ATTR_UNDERLINE)
282         {
283           a->terminal->xmit (a->terminal, "\033[4m", 4);
284         }
285       else
286         {
287           a->terminal->xmit (a->terminal, "\033[24m", 5);
288         }
289     }
290   if (dif & CRT_ATTR_REVERSE)
291     {
292       if (attr & CRT_ATTR_REVERSE)
293         {
294           a->terminal->xmit (a->terminal, "\033[7m", 4);
295         }
296       else
297         {
298           a->terminal->xmit (a->terminal, "\033[27m", 5);
299         }
300     }
301   if (dif & CRT_ATTR_BOLD)
302     {
303       if (attr & CRT_ATTR_BOLD)
304         {
305           a->terminal->xmit (a->terminal, "\033[1m", 4);
306         }
307       else
308         {
309           a->terminal->xmit (a->terminal, "\033[21m", 5);
310           a->terminal->xmit (a->terminal, "\033[22m", 5);
311         }
312     }
313
314 }
315
316
317 static void
318 ansi_render (ANSI * a, CRT_CA ca)
319 {
320   int dif;
321
322   if (ca.chr < 32)
323     ca.chr = ' ';
324   if (ca.chr > 126)
325     ca.chr = ' ';
326
327   ansi_set_attr (a, ca.attr);
328   ansi_set_color (a, ca.color);
329
330   a->terminal->xmit (a->terminal, &ca.chr, 1);
331
332   a->pos.x++;
333
334 /*Can't easily wrap round here as don't know size of destination screen*/
335 /*so invalidate the cached cursor position*/
336
337   if (a->pos.x >= a->size.x)
338     a->pos.x = ANSI_INVAL;
339
340 }
341
342 static void
343 ansi_cls (ANSI * a)
344 {
345   CRT_Pos p = { 0 };
346
347   crt_cls (&a->crt);
348
349   ansi_set_attr (a, CRT_ATTR_NORMAL);
350   ansi_set_color (a, CRT_COLOR_NORMAL);
351   ansi_move (a, p);
352   a->terminal->xmit (a->terminal, "\033[2J", 4);
353 /*different emulators leave cursor in different places after cls differently*/
354   a->pos.x = ANSI_INVAL;
355 }
356
357 #if 0
358 int
359 ansi_scroll_up (ANSI * a, CRT_Pos s, CRT_Pos e)
360 {
361   char buf[16];
362   int i;
363   if (s.x)
364     return -1;
365   if (e.x != (CRT_COLS - 1))
366     return -1;
367   if (s.y)
368     return -1;
369   if (s.y >= a->size.y)
370     return -1;
371
372   ansi_showhide_cursor (a, 1);
373   i = sprintf (buf, "\033[%d;%dr", s.y + 1, e.y + 1);
374   a->terminal->xmit (a->terminal, buf, i);
375   i = sprintf (buf, "\033[%d;%dH", e.y + 1, 0);
376   a->terminal->xmit (a->terminal, buf, i);
377   a->terminal->xmit (a->terminal, "\033D", 2);
378   a->terminal->xmit (a->terminal, "\033[r", 3);
379
380   s.y = e.y;
381   crt_erase (&a->crt, s, e, 1);
382
383   a->pos.x = ANSI_INVAL;
384
385   return 0;
386 }
387
388
389 static void
390 ansi_spot_scroll_up (ANSI * a, CRT * c)
391 {
392   int l, n, p;
393
394   l = c->sh.e.x - c->sh.s.x;
395   l++;
396   l *= sizeof (CRT_CA);
397
398   n = c->sh.e.y - c->sh.s.y;
399   p = CRT_ADDR_POS (&c->sh.s);
400
401   while (n--)
402     {
403       if (memcmp (&c->screen[p], &a->crt.screen[p + CRT_COLS], l))
404         return;
405       p += CRT_COLS;
406     }
407
408   if (ansi_scroll_up (a, c->sh.s, c->sh.e))
409     return;
410
411   n = c->sh.e.y - c->sh.s.y;
412   p = CRT_ADDR_POS (&c->sh.s);
413
414   while (n--)
415     {
416       memcpy (&a->crt.screen[p], &a->crt.screen[p + CRT_COLS], l);
417       p += CRT_COLS;
418     }
419
420   c->sh.dir = 0;                //FIXME: horrid hack
421
422 }
423
424 static void
425 ansi_spot_scroll (ANSI * a, CRT * c)
426 {
427
428   switch (c->sh.dir)
429     {
430     case -1:
431 /*we only care about up for the moment */
432       ansi_spot_scroll_up (a, c);
433       break;
434     }
435
436   return;
437 }
438
439 #endif
440
441
442 static void
443 ansi_draw_line (ANSI * a, CRT_CA * cap, int y)
444 {
445   CRT_Pos p = { 0, y };
446   CRT_CA *acap = &a->crt.screen[CRT_ADDR_POS (&p)];
447
448   for (p.x = 0; p.x < CRT_COLS; ++p.x)
449     {
450       if (p.x >= a->size.x)
451         continue;
452
453       if (crt_ca_cmp (*acap, *cap))
454         {
455           ansi_showhide_cursor (a, 1);
456           *acap = *cap;
457
458           ansi_move (a, p);
459           ansi_render (a, *acap);
460         }
461
462       acap++;
463       cap++;
464     }
465 }
466
467 static void
468 ansi_resize_check (ANSI * a)
469 {
470
471   if (!crt_pos_cmp (a->terminal->size, a->size))
472     return;
473
474   terminal_getsize (a->terminal);
475
476   a->size = a->terminal->size;
477
478   a->pos.x = ANSI_INVAL;
479   a->hide_cursor = ANSI_INVAL;
480
481   crt_reset (&a->crt);
482
483 // FIXME: -- echos back crap?
484 //  a->terminal->xmit (a->terminal, "\033[c", 3);
485
486   ansi_cls (a);
487   a->terminal->xmit (a->terminal, "\033=", 2);
488   a->terminal->xmit (a->terminal, "\033[?6l", 5);
489   a->terminal->xmit (a->terminal, "\033[r", 3);
490
491 }
492
493 #define HISTORY_GUESS_SCROLL 24 /*guess all 24 lines usually scroll */
494 /*if they haven't then ansi_draw will patch it up*/
495
496 static void
497 ansi_history (ANSI * a, History * h)
498 {
499   char buf[32];
500   int i;
501 /*Do we need to catch up on history?*/
502
503   if (a->history_ptr == h->wptr)
504     return;
505   ansi_resize_check (a);
506
507   if ((a->size.x < CRT_COLS) || (a->size.y < CRT_ROWS))
508     return;
509
510   ansi_force_attr_normal (a);
511   ansi_set_color (a, CRT_COLOR_NORMAL);
512
513   i = sprintf (buf, "\033[%d;%dr", 1, HISTORY_GUESS_SCROLL);
514   a->terminal->xmit (a->terminal, buf, i);
515
516
517   while (a->history_ptr != h->wptr)
518     {
519
520       History_ent *e = &h->lines[a->history_ptr];
521
522       HISTORY_INC (h, a->history_ptr);
523
524       if (!e->valid)
525         continue;
526
527       /*If so write the line ot the top of the screen */
528       ansi_draw_line (a, e->line, 0);
529
530
531       /*Roll HISTORY_GUESS_SCROLL lines up putting the top line into the xterm's history */
532
533
534       /*Make extra lines a predictable colour*/
535       ansi_set_color (a, CRT_COLOR_NORMAL);
536
537       ansi_showhide_cursor (a, 1);
538       i = sprintf (buf, "\033[%d;%dH", HISTORY_GUESS_SCROLL, 1);
539       a->terminal->xmit (a->terminal, buf, i);
540       a->terminal->xmit (a->terminal, "\033D", 2);
541       a->pos.x = ANSI_INVAL;
542
543       /*now do the same in our image of the screen */
544
545       {
546         CRT_Pos s = { 0 }
547         , e =
548         {
549         0};
550
551         /*scroll lines up */
552         for (s.y++; s.y < HISTORY_GUESS_SCROLL; s.y++, e.y++)
553           {
554             memcpy (&a->crt.screen[CRT_ADDR_POS (&e)],
555                     &a->crt.screen[CRT_ADDR_POS (&s)],
556                     sizeof (CRT_CA) * CRT_COLS);
557           }
558
559         /* erase new line */
560         s.y = e.y;
561         e.x = CRT_COLS - 1;
562         crt_erase (&a->crt, s, e, 1,CRT_COLOR_NORMAL);
563       }
564
565     }
566 /*reset margins*/
567   a->terminal->xmit (a->terminal, "\033[r", 3);
568   a->pos.x = ANSI_INVAL;
569
570 }
571
572
573
574
575 static void
576 ansi_draw (ANSI * a, CRT * c)
577 {
578   CRT_Pos p;
579   int o;
580   int hidden_cursor = 0;
581
582   ansi_resize_check (a);
583
584
585
586   for (p.y = 0; p.y < CRT_ROWS; ++p.y)
587     {
588       if (p.y >= a->size.y)
589         continue;
590
591       ansi_draw_line (a, &c->screen[CRT_ADDR (p.y, 0)], p.y);
592
593     }
594
595
596   if ((CRT_COLS > a->size.x) || (CRT_ROWS > a->size.y))
597     {
598       char msg[1024];           // = "Window is too small";
599       int i;
600       p.x = 0;
601       p.y = 0;
602
603       i =
604         sprintf (msg, "Window too small (%dx%d need %dx%d)", a->size.x,
605                  a->size.y, CRT_COLS, CRT_ROWS);
606
607       ansi_showhide_cursor (a, 1);
608       ansi_set_attr (a, CRT_ATTR_REVERSE);
609       ansi_set_color (a, CRT_MAKE_COLOR (CRT_COLOR_WHITE, CRT_COLOR_RED));
610       ansi_move (a, p);
611
612       a->terminal->xmit (a->terminal, msg, i);
613       a->pos.x = ANSI_INVAL;
614     }
615
616
617   if ((c->pos.x >= a->size.x) || (c->pos.y >= a->size.y))
618     {
619       ansi_showhide_cursor (a, 1);
620       return;
621     }
622
623   a->crt.pos = c->pos;
624   ansi_move (a, a->crt.pos);
625
626   a->crt.hide_cursor = c->hide_cursor;
627   ansi_showhide_cursor (a, a->crt.hide_cursor);
628 }
629
630 static void
631 ansi_reset (ANSI * a, CRT * c)
632 {
633   a->size.x = -1;
634   ansi_draw (a, c ? c : &a->crt);
635 }
636
637 static void
638 ansi_terminal_reset (ANSI * a)
639 {
640   CRT_Pos p = { 0, CRT_ROWS };
641   ansi_force_attr_normal (a);
642
643   ansi_move (a, p);
644 }
645
646 static void
647 ansi_flush_escape (ANSI * a, Context * c)
648 {
649   ANSI_Parser *p = &a->parser;
650   int i;
651
652   for (i = 0; i < p->escape_ptr; ++i)
653     {
654       keydis_key (c->k, c, p->escape_buf[i]);
655     }
656
657   p->escape_ptr = 0;
658   p->in_escape = 0;
659 }
660
661 static void
662 ansi_parse_deckey (ANSI * a, Context * c)
663 {
664   ANSI_Parser *p = &a->parser;
665   if ((p->escape_buf[1] != '[') && (p->escape_buf[1] != 'O'))
666     {
667       ansi_flush_escape (a, c);
668       return;
669     }
670
671   if ((p->escape_buf[2] >= 'A') || (p->escape_buf[2] <= 'Z'))
672     {
673       keydis_key (c->k, c, KEY_UP + (p->escape_buf[2] - 'A'));
674     }
675   else if ((p->escape_buf[2] >= 'a') || (p->escape_buf[2] <= 'z'))
676     {
677       keydis_key (c->k, c, KEY_154 + (p->escape_buf[2] - 'a'));
678     }
679   else
680     {
681       ansi_flush_escape (a, c);
682       return;
683     }
684   p->in_escape = 0;
685   p->escape_ptr = 0;
686 }
687
688 static void
689 ansi_parse_ansikey (ANSI * a, Context * c)
690 {
691   ANSI_Parser *p = &a->parser;
692
693   if ((p->escape_buf[1] != '[') || (p->escape_buf[3] != '~'))
694     {
695       ansi_flush_escape (a, c);
696       return;
697     }
698   if ((p->escape_buf[2] >= '0') || (p->escape_buf[2] <= '9'))
699     {
700       keydis_key (c->k, c, KEY_180 + (p->escape_buf[2] - '0'));
701     }
702   else
703     {
704       ansi_flush_escape (a, c);
705       return;
706     }
707
708   p->in_escape = 0;
709   p->escape_ptr = 0;
710 }
711
712
713
714 static void
715 ansi_parse_escape (ANSI * a, Context * c)
716 {
717   ANSI_Parser *p = &a->parser;
718   switch (p->escape_ptr)
719     {
720     case 0:
721     case 1:
722       return;
723     case 2:
724       switch (p->escape_buf[1])
725         {
726         case '[':
727         case 'O':
728           break;
729         default:
730           ansi_flush_escape (a, c);
731         }
732       break;
733     case 3:
734       switch (p->escape_buf[1])
735         {
736         case 'O':
737           ansi_parse_deckey (a, c);
738           break;
739         case '[':
740           if ((p->escape_buf[2] >= 'A') && (p->escape_buf[2] <= 'Z'))
741             ansi_parse_deckey (a, c);
742           break;
743         default:
744           ansi_flush_escape (a, c);
745         }
746       break;
747     case 4:
748       switch (p->escape_buf[1])
749         {
750         case '[':
751           ansi_parse_ansikey (a, c);
752           break;
753         default:
754           ansi_flush_escape (a, c);
755         }
756       break;
757     case 5:
758       ansi_flush_escape (a, c);
759     }
760 }
761
762
763 static void
764 ansi_check_escape (ANSI * a, Context * c)
765 {
766   ANSI_Parser *p = &a->parser;
767   struct timeval now, diff;
768   gettimeofday (&now, NULL);
769   timersub (&now, &p->last_escape, &diff);
770
771 #if 0
772   fprintf (stderr, "ie %d tl %d.%06d eb %d\n",
773            p->in_escape, diff.tv_sec, diff.tv_usec, p->escape_ptr);
774 #endif
775
776   if (!p->in_escape)
777     return;
778
779
780   /*Time up? */
781   if (diff.tv_sec || (diff.tv_usec > ANSI_ESCAPE_TIMEOUT))
782     ansi_flush_escape (a, c);
783
784 }
785
786
787 static void
788 ansi_parse_char (ANSI * a, Context * c, int ch)
789 {
790   ANSI_Parser *p = &a->parser;
791
792
793 /*See if it's time to flush the escape*/
794   ansi_check_escape (a, c);
795
796   if (ch == 033)
797     {
798       if (p->in_escape)
799         ansi_flush_escape (a, c);
800
801       p->in_escape++;
802       p->escape_ptr = 0;
803       gettimeofday (&p->last_escape, NULL);
804     }
805
806   if (p->in_escape)
807     {
808       p->escape_buf[p->escape_ptr++] = ch;
809       ansi_parse_escape (a, c);
810     }
811   else
812     {
813       keydis_key (c->k, c, ch);
814     }
815
816 }
817
818 static void
819 ansi_parse (ANSI * a, Context * c, char *buf, int len)
820 {
821   while (len--)
822     ansi_parse_char (a, c, *(buf++));
823 }
824
825 int
826 ansi_dispatch (ANSI * a, Context * c)
827 {
828   char buf[1024];
829   int red;
830
831   ansi_check_escape (a, c);
832
833
834   if (!a->terminal)
835     return 0;
836
837   red = a->terminal->recv (a->terminal, buf, sizeof (buf));
838   if (red <= 0)
839     return red;
840
841 #if 0
842   if (*buf == 3)
843     return -1;
844 #endif
845
846 #if 0
847   if (*buf == 2)
848     {
849 #if  0
850       a->history_ptr = c->h->wptr;
851       HISTORY_INC (c->h, a->history_ptr);
852 #endif
853       return -1;
854     }
855 #endif
856
857
858   ansi_parse (a, c, buf, red);
859
860   return 0;
861 }
862
863
864 static void
865 ansi_update (ANSI * a, Context * c)
866 {
867   ansi_history (a, c->h);
868   ansi_draw (a, &c->v->crt);
869 }
870
871 static void
872 ansi_free (ANSI * a)
873 {
874   a->terminal_reset (a);
875   if (a->terminal)
876     a->terminal->close (a->terminal);
877
878   free (a);
879
880 }
881
882 ANSI *
883 ansi_new_from_terminal (TTY * t)
884 {
885   ANSI *ret;
886
887   ret = malloc (sizeof (ANSI));
888   memset (ret, 0, sizeof (ANSI));
889
890   ret->terminal = t;
891
892   ret->update = ansi_update;
893   ret->reset = ansi_reset;
894   ret->terminal_reset = ansi_terminal_reset;
895   ret->close = ansi_free;
896   ret->dispatch = ansi_dispatch;
897
898   return ret;
899 }