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

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