source: libcaca/trunk/caca/driver_slang.c @ 2821

Last change on this file since 2821 was 2821, checked in by Sam Hocevar, 11 years ago

Starting refactoring to get rid of libcucul. The initial reason for the
split is rendered moot by the plugin system: when enabled, binaries do
not link directly with libX11 or libGL. I hope this is a step towards
more consisteny and clarity.

  • Property svn:keywords set to Id
File size: 16.7 KB
Line 
1/*
2 *  libcaca       Colour ASCII-Art library
3 *  Copyright (c) 2002-2006 Sam Hocevar <sam@zoy.org>
4 *                All Rights Reserved
5 *
6 *  $Id: driver_slang.c 2821 2008-09-27 13:12:46Z sam $
7 *
8 *  This library 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/*
16 *  This file contains the libcaca SLang input and output driver
17 */
18
19#include "config.h"
20
21#if defined(USE_SLANG)
22
23#if defined(HAVE_SLANG_SLANG_H)
24#   include <slang/slang.h>
25#else
26#   include <slang.h>
27#endif
28
29#include <stdlib.h>
30#include <string.h>
31
32#if defined(HAVE_SIGNAL_H)
33#   include <signal.h>
34#endif
35
36#include "caca.h"
37#include "caca_internals.h"
38
39/*
40 * Global variables
41 */
42
43/* Tables generated by tools/optipal.c */
44static int const slang_palette[2*16*16] =
45{
46     1,  0,   2,  0,   3,  0,   4,  0,   5,  0,   6,  0,   7,  0,   8,  0,
47     9,  0,  10,  0,  11,  0,  12,  0,  13,  0,  14,  0,  15,  0,   0,  8,
48     8,  7,   7,  8,  15,  7,   7, 15,  15,  9,   9, 15,   1,  9,   9,  1,
49     7,  9,   9,  7,   8,  1,   1,  8,   0,  1,  15, 10,  10, 15,   2, 10,
50    10,  2,   7, 10,  10,  7,   8,  2,   2,  8,   0,  2,  15, 11,  11, 15,
51     3, 11,  11,  3,   7, 11,  11,  7,   8,  3,   3,  8,   0,  3,  15, 12,
52    12, 15,   4, 12,  12,  4,   7, 12,  12,  7,   8,  4,   4,  8,   0,  4,
53    15, 13,  13, 15,   5, 13,  13,  5,   7, 13,  13,  7,   8,  5,   5,  8,
54     0,  5,  15, 14,  14, 15,   6, 14,  14,  6,   7, 14,  14,  7,   8,  6,
55     6,  8,   0,  6,   4,  6,   6,  4,  12, 14,  14, 12,   6,  2,   2,  6,
56    14, 10,  10, 14,   2,  3,   3,  2,  10, 11,  11, 10,   3,  1,   1,  3,
57    11,  9,   9, 11,   1,  5,   5,  1,   9, 13,  13,  9,   5,  4,   4,  5,
58    13, 12,  12, 13,   4, 14,   6, 12,  12,  6,  14,  4,   6, 10,   2, 14,
59    14,  2,  10,  6,   2, 11,   3, 10,  10,  3,  11,  2,   3,  9,   1, 11,
60    11,  1,   9,  3,   1, 13,   5,  9,   9,  5,  13,  1,   5, 12,   4, 13,
61    13,  4,  12,  5,   0,  7,   0, 15,  15,  8,   8, 15,  15,  1,   7,  1,
62     1,  6,   2,  5,   3,  4,   4,  3,   5,  2,   6,  1,   0,  0,   1,  1,
63     9,  6,  10,  5,  11,  4,  12,  3,  13,  2,  14,  1,   2,  2,   3,  3,
64     4,  4,   5,  5,   6,  6,   7,  7,  14,  9,   1, 15,   8,  9,   8,  8,
65     9,  9,   1,  7,   0,  9,   9,  8,   6,  9,  13, 10,   2, 15,   8, 10,
66     7,  2,  15,  2,   2,  7,   0, 10,  10,  8,   5, 10,  12, 11,   3, 15,
67     8, 11,   7,  3,  15,  3,   3,  7,   0, 11,  11,  8,   4, 11,  11, 12,
68     4, 15,   8, 12,   7,  4,  15,  4,   4,  7,   0, 12,  12,  8,   3, 12,
69    10, 13,   5, 15,   8, 13,   7,  5,  15,  5,   5,  7,   0, 13,  13,  8,
70     2, 13,   9, 14,   6, 15,   8, 14,   7,  6,  15,  6,   6,  7,   0, 14,
71    14,  8,   1, 14,   5,  6,   2,  4,  13, 14,  10, 12,   4,  2,   3,  6,
72    12, 10,  11, 14,   6,  3,   1,  2,  14, 11,   9, 10,   2,  1,   5,  3,
73    10,  9,  13, 11,   3,  5,   4,  1,  11, 13,  12,  9,   1,  4,   6,  5,
74     9, 12,  14, 13,   5, 14,   2, 12,  13,  6,  10,  4,   4, 10,   3, 14,
75    12,  2,  11,  6,   6, 11,   1, 10,  14,  3,   9,  2,   2,  9,   5, 11,
76    10,  1,  13,  3,   3, 13,   4,  9,  11,  5,  12,  1,   1, 12,   6, 13,
77     9,  4,  14,  5,  10, 10,  11, 11,  12, 12,  13, 13,  14, 14,  15, 15,
78};
79
80static int const slang_assoc[16*16] =
81{
82    134, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
83    28, 135, 214, 86, 219, 91, 133, 127, 26, 23, 240, 112, 245, 117, 141, 126,
84    37, 211, 142, 83, 206, 132, 78, 160, 35, 237, 32, 109, 232, 140, 104, 161,
85    46, 87, 82, 143, 131, 215, 210, 169, 44, 113, 108, 41, 139, 241, 236, 170,
86    55, 222, 203, 130, 144, 94, 75, 178, 53, 248, 229, 138, 50, 120, 101, 179,
87    64, 90, 129, 218, 95, 145, 223, 187, 62, 116, 137, 244, 121, 59, 249, 188,
88    73, 128, 79, 207, 74, 202, 146, 196, 71, 136, 105, 233, 100, 228, 68, 197,
89    122, 153, 162, 171, 180, 189, 198, 147, 16, 25, 34, 43, 52, 61, 70, 18,
90    15, 27, 36, 45, 54, 63, 72, 17, 151, 155, 164, 173, 182, 191, 200, 124,
91    154, 22, 238, 110, 243, 115, 156, 24, 150, 152, 216, 88, 221, 93, 148, 20,
92    163, 235, 31, 107, 230, 165, 102, 33, 159, 213, 250, 85, 208, 157, 80, 29,
93    172, 111, 106, 40, 174, 239, 234, 42, 168, 89, 84, 251, 166, 217, 212, 38,
94    181, 246, 227, 183, 49, 118, 99, 51, 177, 224, 205, 175, 252, 96, 77, 47,
95    190, 114, 192, 242, 119, 58, 247, 60, 186, 92, 184, 220, 97, 253, 225, 56,
96    199, 201, 103, 231, 98, 226, 67, 69, 195, 193, 81, 209, 76, 204, 254, 65,
97    123, 149, 158, 167, 176, 185, 194, 19, 125, 21, 30, 39, 48, 57, 66, 255,
98};
99
100/*
101 * Local functions
102 */
103static void slang_init_palette(void);
104static void slang_write_utf32(uint32_t);
105
106#if defined(HAVE_SIGNAL)
107static RETSIGTYPE sigwinch_handler(int);
108static caca_display_t *sigwinch_d; /* FIXME: we ought to get rid of this */
109#endif
110#if defined(HAVE_GETENV) && defined(HAVE_PUTENV)
111static void slang_install_terminal(caca_display_t *);
112static void slang_uninstall_terminal(caca_display_t *);
113#endif
114
115struct driver_private
116{
117    char *term;
118};
119
120static int slang_init_graphics(caca_display_t *dp)
121{
122    dp->drv.p = malloc(sizeof(struct driver_private));
123
124#if defined(HAVE_GETENV) && defined(HAVE_PUTENV)
125    slang_install_terminal(dp);
126#endif
127
128#if defined(HAVE_SIGNAL)
129    sigwinch_d = dp;
130    signal(SIGWINCH, sigwinch_handler);
131#endif
132
133    _caca_set_term_title("caca for S-Lang");
134
135    /* Initialise slang library */
136    SLsig_block_signals();
137    SLtt_get_terminfo();
138
139    if(SLkp_init() == -1)
140    {
141        SLsig_unblock_signals();
142        return -1;
143    }
144
145    SLang_init_tty(-1, 0, 1);
146
147    if(SLsmg_init_smg() == -1)
148    {
149        SLsig_unblock_signals();
150        return -1;
151    }
152
153    SLsig_unblock_signals();
154
155    SLsmg_cls();
156    SLtt_set_cursor_visibility(0);
157    SLkp_define_keysym("\e[M", 1001);
158    SLtt_set_mouse_mode(1, 0);
159    SLsmg_refresh();
160
161    /* Disable scrolling so that hashmap scrolling optimization code
162     * does not cause ugly refreshes due to slow terminals */
163    SLtt_Term_Cannot_Scroll = 1;
164
165    slang_init_palette();
166
167#if defined(VMS) || defined(REAL_UNIX_SYSTEM)
168    /* Disable alt charset support so that we get a chance to have all
169     * 256 colour pairs */
170    SLtt_Has_Alt_Charset = 0;
171#endif
172
173#ifdef HAVE_SLSMG_UTF8_ENABLE
174    SLsmg_utf8_enable(1); /* 1 == force, 0 == disable, -1 == autodetect */
175    SLtt_utf8_enable(1);
176#endif
177
178    dp->resize.allow = 1;
179    caca_set_canvas_size(dp->cv, SLtt_Screen_Cols, SLtt_Screen_Rows);
180    dp->resize.allow = 0;
181
182    return 0;
183}
184
185static int slang_end_graphics(caca_display_t *dp)
186{
187    _caca_set_term_title("");
188    SLtt_set_mouse_mode(0, 0);
189    SLtt_set_cursor_visibility(1);
190    SLang_reset_tty();
191    SLsmg_reset_smg();
192
193#if defined HAVE_GETENV && defined HAVE_PUTENV
194    slang_uninstall_terminal(dp);
195#endif
196
197    free(dp->drv.p);
198
199    return 0;
200}
201
202static int slang_set_display_title(caca_display_t *dp, char const *title)
203{
204    _caca_set_term_title(title);
205
206    return 0;
207}
208
209static int slang_get_display_width(caca_display_t const *dp)
210{
211    /* Fallback to a 6x10 font */
212    return caca_get_canvas_width(dp->cv) * 6;
213}
214
215static int slang_get_display_height(caca_display_t const *dp)
216{
217    /* Fallback to a 6x10 font */
218    return caca_get_canvas_height(dp->cv) * 10;
219}
220
221static void slang_display(caca_display_t *dp)
222{
223    uint32_t const *cvchars = (uint32_t const *)caca_get_canvas_chars(dp->cv);
224    uint32_t const *cvattrs = (uint32_t const *)caca_get_canvas_attrs(dp->cv);
225    int width = caca_get_canvas_width(dp->cv);
226    int height = caca_get_canvas_height(dp->cv);
227    int x, y;
228
229    for(y = 0; y < (int)height; y++)
230    {
231        SLsmg_gotorc(y, 0);
232        for(x = width; x--; )
233        {
234            uint32_t ch = *cvchars++;
235
236#if defined(OPTIMISE_SLANG_PALETTE)
237            /* If foreground == background, just don't use this colour
238             * pair, and print a space instead of the real character.
239             * XXX: disabled, because I can't remember what it was
240             * here for, and in cases where SLang does not render
241             * bright backgrounds, it's just fucked up. */
242#if 0
243            uint8_t fgcolor = caca_attr_to_ansi_fg(*cvattrs);
244            uint8_t bgcolor = caca_attr_to_ansi_bg(*cvattrs);
245
246            if(fgcolor >= 0x10)
247                fgcolor = CACA_LIGHTGRAY;
248
249            if(bgcolor >= 0x10)
250                bgcolor = CACA_BLACK; /* FIXME: handle transparency */
251
252            if(fgcolor == bgcolor)
253            {
254                if(fgcolor == CACA_BLACK)
255                    fgcolor = CACA_WHITE;
256                else if(fgcolor == CACA_WHITE
257                         || fgcolor <= CACA_LIGHTGRAY)
258                    fgcolor = CACA_BLACK;
259                else
260                    fgcolor = CACA_WHITE;
261                SLsmg_set_color(slang_assoc[fgcolor + 16 * bgcolor]);
262                SLsmg_write_char(' ');
263                cvattrs++;
264            }
265            else
266#endif
267            {
268                SLsmg_set_color(slang_assoc[caca_attr_to_ansi(*cvattrs++)]);
269                slang_write_utf32(ch);
270            }
271#else
272            SLsmg_set_color(caca_attr_to_ansi(*cvattrs++));
273            slang_write_utf32(ch);
274#endif
275        }
276    }
277    SLsmg_gotorc(caca_get_cursor_y(dp->cv), caca_get_cursor_x(dp->cv));
278    SLsmg_refresh();
279}
280
281static void slang_handle_resize(caca_display_t *dp)
282{
283    SLtt_get_screen_size();
284    dp->resize.w = SLtt_Screen_Cols;
285    dp->resize.h = SLtt_Screen_Rows;
286
287    if(dp->resize.w != caca_get_canvas_width(dp->cv)
288        || dp->resize.h != caca_get_canvas_height(dp->cv))
289        SLsmg_reinit_smg();
290}
291
292static int slang_get_event(caca_display_t *dp, caca_privevent_t *ev)
293{
294    int intkey;
295
296    if(!SLang_input_pending(0))
297    {
298        ev->type = CACA_EVENT_NONE;
299        return 0;
300    }
301
302    /* We first use SLang_getkey() to see whether Esc was pressed
303     * alone, then (if it wasn't) we unget the key and use SLkp_getkey()
304     * instead, so that escape sequences are interpreted. */
305    intkey = SLang_getkey();
306
307    if(intkey != 0x1b /* Esc */ || SLang_input_pending(0))
308    {
309        SLang_ungetkey(intkey);
310        intkey = SLkp_getkey();
311    }
312
313    /* If the key was ASCII, return it immediately */
314    if(intkey < 0x7f)
315    {
316        ev->type = CACA_EVENT_KEY_PRESS;
317        ev->data.key.ch = intkey;
318        ev->data.key.utf32 = intkey;
319        ev->data.key.utf8[0] = intkey;
320        ev->data.key.utf8[1] = '\0';
321        return 1;
322    }
323
324    /* If the key was UTF-8, parse the whole sequence */
325    if(intkey >= 0x80 && intkey < 0x100)
326    {
327        int keys[7]; /* Necessary for ungetkey(); */
328        char utf8[7];
329        uint32_t utf32;
330        size_t i, bytes = 0;
331
332        keys[0] = intkey;
333        utf8[0] = intkey;
334
335        for(i = 1; i < 6; i++)
336        {
337            if(!SLang_input_pending(0))
338                break;
339            keys[i] = SLang_getkey();
340            utf8[i] = (unsigned char)keys[i];
341        }
342
343        utf8[i] = '\0';
344        utf32 = caca_utf8_to_utf32(utf8, &bytes);
345
346        while(i > bytes)
347            SLang_ungetkey(keys[--i]);
348
349        if(bytes)
350        {
351            ev->type = CACA_EVENT_KEY_PRESS;
352            ev->data.key.ch = 0;
353            ev->data.key.utf32 = utf32;
354            strcpy(ev->data.key.utf8, utf8);
355            return 1;
356        }
357    }
358
359    if(intkey == 0x3e9)
360    {
361        int button = (SLang_getkey() - ' ' + 1) & 0xf;
362        int x = SLang_getkey() - '!';
363        int y = SLang_getkey() - '!';
364
365        ev->data.mouse.button = button;
366        ev->type = CACA_EVENT_MOUSE_PRESS;
367        _push_event(dp, ev);
368        ev->type = CACA_EVENT_MOUSE_RELEASE;
369        _push_event(dp, ev);
370
371        if(dp->mouse.x == x && dp->mouse.y == y)
372            return _pop_event(dp, ev);
373
374        dp->mouse.x = x;
375        dp->mouse.y = y;
376
377        ev->type = CACA_EVENT_MOUSE_MOTION;
378        ev->data.mouse.x = dp->mouse.x;
379        ev->data.mouse.y = dp->mouse.y;
380        return 1;
381    }
382
383    switch(intkey)
384    {
385        case SL_KEY_UP: ev->data.key.ch = CACA_KEY_UP; break;
386        case SL_KEY_DOWN: ev->data.key.ch = CACA_KEY_DOWN; break;
387        case SL_KEY_LEFT: ev->data.key.ch = CACA_KEY_LEFT; break;
388        case SL_KEY_RIGHT: ev->data.key.ch = CACA_KEY_RIGHT; break;
389
390        case SL_KEY_IC: ev->data.key.ch = CACA_KEY_INSERT; break;
391        case SL_KEY_DELETE: ev->data.key.ch = CACA_KEY_DELETE; break;
392        case 0x7f:
393        case SL_KEY_BACKSPACE: ev->data.key.ch = CACA_KEY_BACKSPACE; break;
394        case SL_KEY_HOME: ev->data.key.ch = CACA_KEY_HOME; break;
395        case SL_KEY_END: ev->data.key.ch = CACA_KEY_END; break;
396        case SL_KEY_PPAGE: ev->data.key.ch = CACA_KEY_PAGEUP; break;
397        case SL_KEY_NPAGE: ev->data.key.ch = CACA_KEY_PAGEDOWN; break;
398
399        case SL_KEY_F(1): ev->data.key.ch = CACA_KEY_F1; break;
400        case SL_KEY_F(2): ev->data.key.ch = CACA_KEY_F2; break;
401        case SL_KEY_F(3): ev->data.key.ch = CACA_KEY_F3; break;
402        case SL_KEY_F(4): ev->data.key.ch = CACA_KEY_F4; break;
403        case SL_KEY_F(5): ev->data.key.ch = CACA_KEY_F5; break;
404        case SL_KEY_F(6): ev->data.key.ch = CACA_KEY_F6; break;
405        case SL_KEY_F(7): ev->data.key.ch = CACA_KEY_F7; break;
406        case SL_KEY_F(8): ev->data.key.ch = CACA_KEY_F8; break;
407        case SL_KEY_F(9): ev->data.key.ch = CACA_KEY_F9; break;
408        case SL_KEY_F(10): ev->data.key.ch = CACA_KEY_F10; break;
409        case SL_KEY_F(11): ev->data.key.ch = CACA_KEY_F11; break;
410        case SL_KEY_F(12): ev->data.key.ch = CACA_KEY_F12; break;
411
412        default:
413            /* Unknown key */
414            ev->type = CACA_EVENT_NONE; return 0;
415    }
416
417    ev->type = CACA_EVENT_KEY_PRESS;
418    ev->data.key.utf32 = 0;
419    ev->data.key.utf8[0] = '\0';
420    return 1;
421}
422
423static void slang_set_cursor(caca_display_t *dp, int flags)
424{
425    SLtt_set_cursor_visibility(flags ? 1 : 0);
426}
427
428/*
429 * XXX: following functions are local
430 */
431
432static void slang_init_palette(void)
433{
434    /* See SLang ref., 5.4.4. */
435    static char *slang_colors[16] =
436    {
437        /* Standard colours */
438        "black",
439        "blue",
440        "green",
441        "cyan",
442        "red",
443        "magenta",
444        "brown",
445        "lightgray",
446        /* Bright colours */
447        "gray",
448        "brightblue",
449        "brightgreen",
450        "brightcyan",
451        "brightred",
452        "brightmagenta",
453        "yellow",
454        "white",
455    };
456
457#if defined(OPTIMISE_SLANG_PALETTE)
458    int i;
459
460    for(i = 0; i < 16 * 16; i++)
461        SLtt_set_color(i, NULL, slang_colors[slang_palette[i * 2]],
462                                slang_colors[slang_palette[i * 2 + 1]]);
463#else
464    int fg, bg;
465
466    for(bg = 0; bg < 16; bg++)
467        for(fg = 0; fg < 16; fg++)
468        {
469            int i = fg + 16 * bg;
470            SLtt_set_color(i, NULL, slang_colors[fg], slang_colors[bg]);
471        }
472#endif
473}
474
475static void slang_write_utf32(uint32_t ch)
476{
477#ifdef HAVE_SLSMG_UTF8_ENABLE
478    char buf[10];
479    int bytes;
480#else
481    char ascii;
482#endif
483
484    if(ch == CACA_MAGIC_FULLWIDTH)
485        return;
486
487#ifdef HAVE_SLSMG_UTF8_ENABLE
488    bytes = caca_utf32_to_utf8(buf, ch);
489    buf[bytes] = '\0';
490    SLsmg_write_string(buf);
491#else
492    ascii = caca_utf32_to_ascii(ch);
493    SLsmg_write_char(ascii);
494    if(caca_utf32_is_fullwidth(ch))
495        SLsmg_write_char(ascii);
496#endif
497}
498
499#if defined(HAVE_SIGNAL)
500static RETSIGTYPE sigwinch_handler(int sig)
501{
502    sigwinch_d->resize.resized = 1;
503
504    signal(SIGWINCH, sigwinch_handler);
505}
506#endif
507
508#if defined(HAVE_GETENV) && defined(HAVE_PUTENV)
509static void slang_install_terminal(caca_display_t *dp)
510{
511    char *term, *colorterm;
512
513    dp->drv.p->term = NULL;
514
515    term = getenv("TERM");
516    colorterm = getenv("COLORTERM");
517
518    if(!term || strcmp(term, "xterm"))
519        return;
520
521    /* If we are using gnome-terminal, it's really a 16 colour terminal.
522     * Ditto if we are using xfce4-terminal, or Konsole. */
523    if((colorterm && (!strcmp(colorterm, "gnome-terminal")
524                       || !strcmp(colorterm, "Terminal")))
525         || getenv("KONSOLE_DCOP_SESSION"))
526    {
527        (void)putenv("TERM=xterm-16color");
528        dp->drv.p->term = strdup(term);
529        return;
530    }
531}
532
533static void slang_uninstall_terminal(caca_display_t *dp)
534{
535    /* Needs to be persistent because we use putenv() */
536    static char termenv[1024];
537
538    if(!dp->drv.p->term)
539        return;
540
541    snprintf(termenv, 1023, "TERM=%s", dp->drv.p->term);
542    free(dp->drv.p->term);
543    (void)putenv(termenv);
544}
545#endif
546
547/*
548 * Driver initialisation
549 */
550
551int slang_install(caca_display_t *dp)
552{
553    dp->drv.id = CACA_DRIVER_SLANG;
554    dp->drv.driver = "slang";
555
556    dp->drv.init_graphics = slang_init_graphics;
557    dp->drv.end_graphics = slang_end_graphics;
558    dp->drv.set_display_title = slang_set_display_title;
559    dp->drv.get_display_width = slang_get_display_width;
560    dp->drv.get_display_height = slang_get_display_height;
561    dp->drv.display = slang_display;
562    dp->drv.handle_resize = slang_handle_resize;
563    dp->drv.get_event = slang_get_event;
564    dp->drv.set_mouse = NULL;
565    dp->drv.set_cursor = slang_set_cursor;
566
567    return 0;
568}
569
570#endif /* USE_SLANG */
571
Note: See TracBrowser for help on using the repository browser.