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

Last change on this file since 824 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
RevLine 
[35]1/*
[672]2 *  libcaca       Colour ASCII-Art library
[527]3 *  Copyright (c) 2002-2006 Sam Hocevar <sam@zoy.org>
[268]4 *                All Rights Reserved
[35]5 *
[769]6 *  $Id: driver_ncurses.c 822 2006-04-21 12:01:14Z sam $
7 *
[268]8 *  This library is free software; you can redistribute it and/or
[522]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.
[35]12 */
[17]13
[769]14/*
[540]15 *  This file contains the libcaca Ncurses input and output driver
[205]16 */
17
[63]18#include "config.h"
19
[539]20#if defined(USE_NCURSES)
21
[629]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)
[539]27#   include <ncurses.h>
28#else
29#   include <curses.h>
30#endif
31
[550]32#include <stdlib.h>
33#include <string.h>
34
[348]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
[185]42#include "caca.h"
43#include "caca_internals.h"
[524]44#include "cucul.h"
45#include "cucul_internals.h"
[17]46
[281]47/*
48 * Local functions
49 */
[540]50
[539]51#if defined(HAVE_SIGNAL)
[348]52static RETSIGTYPE sigwinch_handler(int);
[811]53static caca_display_t *sigwinch_d; /* FIXME: we ought to get rid of this */
[348]54#endif
[550]55#if defined(HAVE_GETENV) && defined(HAVE_PUTENV)
56static void ncurses_check_terminal(void);
57#endif
[629]58static void ncurses_write_utf32(uint32_t);
[348]59
[550]60struct driver_private
61{
62    int attr[16*16];
63    mmask_t oldmask;
64};
65
[811]66static int ncurses_init_graphics(caca_display_t *dp)
[539]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    };
[300]89
[539]90    mmask_t newmask;
91    int fg, bg, max;
[483]92
[811]93    dp->drv.p = malloc(sizeof(struct driver_private));
[550]94
95#if defined(HAVE_GETENV) && defined(HAVE_PUTENV)
96    ncurses_check_terminal();
97#endif
98
[539]99#if defined(HAVE_SIGNAL)
[811]100    sigwinch_d = dp;
[348]101    signal(SIGWINCH, sigwinch_handler);
102#endif
103
[539]104    initscr();
105    keypad(stdscr, TRUE);
106    nonl();
107    raw();
108    noecho();
109    nodelay(stdscr, TRUE);
110    curs_set(0);
[227]111
[539]112    /* Activate mouse */
113    newmask = REPORT_MOUSE_POSITION | ALL_MOUSE_EVENTS;
[811]114    mousemask(newmask, &dp->drv.p->oldmask);
[539]115    mouseinterval(-1); /* No click emulation */
[231]116
[539]117    /* Set the escape delay to a ridiculously low value */
118    ESCDELAY = 10;
[227]119
[539]120    /* Activate colour */
121    start_color();
[227]122
[539]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;
[227]130
[539]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]);
[811]139            dp->drv.p->attr[fg + 16 * bg] = COLOR_PAIR(col);
[227]140
[539]141            if(max == 8)
[265]142            {
[539]143                /* Bright fg on simple bg */
[811]144                dp->drv.p->attr[fg + 8 + 16 * bg] = A_BOLD | COLOR_PAIR(col);
[539]145                /* Simple fg on bright bg */
[811]146                dp->drv.p->attr[fg + 16 * (bg + 8)] = A_BLINK
[539]147                                                    | COLOR_PAIR(col);
148                /* Bright fg on bright bg */
[811]149                dp->drv.p->attr[fg + 8 + 16 * (bg + 8)] = A_BLINK | A_BOLD
[263]150                                                        | COLOR_PAIR(col);
[227]151            }
[265]152        }
[263]153
[813]154    _cucul_set_canvas_size(dp->cv, COLS, LINES);
[300]155
[227]156    return 0;
157}
158
[811]159static int ncurses_end_graphics(caca_display_t *dp)
[251]160{
[811]161    mousemask(dp->drv.p->oldmask, NULL);
[539]162    curs_set(1);
163    noraw();
164    endwin();
[300]165
[811]166    free(dp->drv.p);
[550]167
[251]168    return 0;
169}
170
[819]171static int ncurses_set_display_title(caca_display_t *dp, char const *title)
[343]172{
173    return 0;
174}
175
[819]176static unsigned int ncurses_get_display_width(caca_display_t *dp)
[352]177{
178    /* Fallback to a 6x10 font */
[811]179    return dp->cv->width * 6;
[352]180}
181
[819]182static unsigned int ncurses_get_display_height(caca_display_t *dp)
[352]183{
184    /* Fallback to a 6x10 font */
[811]185    return dp->cv->height * 10;
[352]186}
187
[811]188static void ncurses_display(caca_display_t *dp)
[227]189{
[539]190    int x, y;
[811]191    uint32_t *attr = dp->cv->attr;
192    uint32_t *chars = dp->cv->chars;
193    for(y = 0; y < (int)dp->cv->height; y++)
[265]194    {
[539]195        move(y, 0);
[811]196        for(x = dp->cv->width; x--; )
[527]197        {
[811]198            attrset(dp->drv.p->attr[_cucul_argb32_to_ansi8(*attr++)]);
[629]199            ncurses_write_utf32(*chars++);
[527]200        }
[265]201    }
[539]202    refresh();
[227]203}
204
[811]205static void ncurses_handle_resize(caca_display_t *dp)
[347]206{
[539]207    struct winsize size;
[347]208
[822]209#if defined(HAVE_SYS_IOCTL_H)
[539]210    if(ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0)
[347]211    {
[811]212        dp->resize.w = size.ws_col;
213        dp->resize.h = size.ws_row;
[364]214#if defined(HAVE_RESIZE_TERM)
[811]215        resize_term(dp->resize.h, dp->resize.w);
[364]216#else
[811]217        resizeterm(*dp->resize.h, *dp->resize.w);
[364]218#endif
[539]219        wrefresh(curscr);
[553]220        return;
[347]221    }
[822]222#endif
[553]223
224    /* Fallback */
[811]225    dp->resize.w = dp->cv->width;
226    dp->resize.h = dp->cv->height;
[347]227}
228
[811]229static int ncurses_get_event(caca_display_t *dp, caca_event_t *ev)
[548]230{
231    int intkey;
232
233    intkey = getch();
234    if(intkey == ERR)
[681]235    {
236        ev->type = CACA_EVENT_NONE;
237        return 0;
238    }
[548]239
240    if(intkey < 0x100)
241    {
[681]242        ev->type = CACA_EVENT_KEY_PRESS;
[810]243        ev->data.key.ch = intkey;
[681]244        return 1;
[548]245    }
246
247    if(intkey == KEY_MOUSE)
248    {
249        MEVENT mevent;
250        getmouse(&mevent);
251
252        switch(mevent.bstate)
253        {
254            case BUTTON1_PRESSED:
[681]255                ev->data.mouse.button = 1;
[811]256                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev);
[548]257                break;
258            case BUTTON1_RELEASED:
[681]259                ev->data.mouse.button = 1;
[811]260                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev);
[548]261                break;
262            case BUTTON1_CLICKED:
[681]263                ev->data.mouse.button = 1;
[811]264                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev);
265                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev);
[548]266                break;
267            case BUTTON1_DOUBLE_CLICKED:
[681]268                ev->data.mouse.button = 1;
[811]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);
[548]273                break;
274            case BUTTON1_TRIPLE_CLICKED:
[681]275                ev->data.mouse.button = 1;
[811]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);
[548]282                break;
283            case BUTTON1_RESERVED_EVENT:
284                break;
285
286            case BUTTON2_PRESSED:
[681]287                ev->data.mouse.button = 2;
[811]288                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev);
[548]289                break;
290            case BUTTON2_RELEASED:
[681]291                ev->data.mouse.button = 2;
[811]292                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev);
[548]293                break;
294            case BUTTON2_CLICKED:
[681]295                ev->data.mouse.button = 2;
[811]296                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev);
297                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev);
[548]298                break;
299            case BUTTON2_DOUBLE_CLICKED:
[681]300                ev->data.mouse.button = 2;
[811]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);
[548]305                break;
306            case BUTTON2_TRIPLE_CLICKED:
[681]307                ev->data.mouse.button = 2;
[811]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);
[548]314                break;
315            case BUTTON2_RESERVED_EVENT:
316                break;
317
318            case BUTTON3_PRESSED:
[681]319                ev->data.mouse.button = 3;
[811]320                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev);
[548]321                break;
322            case BUTTON3_RELEASED:
[681]323                ev->data.mouse.button = 3;
[811]324                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev);
[548]325                break;
326            case BUTTON3_CLICKED:
[681]327                ev->data.mouse.button = 3;
[811]328                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev);
329                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev);
[548]330                break;
331            case BUTTON3_DOUBLE_CLICKED:
[681]332                ev->data.mouse.button = 3;
[811]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);
[548]337                break;
338            case BUTTON3_TRIPLE_CLICKED:
[681]339                ev->data.mouse.button = 3;
[811]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);
[548]346                break;
347            case BUTTON3_RESERVED_EVENT:
348                break;
349
350            case BUTTON4_PRESSED:
[681]351                ev->data.mouse.button = 4;
[811]352                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev);
[548]353                break;
354            case BUTTON4_RELEASED:
[681]355                ev->data.mouse.button = 4;
[811]356                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev);
[548]357                break;
358            case BUTTON4_CLICKED:
[681]359                ev->data.mouse.button = 4;
[811]360                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(dp, ev);
361                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(dp, ev);
[548]362                break;
363            case BUTTON4_DOUBLE_CLICKED:
[681]364                ev->data.mouse.button = 4;
[811]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);
[548]369                break;
370            case BUTTON4_TRIPLE_CLICKED:
[681]371                ev->data.mouse.button = 4;
[811]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);
[548]378                break;
379            case BUTTON4_RESERVED_EVENT:
380                break;
381
382            default:
383                break;
384        }
385
[811]386        if(dp->mouse.x == (unsigned int)mevent.x &&
387           dp->mouse.y == (unsigned int)mevent.y)
388            return _pop_event(dp, ev);
[548]389
[811]390        dp->mouse.x = mevent.x;
391        dp->mouse.y = mevent.y;
[548]392
[681]393        ev->type = CACA_EVENT_MOUSE_MOTION;
[811]394        ev->data.mouse.x = dp->mouse.x;
395        ev->data.mouse.y = dp->mouse.y;
[681]396        return 1;
[548]397    }
398
399    switch(intkey)
400    {
[810]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;
[548]405
[810]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;
[548]412
[810]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;
[681]425
426        default: ev->type = CACA_EVENT_NONE; return 0;
[548]427    }
428
[681]429    ev->type = CACA_EVENT_KEY_PRESS;
430    ev->data.key.ucs4 = 0;
431    ev->data.key.utf8[0] = '\0';
432    return 1;
[548]433}
434
[539]435/*
436 * XXX: following functions are local
437 */
[281]438
[539]439#if defined(HAVE_SIGNAL)
[348]440static RETSIGTYPE sigwinch_handler(int sig)
441{
[811]442    sigwinch_d->resize.resized = 1;
[348]443
[613]444    signal(SIGWINCH, sigwinch_handler);
[348]445}
446#endif
447
[550]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
[811]486static void ncurses_write_utf32(uint32_t ch)
[629]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
[811]498    if(ch < 0x80)
[629]499    {
[811]500        addch(ch);
[629]501        return;
502    }
503
504#if defined(HAVE_NCURSESW_NCURSES_H)
[811]505    if(ch < 0x10000)
[629]506    {
[811]507        addch(ch); /* FIXME: doesn't work either */
[629]508        return;
509    }
510
[811]511    bytes = (ch < 0x800) ? 2 : (ch < 0x10000) ? 3 : 4;
[629]512    buf[bytes] = '\0';
513    parser = buf + bytes;
514
515    switch(bytes)
516    {
[811]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;
[629]520    }
[811]521    *--parser = ch | mark[bytes];
[629]522
523    addstr(buf);
524#else
525    addch(' ');
526#endif
527}
528
[539]529/*
530 * Driver initialisation
531 */
[527]532
[811]533int ncurses_install(caca_display_t *dp)
[511]534{
[811]535    dp->drv.driver = CACA_DRIVER_NCURSES;
[527]536
[811]537    dp->drv.init_graphics = ncurses_init_graphics;
538    dp->drv.end_graphics = ncurses_end_graphics;
[819]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;
[811]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;
[689]546
[684]547    return 0;
[511]548}
549
[539]550#endif /* USE_NCURSES */
[527]551
Note: See TracBrowser for help on using the repository browser.