source: libcaca/trunk/caca/driver_x11.c @ 780

Last change on this file since 780 was 780, checked in by Sam Hocevar, 14 years ago
  • Added truecolor support to the X11 and GL drivers.
  • Property svn:keywords set to Id
File size: 18.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_x11.c 780 2006-04-16 21:52:05Z 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 X11 input and output driver
16 */
17
18#include "config.h"
19
20#if defined(USE_X11)
21
22#include <X11/Xlib.h>
23#include <X11/Xutil.h>
24#include <X11/keysym.h>
25#if defined(HAVE_X11_XKBLIB_H)
26#   include <X11/XKBlib.h>
27#endif
28
29#include <stdio.h> /* BUFSIZ */
30#include <stdlib.h>
31
32#include "caca.h"
33#include "caca_internals.h"
34#include "cucul.h"
35#include "cucul_internals.h"
36
37/*
38 * Local functions
39 */
40static int x11_error_handler(Display *, XErrorEvent *);
41
42struct driver_private
43{
44    Display *dpy;
45    Window window;
46    Pixmap pixmap;
47    GC gc;
48    long int event_mask;
49    int font_width, font_height;
50    int colors[4096];
51    Font font;
52    XFontStruct *font_struct;
53    int font_offset;
54    Cursor pointer;
55#if defined(HAVE_X11_XKBLIB_H)
56    Bool autorepeat;
57#endif
58};
59
60static int x11_init_graphics(caca_t *kk)
61{
62    Colormap colormap;
63    XSetWindowAttributes x11_winattr;
64    int (*old_error_handler)(Display *, XErrorEvent *);
65    char const *fonts[] = { NULL, "8x13bold", "fixed" }, **parser;
66    char const *geometry;
67    unsigned int width = 0, height = 0;
68    int i;
69
70    kk->drv.p = malloc(sizeof(struct driver_private));
71
72#if defined(HAVE_GETENV)
73    geometry = getenv("CACA_GEOMETRY");
74    if(geometry && *geometry)
75        sscanf(geometry, "%ux%u", &width, &height);
76#endif
77
78    if(width && height)
79        _cucul_set_size(kk->qq, width, height);
80
81    kk->drv.p->dpy = XOpenDisplay(NULL);
82    if(kk->drv.p->dpy == NULL)
83        return -1;
84
85#if defined(HAVE_GETENV)
86    fonts[0] = getenv("CACA_FONT");
87    if(fonts[0] && *fonts[0])
88        parser = fonts;
89    else
90#endif
91        parser = fonts + 1;
92
93    /* Ignore font errors */
94    old_error_handler = XSetErrorHandler(x11_error_handler);
95
96    /* Parse our font list */
97    for( ; ; parser++)
98    {
99        if(!*parser)
100        {
101            XSetErrorHandler(old_error_handler);
102            XCloseDisplay(kk->drv.p->dpy);
103            return -1;
104        }
105
106        kk->drv.p->font = XLoadFont(kk->drv.p->dpy, *parser);
107        if(!kk->drv.p->font)
108            continue;
109
110        kk->drv.p->font_struct = XQueryFont(kk->drv.p->dpy, kk->drv.p->font);
111        if(!kk->drv.p->font_struct)
112        {
113            XUnloadFont(kk->drv.p->dpy, kk->drv.p->font);
114            continue;
115        }
116
117        break;
118    }
119
120    /* Reset the default X11 error handler */
121    XSetErrorHandler(old_error_handler);
122
123    kk->drv.p->font_width = kk->drv.p->font_struct->max_bounds.width;
124    kk->drv.p->font_height = kk->drv.p->font_struct->max_bounds.ascent
125                         + kk->drv.p->font_struct->max_bounds.descent;
126    kk->drv.p->font_offset = kk->drv.p->font_struct->max_bounds.descent;
127
128    colormap = DefaultColormap(kk->drv.p->dpy, DefaultScreen(kk->drv.p->dpy));
129    for(i = 0x000; i < 0x1000; i++)
130    {
131        XColor color;
132        color.red = ((i & 0xf00) >> 8) * 0x1111;
133        color.green = ((i & 0x0f0) >> 4) * 0x1111;
134        color.blue = (i & 0x00f) * 0x1111;
135        XAllocColor(kk->drv.p->dpy, colormap, &color);
136        kk->drv.p->colors[i] = color.pixel;
137    }
138
139    x11_winattr.backing_store = Always;
140    x11_winattr.background_pixel = kk->drv.p->colors[0x000];
141    x11_winattr.event_mask = ExposureMask | StructureNotifyMask;
142
143    kk->drv.p->window =
144        XCreateWindow(kk->drv.p->dpy, DefaultRootWindow(kk->drv.p->dpy), 0, 0,
145                      kk->qq->width * kk->drv.p->font_width,
146                      kk->qq->height * kk->drv.p->font_height,
147                      0, 0, InputOutput, 0,
148                      CWBackingStore | CWBackPixel | CWEventMask,
149                      &x11_winattr);
150
151    XStoreName(kk->drv.p->dpy, kk->drv.p->window, "caca for X");
152
153    XSelectInput(kk->drv.p->dpy, kk->drv.p->window, StructureNotifyMask);
154    XMapWindow(kk->drv.p->dpy, kk->drv.p->window);
155
156    kk->drv.p->gc = XCreateGC(kk->drv.p->dpy, kk->drv.p->window, 0, NULL);
157    XSetForeground(kk->drv.p->dpy, kk->drv.p->gc, kk->drv.p->colors[0x888]);
158    XSetFont(kk->drv.p->dpy, kk->drv.p->gc, kk->drv.p->font);
159
160    for(;;)
161    {
162        XEvent xevent;
163        XNextEvent(kk->drv.p->dpy, &xevent);
164        if (xevent.type == MapNotify)
165            break;
166    }
167
168#if defined(HAVE_X11_XKBLIB_H)
169    /* Disable autorepeat */
170    XkbSetDetectableAutoRepeat(kk->drv.p->dpy, True, &kk->drv.p->autorepeat);
171    if(!kk->drv.p->autorepeat)
172        XAutoRepeatOff(kk->drv.p->dpy);
173#endif
174
175    kk->drv.p->event_mask = KeyPressMask | KeyReleaseMask | ButtonPressMask
176          | ButtonReleaseMask | PointerMotionMask | StructureNotifyMask
177          | ExposureMask;
178
179    XSelectInput(kk->drv.p->dpy, kk->drv.p->window, kk->drv.p->event_mask);
180
181    XSync(kk->drv.p->dpy, False);
182
183    kk->drv.p->pixmap = XCreatePixmap(kk->drv.p->dpy, kk->drv.p->window,
184                                   kk->qq->width * kk->drv.p->font_width,
185                                   kk->qq->height * kk->drv.p->font_height,
186                                   DefaultDepth(kk->drv.p->dpy,
187                                            DefaultScreen(kk->drv.p->dpy)));
188    kk->drv.p->pointer = None;
189
190    return 0;
191}
192
193static int x11_end_graphics(caca_t *kk)
194{
195    XSync(kk->drv.p->dpy, False);
196#if defined(HAVE_X11_XKBLIB_H)
197    if(!kk->drv.p->autorepeat)
198        XAutoRepeatOn(kk->drv.p->dpy);
199#endif
200    XFreePixmap(kk->drv.p->dpy, kk->drv.p->pixmap);
201    XFreeFont(kk->drv.p->dpy, kk->drv.p->font_struct);
202    XFreeGC(kk->drv.p->dpy, kk->drv.p->gc);
203    XUnmapWindow(kk->drv.p->dpy, kk->drv.p->window);
204    XDestroyWindow(kk->drv.p->dpy, kk->drv.p->window);
205    XCloseDisplay(kk->drv.p->dpy);
206
207    free(kk->drv.p);
208
209    return 0;
210}
211
212static int x11_set_window_title(caca_t *kk, char const *title)
213{
214    XStoreName(kk->drv.p->dpy, kk->drv.p->window, title);
215    return 0;
216}
217
218static unsigned int x11_get_window_width(caca_t *kk)
219{
220    return kk->qq->width * kk->drv.p->font_width;
221}
222
223static unsigned int x11_get_window_height(caca_t *kk)
224{
225    return kk->qq->height * kk->drv.p->font_height;
226}
227
228static void x11_display(caca_t *kk)
229{
230    unsigned int x, y, len;
231
232    /* First draw the background colours. Splitting the process in two
233     * loops like this is actually slightly faster. */
234    for(y = 0; y < kk->qq->height; y++)
235    {
236        for(x = 0; x < kk->qq->width; x += len)
237        {
238            uint32_t *attr = kk->qq->attr + x + y * kk->qq->width;
239            uint16_t bg = _cucul_argb32_to_rgb12bg(*attr);
240
241            len = 1;
242            while(x + len < kk->qq->width
243                   && _cucul_argb32_to_rgb12bg(attr[len]) == bg)
244                len++;
245
246            XSetForeground(kk->drv.p->dpy, kk->drv.p->gc,
247                           kk->drv.p->colors[bg]);
248            XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap, kk->drv.p->gc,
249                           x * kk->drv.p->font_width, y * kk->drv.p->font_height,
250                           len * kk->drv.p->font_width, kk->drv.p->font_height);
251        }
252    }
253
254    /* Then print the foreground characters */
255    for(y = 0; y < kk->qq->height; y++)
256    {
257        unsigned int yoff = (y + 1) * kk->drv.p->font_height
258                                    - kk->drv.p->font_offset;
259        uint32_t *chars = kk->qq->chars + y * kk->qq->width;
260
261        for(x = 0; x < kk->qq->width; x++, chars++)
262        {
263            uint32_t *attr = kk->qq->attr + x + y * kk->qq->width;
264
265            /* Skip spaces */
266            if(*chars == 0x00000020)
267                continue;
268
269            XSetForeground(kk->drv.p->dpy, kk->drv.p->gc,
270                           kk->drv.p->colors[_cucul_argb32_to_rgb12fg(*attr)]);
271
272            /* Plain ASCII, no problem. */
273            if(*chars > 0x00000020 && *chars < 0x00000080)
274            {
275                char c = (uint8_t)*chars;
276                XDrawString(kk->drv.p->dpy, kk->drv.p->pixmap, kk->drv.p->gc,
277                            x * kk->drv.p->font_width, yoff, &c, 1);
278                continue;
279            }
280
281            /* We want to be able to print a few special Unicode characters
282             * such as the CP437 gradients and half blocks. For unknown
283             * characters, just print '?'. */
284            switch(*chars)
285            {
286                case 0x00002580: /* ▀ */
287                    XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap,
288                                   kk->drv.p->gc,
289                                   x * kk->drv.p->font_width,
290                                   y * kk->drv.p->font_height,
291                                   kk->drv.p->font_width,
292                                   kk->drv.p->font_height / 2);
293                    break;
294                case 0x00002584: /* ▄ */
295                    XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap,
296                                   kk->drv.p->gc,
297                                   x * kk->drv.p->font_width,
298                                   (y + 1) * kk->drv.p->font_height
299                                           - kk->drv.p->font_height / 2,
300                                   kk->drv.p->font_width,
301                                   kk->drv.p->font_height / 2);
302                    break;
303                case 0x00002588: /* █ */
304                    XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap,
305                                   kk->drv.p->gc,
306                                   x * kk->drv.p->font_width,
307                                   y * kk->drv.p->font_height,
308                                   kk->drv.p->font_width,
309                                   kk->drv.p->font_height);
310                    break;
311                case 0x0000258c: /* ▌ */
312                    XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap,
313                                   kk->drv.p->gc,
314                                   x * kk->drv.p->font_width,
315                                   y * kk->drv.p->font_height,
316                                   kk->drv.p->font_width / 2,
317                                   kk->drv.p->font_height);
318                    break;
319                case 0x00002590: /* ▐ */
320                    XFillRectangle(kk->drv.p->dpy, kk->drv.p->pixmap,
321                                   kk->drv.p->gc,
322                                   (x + 1) * kk->drv.p->font_width
323                                           - kk->drv.p->font_width / 2,
324                                   y * kk->drv.p->font_height,
325                                   kk->drv.p->font_width / 2,
326                                   kk->drv.p->font_height);
327                    break;
328                case 0x00002593: /* ▓ */
329                case 0x00002592: /* ▒ */
330                case 0x00002591: /* ░ */
331                {
332                    /* FIXME: this sucks utterly */
333                    int i, j, k = *chars - 0x00002591;
334                    for(j = kk->drv.p->font_height; j--; )
335                        for(i = kk->drv.p->font_width; i--; )
336                    {
337                        if(((i + 2 * (j & 1)) & 3) > k)
338                            continue;
339
340                        XDrawPoint(kk->drv.p->dpy, kk->drv.p->pixmap,
341                                   kk->drv.p->gc,
342                                   x * kk->drv.p->font_width + i,
343                                   y * kk->drv.p->font_height + j);
344                    }
345                    break;
346                }
347                default:
348                {
349                    char c;
350                    c = '?';
351                    XDrawString(kk->drv.p->dpy, kk->drv.p->pixmap,
352                                kk->drv.p->gc,
353                                x * kk->drv.p->font_width, yoff, &c, 1);
354                    break;
355                }
356            }
357        }
358    }
359
360    XCopyArea(kk->drv.p->dpy, kk->drv.p->pixmap, kk->drv.p->window,
361              kk->drv.p->gc, 0, 0,
362              kk->qq->width * kk->drv.p->font_width,
363              kk->qq->height * kk->drv.p->font_height,
364              0, 0);
365    XFlush(kk->drv.p->dpy);
366}
367
368static void x11_handle_resize(caca_t *kk)
369{
370    Pixmap new_pixmap;
371
372    new_pixmap = XCreatePixmap(kk->drv.p->dpy, kk->drv.p->window,
373                               kk->resize.w * kk->drv.p->font_width,
374                               kk->resize.h * kk->drv.p->font_height,
375                               DefaultDepth(kk->drv.p->dpy,
376                                            DefaultScreen(kk->drv.p->dpy)));
377    XCopyArea(kk->drv.p->dpy, kk->drv.p->pixmap, new_pixmap,
378              kk->drv.p->gc, 0, 0,
379              kk->resize.w * kk->drv.p->font_width,
380              kk->resize.h * kk->drv.p->font_height, 0, 0);
381    XFreePixmap(kk->drv.p->dpy, kk->drv.p->pixmap);
382    kk->drv.p->pixmap = new_pixmap;
383}
384
385static int x11_get_event(caca_t *kk, caca_event_t *ev)
386{
387    XEvent xevent;
388    char key;
389
390    while(XCheckWindowEvent(kk->drv.p->dpy, kk->drv.p->window,
391                            kk->drv.p->event_mask, &xevent) == True)
392    {
393        KeySym keysym;
394
395        /* Expose event */
396        if(xevent.type == Expose)
397        {
398            XCopyArea(kk->drv.p->dpy, kk->drv.p->pixmap,
399                      kk->drv.p->window, kk->drv.p->gc, 0, 0,
400                      kk->qq->width * kk->drv.p->font_width,
401                      kk->qq->height * kk->drv.p->font_height, 0, 0);
402            continue;
403        }
404
405        /* Resize event */
406        if(xevent.type == ConfigureNotify)
407        {
408            unsigned int w, h;
409
410            w = (xevent.xconfigure.width + kk->drv.p->font_width / 3)
411                  / kk->drv.p->font_width;
412            h = (xevent.xconfigure.height + kk->drv.p->font_height / 3)
413                  / kk->drv.p->font_height;
414
415            if(!w || !h || (w == kk->qq->width && h == kk->qq->height))
416                continue;
417
418            kk->resize.w = w;
419            kk->resize.h = h;
420            kk->resize.resized = 1;
421
422            continue;
423        }
424
425        /* Check for mouse motion events */
426        if(xevent.type == MotionNotify)
427        {
428            unsigned int newx = xevent.xmotion.x / kk->drv.p->font_width;
429            unsigned int newy = xevent.xmotion.y / kk->drv.p->font_height;
430
431            if(newx >= kk->qq->width)
432                newx = kk->qq->width - 1;
433            if(newy >= kk->qq->height)
434                newy = kk->qq->height - 1;
435
436            if(kk->mouse.x == newx && kk->mouse.y == newy)
437                continue;
438
439            kk->mouse.x = newx;
440            kk->mouse.y = newy;
441
442            ev->type = CACA_EVENT_MOUSE_MOTION;
443            ev->data.mouse.x = kk->mouse.x;
444            ev->data.mouse.y = kk->mouse.y;
445            return 1;
446        }
447
448        /* Check for mouse press and release events */
449        if(xevent.type == ButtonPress)
450        {
451            ev->type = CACA_EVENT_MOUSE_PRESS;
452            ev->data.mouse.button = ((XButtonEvent *)&xevent)->button;
453            return 1;
454        }
455
456        if(xevent.type == ButtonRelease)
457        {
458            ev->type = CACA_EVENT_MOUSE_RELEASE;
459            ev->data.mouse.button = ((XButtonEvent *)&xevent)->button;
460            return 1;
461        }
462
463        /* Check for key press and release events */
464        if(xevent.type == KeyPress)
465            ev->type = CACA_EVENT_KEY_PRESS;
466        else if(xevent.type == KeyRelease)
467            ev->type = CACA_EVENT_KEY_RELEASE;
468        else
469            continue;
470
471        if(XLookupString(&xevent.xkey, &key, 1, NULL, NULL))
472        {
473            ev->data.key.c = key;
474            ev->data.key.ucs4 = key;
475            ev->data.key.utf8[0] = key;
476            ev->data.key.utf8[1] = '\0';
477            return 1;
478        }
479
480        keysym = XKeycodeToKeysym(kk->drv.p->dpy, xevent.xkey.keycode, 0);
481        switch(keysym)
482        {
483            case XK_F1:    ev->data.key.c = CACA_KEY_F1;    break;
484            case XK_F2:    ev->data.key.c = CACA_KEY_F2;    break;
485            case XK_F3:    ev->data.key.c = CACA_KEY_F3;    break;
486            case XK_F4:    ev->data.key.c = CACA_KEY_F4;    break;
487            case XK_F5:    ev->data.key.c = CACA_KEY_F5;    break;
488            case XK_F6:    ev->data.key.c = CACA_KEY_F6;    break;
489            case XK_F7:    ev->data.key.c = CACA_KEY_F7;    break;
490            case XK_F8:    ev->data.key.c = CACA_KEY_F8;    break;
491            case XK_F9:    ev->data.key.c = CACA_KEY_F9;    break;
492            case XK_F10:   ev->data.key.c = CACA_KEY_F10;   break;
493            case XK_F11:   ev->data.key.c = CACA_KEY_F11;   break;
494            case XK_F12:   ev->data.key.c = CACA_KEY_F12;   break;
495            case XK_F13:   ev->data.key.c = CACA_KEY_F13;   break;
496            case XK_F14:   ev->data.key.c = CACA_KEY_F14;   break;
497            case XK_F15:   ev->data.key.c = CACA_KEY_F15;   break;
498            case XK_Left:  ev->data.key.c = CACA_KEY_LEFT;  break;
499            case XK_Right: ev->data.key.c = CACA_KEY_RIGHT; break;
500            case XK_Up:    ev->data.key.c = CACA_KEY_UP;    break;
501            case XK_Down:  ev->data.key.c = CACA_KEY_DOWN;  break;
502
503            default: ev->type = CACA_EVENT_NONE; return 0;
504        }
505
506        ev->data.key.ucs4 = 0;
507        ev->data.key.utf8[0] = '\0';
508        return 1;
509    }
510
511    ev->type = CACA_EVENT_NONE;
512    return 0;
513}
514
515static void x11_set_mouse(caca_t *kk, int flags)
516{
517    Cursor no_ptr;
518    Pixmap bm_no;
519    XColor black, dummy;
520    Colormap colormap;
521    static char const empty[] = { 0, 0, 0, 0, 0, 0, 0, 0 };
522
523    if(flags)
524    {
525        XDefineCursor(kk->drv.p->dpy,kk->drv.p->window, 0);
526        return;
527    }
528
529    colormap = DefaultColormap(kk->drv.p->dpy, DefaultScreen(kk->drv.p->dpy));
530    if(!XAllocNamedColor(kk->drv.p->dpy, colormap, "black", &black, &dummy))
531    {
532        return;
533    }
534    bm_no = XCreateBitmapFromData(kk->drv.p->dpy, kk->drv.p->window,
535                                  empty, 8, 8);
536    no_ptr = XCreatePixmapCursor(kk->drv.p->dpy, bm_no, bm_no,
537                                 &black, &black, 0, 0);
538    XDefineCursor(kk->drv.p->dpy, kk->drv.p->window, no_ptr);
539    XFreeCursor(kk->drv.p->dpy, no_ptr);
540    if(bm_no != None)
541        XFreePixmap(kk->drv.p->dpy, bm_no);
542    XFreeColors(kk->drv.p->dpy, colormap, &black.pixel, 1, 0);
543
544    XSync(kk->drv.p->dpy, False);
545}
546
547/*
548 * XXX: following functions are local
549 */
550
551static int x11_error_handler(Display *dpy, XErrorEvent *xevent)
552{
553    /* Ignore the error */
554    return 0;
555}
556
557/*
558 * Driver initialisation
559 */
560
561int x11_install(caca_t *kk)
562{
563#if defined(HAVE_GETENV)
564    if(!getenv("DISPLAY") || !*(getenv("DISPLAY")))
565        return -1;
566#endif
567
568    kk->drv.driver = CACA_DRIVER_X11;
569
570    kk->drv.init_graphics = x11_init_graphics;
571    kk->drv.end_graphics = x11_end_graphics;
572    kk->drv.set_window_title = x11_set_window_title;
573    kk->drv.get_window_width = x11_get_window_width;
574    kk->drv.get_window_height = x11_get_window_height;
575    kk->drv.display = x11_display;
576    kk->drv.handle_resize = x11_handle_resize;
577    kk->drv.get_event = x11_get_event;
578    kk->drv.set_mouse = x11_set_mouse;
579
580    return 0;
581}
582
583#endif /* USE_X11 */
584
Note: See TracBrowser for help on using the repository browser.