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

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