source: neercs/trunk/src/term.c @ 2402

Last change on this file since 2402 was 2402, checked in by Jean-Yves Lamoureux, 14 years ago
  • main.c now contains only main()
  • Property svn:keywords set to Id
File size: 20.2 KB
Line 
1/*
2 *  neercs        console-based window manager
3 *  Copyright (c) 2006 Sam Hocevar <sam@zoy.org>
4 *                All Rights Reserved
5 *
6 *  $Id: term.c 2402 2008-06-15 13:11:19Z jylam $
7 *
8 *  This program is free software. It comes without any warranty, to
9 *  the extent permitted by applicable law. You can redistribute it
10 *  and/or modify it under the terms of the Do What The Fuck You Want
11 *  To Public License, Version 2, as published by Sam Hocevar. See
12 *  http://sam.zoy.org/wtfpl/COPYING for more details.
13 */
14
15#include "config.h"
16
17#include <stdlib.h>
18#include <stdio.h>
19#include <string.h>
20#include <pty.h>
21#include <unistd.h>
22#include <fcntl.h>
23
24#include <cucul.h>
25#include <caca.h>
26
27#include "neercs.h"
28
29static void ansi_parse_grcm(struct screen *,
30                            unsigned int, unsigned int const *);
31
32long int import_term(struct screen_list *screen_list, struct screen *sc, void const *data, unsigned int size)
33{
34    unsigned char const *buffer = (unsigned char const*)data;
35    unsigned int i, j, k,skip, dummy = 0;
36    unsigned int width, height;
37    uint32_t savedattr, scrolled = 0;
38    int x = 0, y = 0, save_x = 0, save_y = 0;
39
40    width = cucul_get_canvas_width(sc->cv);
41    height = cucul_get_canvas_height(sc->cv);
42    x = cucul_get_cursor_x(sc->cv);
43    y = cucul_get_cursor_y(sc->cv);
44
45    if(!sc->init)
46    {
47        sc->dfg = CUCUL_DEFAULT;
48        sc->dbg = CUCUL_DEFAULT;
49
50        cucul_set_color_ansi(sc->cv, sc->dfg, sc->dbg);
51        sc->clearattr = cucul_get_attr(sc->cv, -1, -1);
52
53        ansi_parse_grcm(sc, 1, &dummy);
54
55        sc->init = 1;
56    }
57
58    for(i = 0; i < size; i += skip)
59    {
60        uint32_t ch = 0;
61        int wch = 0;
62
63        skip = 1;
64
65        if(buffer[i] == '\r')
66        {
67            x = 0;
68        }
69
70        else if(buffer[i] == '\n')
71        {
72            x = 0;
73            y++;
74        }
75        else if(buffer[i] == '\a')
76        {
77            screen_list->in_bell = 10;
78            sc->bell = 10;
79        }
80
81        else if(buffer[i] == '\t')
82        {
83            x = (x + 7) & ~7;
84        }
85
86        else if(buffer[i] == '\x08')
87        {
88            if(x > 0)
89                x--;
90        }
91
92        /* If there are not enough characters to parse the escape sequence,
93         * wait until the next try. We require 3. */
94        else if(buffer[i] == '\x1b' && i + 2 >= size)
95            break;
96
97        /* XXX: What the fuck is this shit? */
98        else if(buffer[i] == '\x1b' && buffer[i + 1] == '('
99                 && buffer[i + 2] == 'B')
100        {
101            skip += 2;
102        }
103
104        /* Interpret escape commands, as per Standard ECMA-48 "Control
105         * Functions for Coded Character Sets", 5.4. Control sequences. */
106        else if(buffer[i] == '\x1b' && buffer[i + 1] == '[')
107        {
108            unsigned int argc = 0, argv[101];
109            unsigned int param, inter, final;
110
111        /* Compute offsets to parameter bytes, intermediate bytes and
112         * to the final byte. Only the final byte is mandatory, there
113         * can be zero of the others.
114         * 0  param=2             inter                 final           final+1
115         * +-----+------------------+---------------------+-----------------+
116         * | CSI | parameter bytes  | intermediate bytes  |   final byte    |
117         * |     |   0x30 - 0x3f    |    0x20 - 0x2f      |   0x40 - 0x7e   |
118         * | ^[[ | 0123456789:;<=>? | SPC !"#$%&'()*+,-./ | azAZ@[\]^_`{|}~ |
119         * +-----+------------------+---------------------+-----------------+
120         */
121            param = 2;
122
123            for(inter = param; i + inter < size; inter++)
124                if(buffer[i + inter] < 0x30 || buffer[i + inter] > 0x3f)
125                    break;
126
127            for(final = inter; i + final < size; final++)
128                if(buffer[i + final] < 0x20 || buffer[i + final] > 0x2f)
129                    break;
130
131            if(i + final >= size
132                || buffer[i + final] < 0x40 || buffer[i + final] > 0x7e)
133                break; /* Invalid Final Byte */
134
135            skip += final;
136
137            /* Sanity checks */
138            if(param < inter && buffer[i + param] >= 0x3c)
139            {
140                /* Private sequence, only parse what we know */
141                debug("ansi import: private sequence \"^[[%.*s\"",
142                      final - param + 1, buffer + i + param);
143                continue; /* Private sequence, skip it entirely */
144            }
145
146            if(final - param > 100)
147                continue; /* Suspiciously long sequence, skip it */
148
149            /* Parse parameter bytes as per ECMA-48 5.4.2: Parameter string
150             * format */
151            if(param < inter)
152            {
153                argv[0] = 0;
154                for(j = param; j < inter; j++)
155                {
156                    if(buffer[i + j] == ';')
157                        argv[++argc] = 0;
158                    else if(buffer[i + j] >= '0' && buffer[i + j] <= '9')
159                        argv[argc] = 10 * argv[argc] + (buffer[i + j] - '0');
160                }
161                argc++;
162            }
163
164            /* Interpret final byte. The code representations are given in
165             * ECMA-48 5.4: Control sequences, and the code definitions are
166             * given in ECMA-48 8.3: Definition of control functions. */
167            switch(buffer[i + final])
168            {
169            case 'H': /* CUP (0x48) - Cursor Position */
170                x = (argc > 1 && argv[1] > 0) ? argv[1] - 1 : 0;
171                y = (argc > 0 && argv[0] > 0) ? argv[0] - 1 : 0;
172                break;
173            case 'A': /* CUU (0x41) - Cursor Up */
174                y -= argc ? argv[0] : 1;
175                if(y < 0)
176                    y = 0;
177                break;
178            case 'B': /* CUD (0x42) - Cursor Down */
179                y += argc ? argv[0] : 1;
180                break;
181            case 'C': /* CUF (0x43) - Cursor Right */
182                x += argc ? argv[0] : 1;
183                break;
184            case 'D': /* CUB (0x44) - Cursor Left */
185                x -= argc ? argv[0] : 1;
186                if(x < 0)
187                    x = 0;
188                break;
189            case 'G': /* CHA (0x47) - Cursor Character Absolute */
190                x = (argc && argv[0] > 0) ? argv[0] - 1 : 0;
191                break;
192            case 'J': /* ED (0x4a) - Erase In Page */
193                savedattr = cucul_get_attr(sc->cv, -1, -1);
194                cucul_set_attr(sc->cv, sc->clearattr);
195                if(!argc || argv[0] == 0)
196                {
197                    cucul_draw_line(sc->cv, x, y, width, y, ' ');
198                    cucul_fill_box(sc->cv, 0, y + 1, width - 1, height - 1, ' ');
199                }
200                else if(argv[0] == 1)
201                {
202                    cucul_fill_box(sc->cv, 0, 0, width - 1, y - 1, ' ');
203                    cucul_draw_line(sc->cv, 0, y, x, y, ' ');
204                }
205                else if(argv[0] == 2)
206                {
207                    //x = y = 0;
208                    cucul_fill_box(sc->cv, 0, 0, width, height - 1, ' ');
209                }
210                cucul_set_attr(sc->cv, savedattr);
211                break;
212            case 'K': /* EL (0x4b) - Erase In Line */
213                if(!argc || argv[0] == 0)
214                    cucul_draw_line(sc->cv, x, y, width, y, ' ');
215                else if(argv[0] == 1)
216                    cucul_draw_line(sc->cv, 0, y, x, y, ' ');
217                else if(argv[0] == 2)
218                    if((unsigned int)x < width)
219                        cucul_draw_line(sc->cv, x, y, width - 1, y, ' ');
220                //x = width;
221                break;
222            case 'P': /* DCH (0x50) - Delete Character */
223                if(!argc || argv[0] == 0)
224                    argv[0] = 1; /* echo -ne 'foobar\r\e[0P\n' */
225                for(j = 0; (unsigned int)(j + argv[0]) < width; j++)
226                {
227                    cucul_put_char(sc->cv, j, y,
228                                   cucul_get_char(sc->cv, j + argv[0], y));
229                    cucul_put_attr(sc->cv, j, y,
230                                   cucul_get_attr(sc->cv, j + argv[0], y));
231                }
232#if 0
233                savedattr = cucul_get_attr(sc->cv, -1, -1);
234                cucul_set_attr(sc->cv, sc->clearattr);
235                for( ; (unsigned int)j < width; j++)
236                    cucul_put_char(sc->cv, j, y, ' ');
237                cucul_set_attr(sc->cv, savedattr);
238#endif
239            case 'X': /* ECH (0x58) - Erase Character */
240                if(argc && argv[0])
241                {
242                    savedattr = cucul_get_attr(sc->cv, -1, -1);
243                    cucul_set_attr(sc->cv, sc->clearattr);
244                    cucul_draw_line(sc->cv, x, y, x + argv[0] - 1, y, ' ');
245                    cucul_set_attr(sc->cv, savedattr);
246                }
247            case 'd': /* VPA (0x64) - Line Position Absolute */
248                y = (argc && argv[0] > 0) ? argv[0] - 1 : 0;
249                break;
250            case 'f': /* HVP (0x66) - Character And Line Position */
251                x = (argc > 1 && argv[1] > 0) ? argv[1] - 1 : 0;
252                y = (argc > 0 && argv[0] > 0) ? argv[0] - 1 : 0;
253                break;
254            case 'r': /* SS  (0x72) - Scroll Screen FIXME */
255
256                if((argv[0]==0 && argv[1]==0)  || scrolled) break;
257                for(j = argv[0]-1; j < argv[1]-1; j++)
258                {
259                    for(k = 0; k < width; k++)
260                    {
261                        cucul_put_char(sc->cv, k, j, cucul_get_char(sc->cv, k, j+1));
262                        cucul_put_attr(sc->cv, k, j, cucul_get_attr(sc->cv, k, j+1));
263                    }
264                }
265                cucul_draw_line(sc->cv, 0, argv[1]-1, width, argv[1]-1, ' ');
266                scrolled = 1;
267                break;
268            case 'h': /* SM (0x68) - FIXME */
269                debug("ansi import: set mode %i", argc ? (int)argv[0] : -1);
270                break;
271            case 'l': /* RM (0x6c) - FIXME */
272                debug("ansi import: reset mode %i", argc ? (int)argv[0] : -1);
273                break;
274            case 'm': /* SGR (0x6d) - Select Graphic Rendition */
275                if(argc)
276                    ansi_parse_grcm(sc, argc, argv);
277                else
278                    ansi_parse_grcm(sc, 1, &dummy);
279                break;
280            case 's': /* Private (save cursor position) */
281                save_x = x;
282                save_y = y;
283                break;
284            case 'u': /* Private (reload cursor position) */
285                x = save_x;
286                y = save_y;
287                break;
288            default:
289                debug("ansi import: unknown command \"^[[%.*s\"",
290                      final - param + 1, buffer + i + param);
291                break;
292            }
293        }
294
295        /* Parse OSC stuff. */
296        else if(buffer[i] == '\x1b' && buffer[i + 1] == ']')
297        {
298            char *string;
299            unsigned int command = 0;
300            unsigned int mode = 2, semicolon, final;
301
302            for(semicolon = mode; i + semicolon < size; semicolon++)
303            {
304                if(buffer[i + semicolon] < '0' || buffer[i + semicolon] > '9')
305                    break;
306                command = 10 * command + (buffer[i + semicolon] - '0');
307            }
308
309            if(i + semicolon >= size || buffer[i + semicolon] != ';')
310                break; /* Invalid Mode */
311
312            for(final = semicolon + 1; i + final < size; final++)
313                if(buffer[i + final] < 0x20)
314                    break;
315
316            if(i + final >= size || buffer[i + final] != '\a')
317                break; /* Not enough data or no bell found */
318                /* FIXME: XTerm also reacts to <ESC><backslash> and <ST> */
319                /* FIXME: differenciate between not enough data (try again)
320                 *        and invalid data (print shit) */
321
322            skip += final;
323
324            string = malloc(final - (semicolon + 1) + 1);
325            memcpy(string, buffer + i + (semicolon + 1), final - (semicolon + 1));
326            string[final - (semicolon + 1)] = '\0';
327            debug("ansi import: got OSC command %i string '%s'", command,
328                  string);
329            if(command == 0 || command == 2)
330            {
331                if(sc->title)
332                    free(sc->title);
333                sc->title = string;
334            }
335            else
336                free(string);
337        }
338
339        /* Get the character we’re going to paste */
340        else
341        {
342            unsigned int bytes;
343
344            if(i + 6 < size)
345                ch = cucul_utf8_to_utf32((char const *)(buffer + i), &bytes);
346            else
347            {
348                /* Add a trailing zero to what we're going to read */
349                char tmp[7];
350                memcpy(tmp, buffer + i, size - i);
351                tmp[size - i] = '\0';
352                ch = cucul_utf8_to_utf32(tmp, &bytes);
353            }
354
355            if(!bytes)
356            {
357                /* If the Unicode is invalid, assume it was latin1. */
358                ch = buffer[i];
359                bytes = 1;
360            }
361            wch = cucul_utf32_is_fullwidth(ch) ? 2 : 1;
362            skip += bytes - 1;
363        }
364
365        /* Wrap long lines or grow horizontally */
366        while((unsigned int)x + wch > width)
367        {
368            x -= width;
369            y++;
370        }
371
372        /* Scroll or grow vertically */
373        if((unsigned int)y >= height)
374        {
375            int lines = (y - height) + 1;
376
377            savedattr = cucul_get_attr(sc->cv, -1, -1);
378
379            for(j = 0; j + lines < height; j++)
380            {
381                for(k = 0; k < width; k++)
382                {
383                    cucul_put_char(sc->cv, k, j, cucul_get_char(sc->cv, k, j + lines));
384                    cucul_put_attr(sc->cv, k, j, cucul_get_attr(sc->cv, k, j + lines));
385                }
386            }
387            cucul_set_attr(sc->cv, sc->clearattr);
388            cucul_fill_box(sc->cv, 0, height - lines,
389                                   width - 1, height - 1, ' ');
390            y -= lines;
391            cucul_set_attr(sc->cv, savedattr);
392        }
393
394        /* Now paste our character, if any */
395        if(wch)
396        {
397            cucul_put_char(sc->cv, x, y, ch);
398            x += wch;
399        }
400    }
401
402    cucul_gotoxy(sc->cv, x, y);
403
404    return i;
405}
406
407/* XXX : ANSI loader helper */
408
409static void ansi_parse_grcm(struct screen *sc,
410                            unsigned int argc, unsigned int const *argv)
411{
412    static uint8_t const ansi2cucul[] =
413    {
414        CUCUL_BLACK, CUCUL_RED, CUCUL_GREEN, CUCUL_BROWN,
415        CUCUL_BLUE, CUCUL_MAGENTA, CUCUL_CYAN, CUCUL_LIGHTGRAY
416    };
417
418    unsigned int j;
419    uint8_t efg, ebg; /* Effective (libcucul) fg/bg */
420
421    for(j = 0; j < argc; j++)
422    {
423        /* Defined in ECMA-48 8.3.117: SGR - SELECT GRAPHIC RENDITION */
424        if(argv[j] >= 30 && argv[j] <= 37)
425            sc->fg = ansi2cucul[argv[j] - 30];
426        else if(argv[j] >= 40 && argv[j] <= 47)
427            sc->bg = ansi2cucul[argv[j] - 40];
428        else if(argv[j] >= 90 && argv[j] <= 97)
429            sc->fg = ansi2cucul[argv[j] - 90] + 8;
430        else if(argv[j] >= 100 && argv[j] <= 107)
431            sc->bg = ansi2cucul[argv[j] - 100] + 8;
432        else switch(argv[j])
433        {
434        case 0: /* default rendition */
435            sc->fg = sc->dfg;
436            sc->bg = sc->dbg;
437            sc->bold = sc->blink = sc->italics = sc->negative
438             = sc->concealed = sc->underline = sc->faint = sc->strike
439             = sc->proportional = 0;
440            break;
441        case 1: /* bold or increased intensity */
442            sc->bold = 1;
443            break;
444        case 2: /* faint, decreased intensity or second colour */
445            sc->faint = 1;
446            break;
447        case 3: /* italicized */
448            sc->italics = 1;
449            break;
450        case 4: /* singly underlined */
451            sc->underline = 1;
452            break;
453        case 5: /* slowly blinking (less then 150 per minute) */
454        case 6: /* rapidly blinking (150 per minute or more) */
455            sc->blink = 1;
456            break;
457        case 7: /* negative image */
458            sc->negative = 1;
459            break;
460        case 8: /* concealed characters */
461            sc->concealed = 1;
462            break;
463        case 9: /* crossed-out (characters still legible but marked as to be
464                 * deleted */
465            sc->strike = 1;
466            break;
467        case 21: /* doubly underlined */
468            sc->underline = 1;
469            break;
470        case 22: /* normal colour or normal intensity (neither bold nor
471                  * faint) */
472            sc->bold = sc->faint = 0;
473            break;
474        case 23: /* not italicized, not fraktur */
475            sc->italics = 0;
476            break;
477        case 24: /* not underlined (neither singly nor doubly) */
478            sc->underline = 0;
479            break;
480        case 25: /* steady (not blinking) */
481            sc->blink = 0;
482            break;
483        case 26: /* (reserved for proportional spacing as specified in CCITT
484                  * Recommendation T.61) */
485            sc->proportional = 1;
486            break;
487        case 27: /* positive image */
488            sc->negative = 0;
489            break;
490        case 28: /* revealed characters */
491            sc->concealed = 0;
492            break;
493        case 29: /* not crossed out */
494            sc->strike = 0;
495            break;
496        case 38: /* (reserved for future standardization, intended for setting
497                  * character foreground colour as specified in ISO 8613-6
498                  * [CCITT Recommendation T.416]) */
499            break;
500        case 39: /* default display colour (implementation-defined) */
501            sc->fg = sc->dfg;
502            break;
503        case 48: /* (reserved for future standardization, intended for setting
504                  * character background colour as specified in ISO 8613-6
505                  * [CCITT Recommendation T.416]) */
506            break;
507        case 49: /* default background colour (implementation-defined) */
508            sc->bg = sc->dbg;
509            break;
510        case 50: /* (reserved for cancelling the effect of the rendering
511                  * aspect established by parameter value 26) */
512            sc->proportional = 0;
513            break;
514        default:
515            debug("ansi import: unknown sgr %i", argv[j]);
516            break;
517        }
518    }
519
520    if(sc->concealed)
521    {
522        efg = ebg = CUCUL_TRANSPARENT;
523    }
524    else
525    {
526        efg = sc->negative ? sc->bg : sc->fg;
527        ebg = sc->negative ? sc->fg : sc->bg;
528
529        if(sc->bold)
530        {
531            if(efg < 8)
532                efg += 8;
533            else if(efg == CUCUL_DEFAULT)
534                efg = CUCUL_WHITE;
535        }
536    }
537
538    cucul_set_color_ansi(sc->cv, efg, ebg);
539}
540
541int create_pty(char *cmd, unsigned int w, unsigned int h, int *cpid)
542{
543    char **argv;
544    int fd;
545    pid_t pid;
546
547    pid = forkpty(&fd, NULL, NULL, NULL);
548    if(pid < 0)
549    {
550        fprintf(stderr, "forkpty() error\n");
551        return -1;
552    }
553    else if(pid == 0)
554    {
555        set_tty_size(0, w, h);
556        putenv("CACA_DRIVER=slang");
557        putenv("TERM=xterm");
558        argv = malloc(2 * sizeof(char *));
559        if(!argv)
560        {
561            fprintf(stderr, "Can't allocate memory at %s:%d\n", __FUNCTION__, __LINE__);
562            return -1;
563        }
564        argv[0] = cmd;
565        argv[1] = NULL;
566        execvp(cmd, argv);
567        fprintf(stderr, "execvp() error\n");
568        return -1;
569    }
570
571    *cpid = pid;
572
573    fcntl(fd, F_SETFL, O_NDELAY);
574    return fd;
575}
576
577int set_tty_size(int fd, unsigned int w, unsigned int h)
578{
579    struct winsize ws;
580
581    memset(&ws, 0, sizeof(ws));
582    ws.ws_row = h;
583    ws.ws_col = w;
584    ioctl(fd, TIOCSWINSZ, (char *)&ws);
585
586    return 0;
587}
588
589
590
591int update_terms(struct screen_list* screen_list)
592{
593    int i, refresh = 0;
594    for(i = 0; i < screen_list->count; i++)
595    {
596        if(screen_list->screen[i]->total)
597        {
598            unsigned long int bytes;
599
600            bytes = import_term(screen_list,
601                                screen_list->screen[i],
602                                screen_list->screen[i]->buf,
603                                screen_list->screen[i]->total);
604
605            if(bytes > 0)
606            {
607                screen_list->screen[i]->total -= bytes;
608                memmove(screen_list->screen[i]->buf,
609                        screen_list->screen[i]->buf + bytes,
610                        screen_list->screen[i]->total);
611                refresh = 1;
612            }
613        }
614    }
615    return refresh;
616}
617
Note: See TracBrowser for help on using the repository browser.