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