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