source: libcaca/trunk/caca/driver_ncurses.c @ 1452

Last change on this file since 1452 was 1452, checked in by Sam Hocevar, 14 years ago
  • Add a no warranty clause to the code.
  • Property svn:keywords set to Id
File size: 12.9 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_ncurses.c 1452 2006-12-11 15:48:46Z sam $
7 *
8 *  This library is free software. It commes 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 Ncurses input and output driver
17 */
18
19#include "config.h"
20#include "common.h"
21
22#if defined USE_NCURSES
23
24#if defined HAVE_NCURSESW_NCURSES_H
25#   include <ncursesw/ncurses.h>
26#elif defined HAVE_NCURSES_NCURSES_H
27#   include <ncurses/ncurses.h>
28#elif defined HAVE_NCURSES_H
29#   include <ncurses.h>
30#else
31#   include <curses.h>
32#endif
33
34#include <stdlib.h>
35#include <string.h>
36
37#if defined HAVE_SIGNAL_H
38#   include <signal.h>
39#endif
40#if defined HAVE_SYS_IOCTL_H
41#   include <sys/ioctl.h>
42#endif
43#if defined HAVE_LOCALE_H
44#   include <locale.h>
45#endif
46
47#include "caca.h"
48#include "caca_internals.h"
49#include "cucul.h"
50#include "cucul_internals.h"
51
52/*
53 * Local functions
54 */
55
56#if defined HAVE_SIGNAL
57static RETSIGTYPE sigwinch_handler(int);
58static caca_display_t *sigwinch_d; /* FIXME: we ought to get rid of this */
59#endif
60#if defined HAVE_GETENV && defined HAVE_PUTENV
61static void ncurses_check_terminal(void);
62#endif
63static void ncurses_write_utf32(uint32_t);
64
65struct driver_private
66{
67    int attr[16*16];
68    mmask_t oldmask;
69};
70
71static int ncurses_init_graphics(caca_display_t *dp)
72{
73    static int curses_colors[] =
74    {
75        /* Standard curses colours */
76        COLOR_BLACK,
77        COLOR_BLUE,
78        COLOR_GREEN,
79        COLOR_CYAN,
80        COLOR_RED,
81        COLOR_MAGENTA,
82        COLOR_YELLOW,
83        COLOR_WHITE,
84        /* Extra values for xterm-16color */
85        COLOR_BLACK + 8,
86        COLOR_BLUE + 8,
87        COLOR_GREEN + 8,
88        COLOR_CYAN + 8,
89        COLOR_RED + 8,
90        COLOR_MAGENTA + 8,
91        COLOR_YELLOW + 8,
92        COLOR_WHITE + 8
93    };
94
95    mmask_t newmask;
96    int fg, bg, max;
97
98    dp->drv.p = malloc(sizeof(struct driver_private));
99
100#if defined HAVE_GETENV && defined HAVE_PUTENV
101    ncurses_check_terminal();
102#endif
103
104#if defined HAVE_SIGNAL
105    sigwinch_d = dp;
106    signal(SIGWINCH, sigwinch_handler);
107#endif
108
109#if defined HAVE_LOCALE_H
110    setlocale(LC_ALL, "");
111#endif
112
113    _caca_set_term_title("caca for ncurses");
114
115    initscr();
116    keypad(stdscr, TRUE);
117    nonl();
118    raw();
119    noecho();
120    nodelay(stdscr, TRUE);
121    curs_set(0);
122
123    /* Activate mouse */
124    newmask = REPORT_MOUSE_POSITION | ALL_MOUSE_EVENTS;
125    mousemask(newmask, &dp->drv.p->oldmask);
126    mouseinterval(-1); /* No click emulation */
127
128    /* Set the escape delay to a ridiculously low value */
129    ESCDELAY = 10;
130
131    /* Activate colour */
132    start_color();
133
134    /* If COLORS == 16, it means the terminal supports full bright colours
135     * using setab and setaf (will use \e[90m \e[91m etc. for colours >= 8),
136     * we can build 16*16 colour pairs.
137     * If COLORS == 8, it means the terminal does not know about bright
138     * colours and we need to get them through A_BOLD and A_BLINK (\e[1m
139     * and \e[5m). We can only build 8*8 colour pairs. */
140    max = COLORS >= 16 ? 16 : 8;
141
142    for(bg = 0; bg < max; bg++)
143        for(fg = 0; fg < max; fg++)
144        {
145            /* Use ((max + 7 - fg) % max) instead of fg so that colour 0
146             * is light gray on black. Some terminals don't like this
147             * colour pair to be redefined. */
148            int col = ((max + 7 - fg) % max) + max * bg;
149            init_pair(col, curses_colors[fg], curses_colors[bg]);
150            dp->drv.p->attr[fg + 16 * bg] = COLOR_PAIR(col);
151
152            if(max == 8)
153            {
154                /* Bright fg on simple bg */
155                dp->drv.p->attr[fg + 8 + 16 * bg] = A_BOLD | COLOR_PAIR(col);
156                /* Simple fg on bright bg */
157                dp->drv.p->attr[fg + 16 * (bg + 8)] = A_BLINK
158                                                    | COLOR_PAIR(col);
159                /* Bright fg on bright bg */
160                dp->drv.p->attr[fg + 8 + 16 * (bg + 8)] = A_BLINK | A_BOLD
161                                                        | COLOR_PAIR(col);
162            }
163        }
164
165    _cucul_set_canvas_size(dp->cv, COLS, LINES);
166
167    return 0;
168}
169
170static int ncurses_end_graphics(caca_display_t *dp)
171{
172    _caca_set_term_title("");
173    mousemask(dp->drv.p->oldmask, NULL);
174    curs_set(1);
175    noraw();
176    endwin();
177
178    free(dp->drv.p);
179
180    return 0;
181}
182
183static int ncurses_set_display_title(caca_display_t *dp, char const *title)
184{
185    _caca_set_term_title(title);
186
187    return 0;
188}
189
190static unsigned int ncurses_get_display_width(caca_display_t *dp)
191{
192    /* Fallback to a 6x10 font */
193    return dp->cv->width * 6;
194}
195
196static unsigned int ncurses_get_display_height(caca_display_t *dp)
197{
198    /* Fallback to a 6x10 font */
199    return dp->cv->height * 10;
200}
201
202static void ncurses_display(caca_display_t *dp)
203{
204    int x, y;
205    uint32_t *attrs = dp->cv->attrs;
206    uint32_t *chars = dp->cv->chars;
207    for(y = 0; y < (int)dp->cv->height; y++)
208    {
209        move(y, 0);
210        for(x = dp->cv->width; x--; )
211        {
212            attrset(dp->drv.p->attr[cucul_attr_to_ansi(*attrs++)]);
213            ncurses_write_utf32(*chars++);
214        }
215    }
216    refresh();
217}
218
219static void ncurses_handle_resize(caca_display_t *dp)
220{
221    struct winsize size;
222
223#if defined HAVE_SYS_IOCTL_H
224    if(ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0)
225    {
226        dp->resize.w = size.ws_col;
227        dp->resize.h = size.ws_row;
228#if defined HAVE_RESIZE_TERM
229        resize_term(dp->resize.h, dp->resize.w);
230#else
231        resizeterm(dp->resize.h, dp->resize.w);
232#endif
233        wrefresh(curscr);
234        return;
235    }
236#endif
237
238    /* Fallback */
239    dp->resize.w = dp->cv->width;
240    dp->resize.h = dp->cv->height;
241}
242
243static int ncurses_get_event(caca_display_t *dp, caca_event_t *ev)
244{
245    int intkey;
246
247    intkey = getch();
248    if(intkey == ERR)
249    {
250        ev->type = CACA_EVENT_NONE;
251        return 0;
252    }
253
254    if(intkey < 0x7f)
255    {
256        ev->type = CACA_EVENT_KEY_PRESS;
257        ev->data.key.ch = intkey;
258        ev->data.key.utf32 = intkey;
259        ev->data.key.utf8[0] = intkey;
260        ev->data.key.utf8[1] = '\0';
261        return 1;
262    }
263
264    /* If the key was UTF-8, parse the whole sequence */
265    if(intkey >= 0x80 && intkey < 0x100)
266    {
267        int keys[7]; /* Necessary for ungetch(); */
268        char utf8[7];
269        uint32_t utf32;
270        unsigned int i, bytes = 0;
271
272        keys[0] = intkey;
273        utf8[0] = intkey;
274
275        for(i = 1; i < 6; i++)
276        {
277            keys[i] = getch();
278            utf8[i] = (unsigned char)keys[i];
279        }
280
281        utf8[i] = '\0';
282        utf32 = cucul_utf8_to_utf32(utf8, &bytes);
283
284        while(i > bytes)
285            ungetch(keys[--i]);
286
287        if(bytes)
288        {
289            ev->type = CACA_EVENT_KEY_PRESS;
290            ev->data.key.ch = 0;
291            ev->data.key.utf32 = utf32;
292            strcpy(ev->data.key.utf8, utf8);
293            return 1;
294        }
295    }
296
297    if(intkey == KEY_MOUSE)
298    {
299        MEVENT mevent;
300        getmouse(&mevent);
301
302        switch(mevent.bstate)
303        {
304#define PRESS(x) ev->data.mouse.button = x; \
305                 ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev)
306#define RELEASE(x) ev->data.mouse.button = x; \
307                   ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev)
308#define CLICK(x) PRESS(x); RELEASE(x)
309            case BUTTON1_PRESSED: PRESS(1); break;
310            case BUTTON1_RELEASED: RELEASE(1); break;
311            case BUTTON1_CLICKED: CLICK(1); break;
312            case BUTTON1_DOUBLE_CLICKED: CLICK(1); CLICK(1); break;
313            case BUTTON1_TRIPLE_CLICKED: CLICK(1); CLICK(1); CLICK(1); break;
314            case BUTTON1_RESERVED_EVENT: break;
315
316            case BUTTON2_PRESSED: PRESS(2); break;
317            case BUTTON2_RELEASED: RELEASE(2); break;
318            case BUTTON2_CLICKED: CLICK(2); break;
319            case BUTTON2_DOUBLE_CLICKED: CLICK(2); CLICK(2); break;
320            case BUTTON2_TRIPLE_CLICKED: CLICK(2); CLICK(2); CLICK(2); break;
321            case BUTTON2_RESERVED_EVENT: break;
322
323            case BUTTON3_PRESSED: PRESS(3); break;
324            case BUTTON3_RELEASED: RELEASE(3); break;
325            case BUTTON3_CLICKED: CLICK(3); break;
326            case BUTTON3_DOUBLE_CLICKED: CLICK(3); CLICK(3); break;
327            case BUTTON3_TRIPLE_CLICKED: CLICK(3); CLICK(3); CLICK(3); break;
328            case BUTTON3_RESERVED_EVENT: break;
329
330            case BUTTON4_PRESSED: PRESS(4); break;
331            case BUTTON4_RELEASED: RELEASE(4); break;
332            case BUTTON4_CLICKED: CLICK(4); break;
333            case BUTTON4_DOUBLE_CLICKED: CLICK(4); CLICK(4); break;
334            case BUTTON4_TRIPLE_CLICKED: CLICK(4); CLICK(4); CLICK(4); break;
335            case BUTTON4_RESERVED_EVENT: break;
336
337            default:
338                break;
339#undef PRESS
340#undef RELEASE
341#undef CLICK
342        }
343
344        if(dp->mouse.x == (unsigned int)mevent.x &&
345           dp->mouse.y == (unsigned int)mevent.y)
346            return _pop_event(dp, ev);
347
348        dp->mouse.x = mevent.x;
349        dp->mouse.y = mevent.y;
350
351        ev->type = CACA_EVENT_MOUSE_MOTION;
352        ev->data.mouse.x = dp->mouse.x;
353        ev->data.mouse.y = dp->mouse.y;
354        return 1;
355    }
356
357    switch(intkey)
358    {
359        case KEY_UP: ev->data.key.ch = CACA_KEY_UP; break;
360        case KEY_DOWN: ev->data.key.ch = CACA_KEY_DOWN; break;
361        case KEY_LEFT: ev->data.key.ch = CACA_KEY_LEFT; break;
362        case KEY_RIGHT: ev->data.key.ch = CACA_KEY_RIGHT; break;
363
364        case KEY_IC: ev->data.key.ch = CACA_KEY_INSERT; break;
365        case KEY_DC: ev->data.key.ch = CACA_KEY_DELETE; break;
366        case 0x7f:
367        case KEY_BACKSPACE: ev->data.key.ch = CACA_KEY_BACKSPACE; break;
368        case KEY_HOME: ev->data.key.ch = CACA_KEY_HOME; break;
369        case KEY_END: ev->data.key.ch = CACA_KEY_END; break;
370        case KEY_PPAGE: ev->data.key.ch = CACA_KEY_PAGEUP; break;
371        case KEY_NPAGE: ev->data.key.ch = CACA_KEY_PAGEDOWN; break;
372
373        case KEY_F(1): ev->data.key.ch = CACA_KEY_F1; break;
374        case KEY_F(2): ev->data.key.ch = CACA_KEY_F2; break;
375        case KEY_F(3): ev->data.key.ch = CACA_KEY_F3; break;
376        case KEY_F(4): ev->data.key.ch = CACA_KEY_F4; break;
377        case KEY_F(5): ev->data.key.ch = CACA_KEY_F5; break;
378        case KEY_F(6): ev->data.key.ch = CACA_KEY_F6; break;
379        case KEY_F(7): ev->data.key.ch = CACA_KEY_F7; break;
380        case KEY_F(8): ev->data.key.ch = CACA_KEY_F8; break;
381        case KEY_F(9): ev->data.key.ch = CACA_KEY_F9; break;
382        case KEY_F(10): ev->data.key.ch = CACA_KEY_F10; break;
383        case KEY_F(11): ev->data.key.ch = CACA_KEY_F11; break;
384        case KEY_F(12): ev->data.key.ch = CACA_KEY_F12; break;
385
386        default:
387            /* Unknown key */
388            ev->type = CACA_EVENT_NONE; return 0;
389    }
390
391    ev->type = CACA_EVENT_KEY_PRESS;
392    ev->data.key.utf32 = 0;
393    ev->data.key.utf8[0] = '\0';
394    return 1;
395}
396
397/*
398 * XXX: following functions are local
399 */
400
401#if defined HAVE_SIGNAL
402static RETSIGTYPE sigwinch_handler(int sig)
403{
404    sigwinch_d->resize.resized = 1;
405
406    signal(SIGWINCH, sigwinch_handler);
407}
408#endif
409
410#if defined HAVE_GETENV && defined HAVE_PUTENV
411static void ncurses_check_terminal(void)
412{
413    char *term, *colorterm;
414
415    term = getenv("TERM");
416    colorterm = getenv("COLORTERM");
417
418    if(!term || strcmp(term, "xterm"))
419        return;
420
421    /* If we are using gnome-terminal, it's really a 16 colour terminal.
422     * Ditto if we are using xfce4-terminal, or Konsole. */
423    if((colorterm && (!strcmp(colorterm, "gnome-terminal")
424                       || !strcmp(colorterm, "Terminal")))
425         || getenv("KONSOLE_DCOP_SESSION"))
426    {
427        SCREEN *screen;
428        screen = newterm("xterm-16color", stdout, stdin);
429        if(screen == NULL)
430            return;
431        endwin();
432        (void)putenv("TERM=xterm-16color");
433        return;
434    }
435}
436#endif
437
438static void ncurses_write_utf32(uint32_t ch)
439{
440#if defined HAVE_NCURSESW_NCURSES_H
441    char buf[10];
442    int bytes;
443#endif
444
445    if(ch == CUCUL_MAGIC_FULLWIDTH)
446        return;
447
448#if defined HAVE_NCURSESW_NCURSES_H
449    bytes = cucul_utf32_to_utf8(buf, ch);
450    buf[bytes] = '\0';
451    addstr(buf);
452#else
453    if(ch < 0x80)
454        addch(ch);
455    else if(cucul_utf32_is_fullwidth(ch))
456        addstr("? ");
457    else
458        addch('?');
459#endif
460}
461
462/*
463 * Driver initialisation
464 */
465
466int ncurses_install(caca_display_t *dp)
467{
468    dp->drv.driver = CACA_DRIVER_NCURSES;
469
470    dp->drv.init_graphics = ncurses_init_graphics;
471    dp->drv.end_graphics = ncurses_end_graphics;
472    dp->drv.set_display_title = ncurses_set_display_title;
473    dp->drv.get_display_width = ncurses_get_display_width;
474    dp->drv.get_display_height = ncurses_get_display_height;
475    dp->drv.display = ncurses_display;
476    dp->drv.handle_resize = ncurses_handle_resize;
477    dp->drv.get_event = ncurses_get_event;
478    dp->drv.set_mouse = NULL;
479    dp->drv.set_cursor = NULL;
480
481    return 0;
482}
483
484#endif /* USE_NCURSES */
485
Note: See TracBrowser for help on using the repository browser.