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

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