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