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

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