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

Last change on this file since 810 was 810, checked in by Sam Hocevar, 14 years ago
  • Renamed cucul_t into cucul_canvas_t. Eh ouais mon con.
  • 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 *  $Id: driver_ncurses.c 810 2006-04-18 12:59:07Z 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_t *sigwinch_kk; /* 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_t *kk)
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    kk->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_kk = kk;
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, &kk->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            kk->drv.p->attr[fg + 16 * bg] = COLOR_PAIR(col);
140
141            if(max == 8)
142            {
143                /* Bright fg on simple bg */
144                kk->drv.p->attr[fg + 8 + 16 * bg] = A_BOLD | COLOR_PAIR(col);
145                /* Simple fg on bright bg */
146                kk->drv.p->attr[fg + 16 * (bg + 8)] = A_BLINK
147                                                    | COLOR_PAIR(col);
148                /* Bright fg on bright bg */
149                kk->drv.p->attr[fg + 8 + 16 * (bg + 8)] = A_BLINK | A_BOLD
150                                                        | COLOR_PAIR(col);
151            }
152        }
153
154    _cucul_set_size(kk->c, COLS, LINES);
155
156    return 0;
157}
158
159static int ncurses_end_graphics(caca_t *kk)
160{
161    mousemask(kk->drv.p->oldmask, NULL);
162    curs_set(1);
163    noraw();
164    endwin();
165
166    free(kk->drv.p);
167
168    return 0;
169}
170
171static int ncurses_set_window_title(caca_t *kk, char const *title)
172{
173    return 0;
174}
175
176static unsigned int ncurses_get_window_width(caca_t *kk)
177{
178    /* Fallback to a 6x10 font */
179    return kk->c->width * 6;
180}
181
182static unsigned int ncurses_get_window_height(caca_t *kk)
183{
184    /* Fallback to a 6x10 font */
185    return kk->c->height * 10;
186}
187
188static void ncurses_display(caca_t *kk)
189{
190    int x, y;
191    uint32_t *attr = kk->c->attr;
192    uint32_t *chars = kk->c->chars;
193    for(y = 0; y < (int)kk->c->height; y++)
194    {
195        move(y, 0);
196        for(x = kk->c->width; x--; )
197        {
198            attrset(kk->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_t *kk)
206{
207    struct winsize size;
208
209    if(ioctl(fileno(stdout), TIOCGWINSZ, &size) == 0)
210    {
211        kk->resize.w = size.ws_col;
212        kk->resize.h = size.ws_row;
213#if defined(HAVE_RESIZE_TERM)
214        resize_term(kk->resize.h, kk->resize.w);
215#else
216        resizeterm(*kk->resize.h, *kk->resize.w);
217#endif
218        wrefresh(curscr);
219        return;
220    }
221
222    /* Fallback */
223    kk->resize.w = kk->c->width;
224    kk->resize.h = kk->c->height;
225}
226
227static int ncurses_get_event(caca_t *kk, caca_event_t *ev)
228{
229    int intkey;
230
231    intkey = getch();
232    if(intkey == ERR)
233    {
234        ev->type = CACA_EVENT_NONE;
235        return 0;
236    }
237
238    if(intkey < 0x100)
239    {
240        ev->type = CACA_EVENT_KEY_PRESS;
241        ev->data.key.ch = intkey;
242        return 1;
243    }
244
245    if(intkey == KEY_MOUSE)
246    {
247        MEVENT mevent;
248        getmouse(&mevent);
249
250        switch(mevent.bstate)
251        {
252            case BUTTON1_PRESSED:
253                ev->data.mouse.button = 1;
254                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
255                break;
256            case BUTTON1_RELEASED:
257                ev->data.mouse.button = 1;
258                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
259                break;
260            case BUTTON1_CLICKED:
261                ev->data.mouse.button = 1;
262                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
263                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
264                break;
265            case BUTTON1_DOUBLE_CLICKED:
266                ev->data.mouse.button = 1;
267                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
268                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
269                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
270                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
271                break;
272            case BUTTON1_TRIPLE_CLICKED:
273                ev->data.mouse.button = 1;
274                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
275                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
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                break;
281            case BUTTON1_RESERVED_EVENT:
282                break;
283
284            case BUTTON2_PRESSED:
285                ev->data.mouse.button = 2;
286                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
287                break;
288            case BUTTON2_RELEASED:
289                ev->data.mouse.button = 2;
290                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
291                break;
292            case BUTTON2_CLICKED:
293                ev->data.mouse.button = 2;
294                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
295                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
296                break;
297            case BUTTON2_DOUBLE_CLICKED:
298                ev->data.mouse.button = 2;
299                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
300                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
301                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
302                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
303                break;
304            case BUTTON2_TRIPLE_CLICKED:
305                ev->data.mouse.button = 2;
306                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
307                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
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                break;
313            case BUTTON2_RESERVED_EVENT:
314                break;
315
316            case BUTTON3_PRESSED:
317                ev->data.mouse.button = 3;
318                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
319                break;
320            case BUTTON3_RELEASED:
321                ev->data.mouse.button = 3;
322                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
323                break;
324            case BUTTON3_CLICKED:
325                ev->data.mouse.button = 3;
326                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
327                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
328                break;
329            case BUTTON3_DOUBLE_CLICKED:
330                ev->data.mouse.button = 3;
331                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
332                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
333                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
334                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
335                break;
336            case BUTTON3_TRIPLE_CLICKED:
337                ev->data.mouse.button = 3;
338                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
339                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
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                break;
345            case BUTTON3_RESERVED_EVENT:
346                break;
347
348            case BUTTON4_PRESSED:
349                ev->data.mouse.button = 4;
350                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
351                break;
352            case BUTTON4_RELEASED:
353                ev->data.mouse.button = 4;
354                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
355                break;
356            case BUTTON4_CLICKED:
357                ev->data.mouse.button = 4;
358                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
359                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
360                break;
361            case BUTTON4_DOUBLE_CLICKED:
362                ev->data.mouse.button = 4;
363                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
364                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
365                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
366                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
367                break;
368            case BUTTON4_TRIPLE_CLICKED:
369                ev->data.mouse.button = 4;
370                ev->type = CACA_EVENT_MOUSE_PRESS; _push_event(kk, ev);
371                ev->type = CACA_EVENT_MOUSE_RELEASE; _push_event(kk, ev);
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                break;
377            case BUTTON4_RESERVED_EVENT:
378                break;
379
380            default:
381                break;
382        }
383
384        if(kk->mouse.x == (unsigned int)mevent.x &&
385           kk->mouse.y == (unsigned int)mevent.y)
386            return _pop_event(kk, ev);
387
388        kk->mouse.x = mevent.x;
389        kk->mouse.y = mevent.y;
390
391        ev->type = CACA_EVENT_MOUSE_MOTION;
392        ev->data.mouse.x = kk->mouse.x;
393        ev->data.mouse.y = kk->mouse.y;
394        return 1;
395    }
396
397    switch(intkey)
398    {
399        case KEY_UP: ev->data.key.ch = CACA_KEY_UP; break;
400        case KEY_DOWN: ev->data.key.ch = CACA_KEY_DOWN; break;
401        case KEY_LEFT: ev->data.key.ch = CACA_KEY_LEFT; break;
402        case KEY_RIGHT: ev->data.key.ch = CACA_KEY_RIGHT; break;
403
404        case KEY_IC: ev->data.key.ch = CACA_KEY_INSERT; break;
405        case KEY_DC: ev->data.key.ch = CACA_KEY_DELETE; break;
406        case KEY_HOME: ev->data.key.ch = CACA_KEY_HOME; break;
407        case KEY_END: ev->data.key.ch = CACA_KEY_END; break;
408        case KEY_PPAGE: ev->data.key.ch = CACA_KEY_PAGEUP; break;
409        case KEY_NPAGE: ev->data.key.ch = CACA_KEY_PAGEDOWN; break;
410
411        case KEY_F(1): ev->data.key.ch = CACA_KEY_F1; break;
412        case KEY_F(2): ev->data.key.ch = CACA_KEY_F2; break;
413        case KEY_F(3): ev->data.key.ch = CACA_KEY_F3; break;
414        case KEY_F(4): ev->data.key.ch = CACA_KEY_F4; break;
415        case KEY_F(5): ev->data.key.ch = CACA_KEY_F5; break;
416        case KEY_F(6): ev->data.key.ch = CACA_KEY_F6; break;
417        case KEY_F(7): ev->data.key.ch = CACA_KEY_F7; break;
418        case KEY_F(8): ev->data.key.ch = CACA_KEY_F8; break;
419        case KEY_F(9): ev->data.key.ch = CACA_KEY_F9; break;
420        case KEY_F(10): ev->data.key.ch = CACA_KEY_F10; break;
421        case KEY_F(11): ev->data.key.ch = CACA_KEY_F11; break;
422        case KEY_F(12): ev->data.key.ch = CACA_KEY_F12; break;
423
424        default: ev->type = CACA_EVENT_NONE; return 0;
425    }
426
427    ev->type = CACA_EVENT_KEY_PRESS;
428    ev->data.key.ucs4 = 0;
429    ev->data.key.utf8[0] = '\0';
430    return 1;
431}
432
433/*
434 * XXX: following functions are local
435 */
436
437#if defined(HAVE_SIGNAL)
438static RETSIGTYPE sigwinch_handler(int sig)
439{
440    sigwinch_kk->resize.resized = 1;
441
442    signal(SIGWINCH, sigwinch_handler);
443}
444#endif
445
446#if defined(HAVE_GETENV) && defined(HAVE_PUTENV)
447static void ncurses_check_terminal(void)
448{
449    char *term, *colorterm, *other;
450
451    term = getenv("TERM");
452    colorterm = getenv("COLORTERM");
453
454    if(term && !strcmp(term, "xterm"))
455    {
456        /* If we are using gnome-terminal, it's really a 16 colour terminal */
457        if(colorterm && !strcmp(colorterm, "gnome-terminal"))
458        {
459            SCREEN *screen;
460            screen = newterm("xterm-16color", stdout, stdin);
461            if(screen == NULL)
462                return;
463            endwin();
464            (void)putenv("TERM=xterm-16color");
465            return;
466        }
467
468        /* Ditto if we are using Konsole */
469        other = getenv("KONSOLE_DCOP_SESSION");
470        if(other)
471        {
472            SCREEN *screen;
473            screen = newterm("xterm-16color", stdout, stdin);
474            if(screen == NULL)
475                return;
476            endwin();
477            (void)putenv("TERM=xterm-16color");
478            return;
479        }
480    }
481}
482#endif
483
484static void ncurses_write_utf32(uint32_t c)
485{
486#if defined(HAVE_NCURSESW_NCURSES_H)
487    static const uint8_t mark[7] =
488    {
489        0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC
490    };
491
492    char buf[10], *parser;
493    int bytes;
494#endif
495
496    if(c < 0x80)
497    {
498        addch(c);
499        return;
500    }
501
502#if defined(HAVE_NCURSESW_NCURSES_H)
503    if(c < 0x10000)
504    {
505        addch(c); /* FIXME: doesn't work either */
506        return;
507    }
508
509    bytes = (c < 0x800) ? 2 : (c < 0x10000) ? 3 : 4;
510    buf[bytes] = '\0';
511    parser = buf + bytes;
512
513    switch(bytes)
514    {
515        case 4: *--parser = (c | 0x80) & 0xbf; c >>= 6;
516        case 3: *--parser = (c | 0x80) & 0xbf; c >>= 6;
517        case 2: *--parser = (c | 0x80) & 0xbf; c >>= 6;
518    }
519    *--parser = c | mark[bytes];
520
521    addstr(buf);
522#else
523    addch(' ');
524#endif
525}
526
527/*
528 * Driver initialisation
529 */
530
531int ncurses_install(caca_t *kk)
532{
533    kk->drv.driver = CACA_DRIVER_NCURSES;
534
535    kk->drv.init_graphics = ncurses_init_graphics;
536    kk->drv.end_graphics = ncurses_end_graphics;
537    kk->drv.set_window_title = ncurses_set_window_title;
538    kk->drv.get_window_width = ncurses_get_window_width;
539    kk->drv.get_window_height = ncurses_get_window_height;
540    kk->drv.display = ncurses_display;
541    kk->drv.handle_resize = ncurses_handle_resize;
542    kk->drv.get_event = ncurses_get_event;
543    kk->drv.set_mouse = NULL;
544
545    return 0;
546}
547
548#endif /* USE_NCURSES */
549
Note: See TracBrowser for help on using the repository browser.