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

Last change on this file since 684 was 684, checked in by Sam Hocevar, 15 years ago
  • Allow the driver initialisation to fail, for instance when $DISPLAY = "".
  • Property svn:keywords set to Id
File size: 16.6 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 *  This library is free software; you can redistribute it and/or
7 *  modify it under the terms of the Do What The Fuck You Want To
8 *  Public License, Version 2, as published by Sam Hocevar. See
9 *  http://sam.zoy.org/wtfpl/COPYING for more details.
10 */
11
12/** \file driver_ncurses.c
13 *  \version \$Id: driver_ncurses.c 684 2006-03-24 09:48:20Z sam $
14 *  \author Sam Hocevar <sam@zoy.org>
15 *  \brief Ncurses driver
16 *
17 *  This file contains the libcaca Ncurses input and output driver
18 */
19
20#include "config.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
44#include "caca.h"
45#include "caca_internals.h"
46#include "cucul.h"
47#include "cucul_internals.h"
48
49/*
50 * Local functions
51 */
52
53#if defined(HAVE_SIGNAL)
54static RETSIGTYPE sigwinch_handler(int);
55static caca_t *sigwinch_kk; /* FIXME: we ought to get rid of this */
56#endif
57#if defined(HAVE_GETENV) && defined(HAVE_PUTENV)
58static void ncurses_check_terminal(void);
59#endif
60static void ncurses_write_utf32(uint32_t);
61
62struct driver_private
63{
64    int attr[16*16];
65    mmask_t oldmask;
66};
67
68static int ncurses_init_graphics(caca_t *kk)
69{
70    static int curses_colors[] =
71    {
72        /* Standard curses colours */
73        COLOR_BLACK,
74        COLOR_BLUE,
75        COLOR_GREEN,
76        COLOR_CYAN,
77        COLOR_RED,
78        COLOR_MAGENTA,
79        COLOR_YELLOW,
80        COLOR_WHITE,
81        /* Extra values for xterm-16color */
82        COLOR_BLACK + 8,
83        COLOR_BLUE + 8,
84        COLOR_GREEN + 8,
85        COLOR_CYAN + 8,
86        COLOR_RED + 8,
87        COLOR_MAGENTA + 8,
88        COLOR_YELLOW + 8,
89        COLOR_WHITE + 8
90    };
91
92    mmask_t newmask;
93    int fg, bg, max;
94
95    kk->drv.p = malloc(sizeof(struct driver_private));
96
97#if defined(HAVE_GETENV) && defined(HAVE_PUTENV)
98    ncurses_check_terminal();
99#endif
100
101#if defined(HAVE_SIGNAL)
102    sigwinch_kk = kk;
103    signal(SIGWINCH, sigwinch_handler);
104#endif
105
106    initscr();
107    keypad(stdscr, TRUE);
108    nonl();
109    raw();
110    noecho();
111    nodelay(stdscr, TRUE);
112    curs_set(0);
113
114    /* Activate mouse */
115    newmask = REPORT_MOUSE_POSITION | ALL_MOUSE_EVENTS;
116    mousemask(newmask, &kk->drv.p->oldmask);
117    mouseinterval(-1); /* No click emulation */
118
119    /* Set the escape delay to a ridiculously low value */
120    ESCDELAY = 10;
121
122    /* Activate colour */
123    start_color();
124
125    /* If COLORS == 16, it means the terminal supports full bright colours
126     * using setab and setaf (will use \e[90m \e[91m etc. for colours >= 8),
127     * we can build 16*16 colour pairs.
128     * If COLORS == 8, it means the terminal does not know about bright
129     * colours and we need to get them through A_BOLD and A_BLINK (\e[1m
130     * and \e[5m). We can only build 8*8 colour pairs. */
131    max = COLORS >= 16 ? 16 : 8;
132
133    for(bg = 0; bg < max; bg++)
134        for(fg = 0; fg < max; fg++)
135        {
136            /* Use ((max + 7 - fg) % max) instead of fg so that colour 0
137             * is light gray on black. Some terminals don't like this
138             * colour pair to be redefined. */
139            int col = ((max + 7 - fg) % max) + max * bg;
140            init_pair(col, curses_colors[fg], curses_colors[bg]);
141            kk->drv.p->attr[fg + 16 * bg] = COLOR_PAIR(col);
142
143            if(max == 8)
144            {
145                /* Bright fg on simple bg */
146                kk->drv.p->attr[fg + 8 + 16 * bg] = A_BOLD | COLOR_PAIR(col);
147                /* Simple fg on bright bg */
148                kk->drv.p->attr[fg + 16 * (bg + 8)] = A_BLINK
149                                                    | COLOR_PAIR(col);
150                /* Bright fg on bright bg */
151                kk->drv.p->attr[fg + 8 + 16 * (bg + 8)] = A_BLINK | A_BOLD
152                                                        | COLOR_PAIR(col);
153            }
154        }
155
156    _cucul_set_size(kk->qq, COLS, LINES);
157
158    return 0;
159}
160
161static int ncurses_end_graphics(caca_t *kk)
162{
163    mousemask(kk->drv.p->oldmask, NULL);
164    curs_set(1);
165    noraw();
166    endwin();
167
168    free(kk->drv.p);
169
170    return 0;
171}
172
173static int ncurses_set_window_title(caca_t *kk, char const *title)
174{
175    return 0;
176}
177
178static unsigned int ncurses_get_window_width(caca_t *kk)
179{
180    /* Fallback to a 6x10 font */
181    return kk->qq->width * 6;
182}
183
184static unsigned int ncurses_get_window_height(caca_t *kk)
185{
186    /* Fallback to a 6x10 font */
187    return kk->qq->height * 10;
188}
189
190static void ncurses_display(caca_t *kk)
191{
192    int x, y;
193    uint8_t *attr = kk->qq->attr;
194    uint32_t *chars = kk->qq->chars;
195    for(y = 0; y < (int)kk->qq->height; y++)
196    {
197        move(y, 0);
198        for(x = kk->qq->width; x--; )
199        {
200            attrset(kk->drv.p->attr[*attr++]);
201            ncurses_write_utf32(*chars++);
202        }
203    }
204    refresh();
205}
206
207static void ncurses_handle_resize(caca_t *kk)
208{
209    struct winsize size;
210
211    if(ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0)
212    {
213        kk->resize.w = size.ws_col;
214        kk->resize.h = size.ws_row;
215#if defined(HAVE_RESIZE_TERM)
216        resize_term(kk->resize.h, kk->resize.w);
217#else
218        resizeterm(*kk->resize.h, *kk->resize.w);
219#endif
220        wrefresh(curscr);
221        return;
222    }
223
224    /* Fallback */
225    kk->resize.w = kk->qq->width;
226    kk->resize.h = kk->qq->height;
227}
228
229static int ncurses_get_event(caca_t *kk, struct caca_event *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.c = 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(kk, ev);
257                break;
258            case BUTTON1_RELEASED:
259                ev->data.mouse.button = 1;
260                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
261                break;
262            case BUTTON1_CLICKED:
263                ev->data.mouse.button = 1;
264                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
265                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
266                break;
267            case BUTTON1_DOUBLE_CLICKED:
268                ev->data.mouse.button = 1;
269                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
270                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
271                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
272                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
273                break;
274            case BUTTON1_TRIPLE_CLICKED:
275                ev->data.mouse.button = 1;
276                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
277                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
278                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
279                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
280                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
281                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, 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(kk, ev);
289                break;
290            case BUTTON2_RELEASED:
291                ev->data.mouse.button = 2;
292                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
293                break;
294            case BUTTON2_CLICKED:
295                ev->data.mouse.button = 2;
296                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
297                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
298                break;
299            case BUTTON2_DOUBLE_CLICKED:
300                ev->data.mouse.button = 2;
301                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
302                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
303                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
304                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
305                break;
306            case BUTTON2_TRIPLE_CLICKED:
307                ev->data.mouse.button = 2;
308                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
309                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
310                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
311                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
312                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
313                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, 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(kk, ev);
321                break;
322            case BUTTON3_RELEASED:
323                ev->data.mouse.button = 3;
324                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
325                break;
326            case BUTTON3_CLICKED:
327                ev->data.mouse.button = 3;
328                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
329                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
330                break;
331            case BUTTON3_DOUBLE_CLICKED:
332                ev->data.mouse.button = 3;
333                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
334                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
335                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
336                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
337                break;
338            case BUTTON3_TRIPLE_CLICKED:
339                ev->data.mouse.button = 3;
340                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
341                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
342                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
343                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
344                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
345                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, 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(kk, ev);
353                break;
354            case BUTTON4_RELEASED:
355                ev->data.mouse.button = 4;
356                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
357                break;
358            case BUTTON4_CLICKED:
359                ev->data.mouse.button = 4;
360                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
361                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
362                break;
363            case BUTTON4_DOUBLE_CLICKED:
364                ev->data.mouse.button = 4;
365                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
366                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
367                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
368                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
369                break;
370            case BUTTON4_TRIPLE_CLICKED:
371                ev->data.mouse.button = 4;
372                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
373                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
374                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
375                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
376                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
377                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
378                break;
379            case BUTTON4_RESERVED_EVENT:
380                break;
381
382            default:
383                break;
384        }
385
386        if(kk->mouse.x == (unsigned int)mevent.x &&
387           kk->mouse.y == (unsigned int)mevent.y)
388            return _pop_event(kk, ev);
389
390        kk->mouse.x = mevent.x;
391        kk->mouse.y = mevent.y;
392
393        ev->type = CACA_EVENT_MOUSE_MOTION;
394        ev->data.mouse.x = kk->mouse.x;
395        ev->data.mouse.y = kk->mouse.y;
396        return 1;
397    }
398
399    switch(intkey)
400    {
401        case KEY_UP: ev->data.key.c = CACA_KEY_UP; break;
402        case KEY_DOWN: ev->data.key.c = CACA_KEY_DOWN; break;
403        case KEY_LEFT: ev->data.key.c = CACA_KEY_LEFT; break;
404        case KEY_RIGHT: ev->data.key.c = CACA_KEY_RIGHT; break;
405
406        case KEY_IC: ev->data.key.c = CACA_KEY_INSERT; break;
407        case KEY_DC: ev->data.key.c = CACA_KEY_DELETE; break;
408        case KEY_HOME: ev->data.key.c = CACA_KEY_HOME; break;
409        case KEY_END: ev->data.key.c = CACA_KEY_END; break;
410        case KEY_PPAGE: ev->data.key.c = CACA_KEY_PAGEUP; break;
411        case KEY_NPAGE: ev->data.key.c = CACA_KEY_PAGEDOWN; break;
412
413        case KEY_F(1): ev->data.key.c = CACA_KEY_F1; break;
414        case KEY_F(2): ev->data.key.c = CACA_KEY_F2; break;
415        case KEY_F(3): ev->data.key.c = CACA_KEY_F3; break;
416        case KEY_F(4): ev->data.key.c = CACA_KEY_F4; break;
417        case KEY_F(5): ev->data.key.c = CACA_KEY_F5; break;
418        case KEY_F(6): ev->data.key.c = CACA_KEY_F6; break;
419        case KEY_F(7): ev->data.key.c = CACA_KEY_F7; break;
420        case KEY_F(8): ev->data.key.c = CACA_KEY_F8; break;
421        case KEY_F(9): ev->data.key.c = CACA_KEY_F9; break;
422        case KEY_F(10): ev->data.key.c = CACA_KEY_F10; break;
423        case KEY_F(11): ev->data.key.c = CACA_KEY_F11; break;
424        case KEY_F(12): ev->data.key.c = 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_kk->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 c)
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(c < 0x80)
499    {
500        addch(c);
501        return;
502    }
503
504#if defined(HAVE_NCURSESW_NCURSES_H)
505    if(c < 0x10000)
506    {
507        addch(c); /* FIXME: doesn't work either */
508        return;
509    }
510
511    bytes = (c < 0x800) ? 2 : (c < 0x10000) ? 3 : 4;
512    buf[bytes] = '\0';
513    parser = buf + bytes;
514
515    switch(bytes)
516    {
517        case 4: *--parser = (c | 0x80) & 0xbf; c >>= 6;
518        case 3: *--parser = (c | 0x80) & 0xbf; c >>= 6;
519        case 2: *--parser = (c | 0x80) & 0xbf; c >>= 6;
520    }
521    *--parser = c | mark[bytes];
522
523    addstr(buf);
524#else
525    addch(' ');
526#endif
527}
528
529/*
530 * Driver initialisation
531 */
532
533int ncurses_install(caca_t *kk)
534{
535    kk->drv.driver = CACA_DRIVER_NCURSES;
536
537    kk->drv.init_graphics = ncurses_init_graphics;
538    kk->drv.end_graphics = ncurses_end_graphics;
539    kk->drv.set_window_title = ncurses_set_window_title;
540    kk->drv.get_window_width = ncurses_get_window_width;
541    kk->drv.get_window_height = ncurses_get_window_height;
542    kk->drv.display = ncurses_display;
543    kk->drv.handle_resize = ncurses_handle_resize;
544    kk->drv.get_event = ncurses_get_event;
545
546    return 0;
547}
548
549#endif /* USE_NCURSES */
550
Note: See TracBrowser for help on using the repository browser.