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

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