aboutsummaryrefslogtreecommitdiffstats
path: root/drv_X11.c
blob: ef5624589bb8a1f4542f1a9a865048a3c6ef5a8f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/* $Id: widget.h,v 1.20 2006/08/08 20:16:29 harbaum Exp $
 *
 * generic widget handling
 *
 * Copyright (C) 2003, 2004 Michael Reinelt <reinelt@eunet.at>
 * Copyright (C) 2004 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
 *
 * This file is part of LCD4Linux.
 *
 * LCD4Linux is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * LCD4Linux is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 * $Log: widget.h,v $
 * Revision 1.20  2006/08/08 20:16:29  harbaum
 * Added "extracolor" (used for e.g. bar border) and RGB support for LEDMATRIX
 *
 * Revision 1.19  2006/02/21 15:55:59  cmay
 * removed new update function for keypad, consolidated it with draw
 *
 * Revision 1.18  2006/02/21 05:50:34  reinelt
 * keypad support from Cris Maj
 *
 * Revision 1.17  2006/01/30 05:47:38  reinelt
 * graphic subsystem changed to full-color RGBA
 *
 * Revision 1.16  2006/01/23 06:17:18  reinelt
 * timer widget added
 *
 * Revision 1.15  2005/12/18 16:18:36  reinelt
 * GPO's added again
 *
 * Revision 1.14  2005/11/06 09:17:20  reinelt
 * re-use icons (thanks to Jesus de Santos Garcia)
 *
 * Revision 1.13  2005/05/08 04:32:45  reinelt
 * CodingStyle added and applied
 *
 * Revision 1.12  2005/01/18 06:30:24  reinelt
 * added (C) to all copyright statements
 *
 * Revision 1.11  2004/06/26 12:05:00  reinelt
 *
 * uh-oh... the last CVS log message messed up things a lot...
 *
 * Revision 1.10  2004/06/26 09:27:21  reinelt
 *
 * added '-W' to CFLAGS
 * changed all C++ comments to C ones
 * cleaned up a lot of signed/unsigned mistakes
 *
 * Revision 1.9  2004/06/20 10:09:56  reinelt
 *
 * 'const'ified the whole source
 *
 * Revision 1.8  2004/03/03 03:47:04  reinelt
 * big patch from Martin Hejl:
 * - use qprintf() where appropriate
 * - save CPU cycles on gettimeofday()
 * - add quit() functions to free allocated memory
 * - fixed lots of memory leaks
 *
 * Revision 1.7  2004/01/14 11:33:00  reinelt
 * new plugin 'uname' which does what it's called
 * text widget nearly finished
 * first results displayed on MatrixOrbital
 *
 * Revision 1.6  2004/01/13 08:18:20  reinelt
 * timer queues added
 * liblcd4linux deactivated turing transformation to new layout
 *
 * Revision 1.5  2004/01/11 18:26:02  reinelt
 * further widget and layout processing
 *
 * Revision 1.4  2004/01/10 20:22:33  reinelt
 * added new function 'cfg_list()' (not finished yet)
 * added layout.c (will replace processor.c someday)
 * added widget_text.c (will be the first and most important widget)
 * modified lcd4linux.c so that old-style configs should work, too
 *
 * Revision 1.3  2004/01/10 17:34:40  reinelt
 * further matrixOrbital changes
 * widgets initialized
 *
 * Revision 1.2  2003/10/05 17:58:50  reinelt
 * libtool junk; copyright messages cleaned up
 *
 * Revision 1.1  2003/09/19 03:51:29  reinelt
 * minor fixes, widget.c added
 *
 */


#ifndef _WIDGET_H_
#define _WIDGET_H_

#include "rgb.h"


struct WIDGET;			/* forward declaration */


typedef struct WIDGET_CLASS {
    char *name;
    int type;
    int (*init) (struct WIDGET * Self);
    int (*draw) (struct WIDGET * Self);
    int (*find) (struct WIDGET * Self, void *needle);
    int (*quit) (struct WIDGET * Self);
} WIDGET_CLASS;


typedef struct WIDGET {
    char *name;
    WIDGET_CLASS *class;
    struct WIDG
/* $Id$
 * $URL$
 *
 * new style X11 Driver for LCD4Linux 
 *
 * Copyright (C) 2003 Michael Reinelt <reinelt@eunet.at>
 * Copyright (C) 2004 The LCD4Linux Team <lcd4linux-devel@users.sourceforge.net>
 *
 * based on the old XWindow.c which is
 * Copyright (C) 2000 Herbert Rosmanith <herp@wildsau.idv.uni-linz.ac.at>
 *
 * This file is part of LCD4Linux.
 *
 * LCD4Linux is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * LCD4Linux is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* 
 *
 * exported fuctions:
 *
 * struct DRIVER drv_X11
 *
 */


#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <termios.h>
#include <fcntl.h>
#include <sys/time.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "debug.h"
#include "cfg.h"
#include "qprintf.h"
#include "timer.h"
#include "plugin.h"
#include "drv.h"
#include "widget.h"
#include "widget_keypad.h"
#include "drv_generic_graphic.h"
#include "drv_generic_keypad.h"

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif

static char Name[] = "X11";

static int pixel = -1;		/* pointsize in pixel */
static int pgap = 0;		/* gap between points */
static int rgap = 0;		/* row gap between lines */
static int cgap = 0;		/* column gap between characters */
static int border = 0;		/* window border */
static int buttons = 0;		/* number of keypad buttons */
static int btnwidth = 0;
static int btnheight = 0;

static int dimx, dimy;		/* total window dimension in pixel */

static RGBA *drv_X11_FB = NULL;

static Display *dp;
static int sc, dd;
static Window w, rw;
static Visual *vi;
static GC gc;
static Colormap cm;
static XColor xc;
static Pixmap pm;


/****************************************/
/***  hardware dependant functions    ***/
/****************************************/

static void drv_X11_color(RGBA c)
{
    xc.red = 255 * c.R;
    xc.green = 255 * c.G;
    xc.blue = 255 * c.B;
    xc.flags = DoRed | DoGreen | DoBlue;
    if (XAllocColor(dp, cm, &xc) == False) {
	error("%s: XAllocColor(%02x%02x%02x) failed!", Name, c.R, c.G, c.B);
    }
    XSetForeground(dp, gc, xc.pixel);
    XSetBackground(dp, gc, xc.pixel);

}


static void drv_X11_blit(const int row, const int col, const int height, const int width)
{
    int r, c;
    int dirty = 0;

    for (r = row; r < row + height; r++) {
	int y = border + (r / YRES) * rgap + r * (pixel + pgap);
	for (c = col; c < col + width; c++) {
	    int x = border + (c / XRES) * cgap + c * (pixel + pgap);
	    RGBA p1 = drv_X11_FB[r * DCOLS + c];
	    RGBA p2 = drv_generic_graphic_rgb(r, c);
	    if (p1.R != p2.R || p1.G != p2.G || p1.B != p2.B) {
		drv_X11_color(p2);
		XFillRectangle(dp, w, gc, x, y, pixel, pixel);
		drv_X11_FB[r * DCOLS + c] = p2;
		dirty = 1;
	    }
	}
    }
    if (dirty) {
	XSync(dp, False);
    }
}


static int drv_X11_brightness(int brightness)
{
    static unsigned char Brightness = 0;
    RGBA col = BL_COL;
    int i;
    float dim;
    
    /* -1 is used to query the current brightness */
    if (brightness == -1)
	return Brightness;
    
    if (brightness < 0)
	brightness = 0;
    if (brightness > 255)
	brightness = 255;
    Brightness = brightness;
    dim = Brightness / 255.0;
    col.R *= dim;
    col.G *= dim;
    col.B *= dim;

    debug("%s: set backlight to %d%%, bl_col=0x%8x, dimmed=0x%8x", Name, (int)(dim * 100), BL_COL, col);
    for (i = 0; i < DCOLS * DROWS; i++) {
	drv_X11_FB[i] = col;
    }
    
    drv_X11_color(col);
    
    XFillRectangle(dp, pm, gc, 0, 0, dimx + 2 * border + btnwidth, dimy + 2 * border);
    XSetWindowBackground(dp, w, xc.pixel);
    XClearWindow(dp, w);

    return Brightness;
}


static int drv_X11_keypad(const int num)
{
    int val = WIDGET_KEY_PRESSED;
    
    switch (num) {
        case 1:
            val += WIDGET_KEY_UP;
            break;
        case 2:
            val += WIDGET_KEY_DOWN;
            break;
        case 3:
            val += WIDGET_KEY_LEFT;
            break;
        case 4:
            val += WIDGET_KEY_RIGHT;
            break;
        case 5:
            val += WIDGET_KEY_CONFIRM;
            break;
        case 6:
            val += WIDGET_KEY_CANCEL;
            break;
        default:
            error("%s: unknown keypad value %d", Name, num);
    }
    
    debug("%s: key %c (0x%x) pressed", Name, num, num);
    return val;
}


static void drv_X11_expose(const int x, const int y, const int width, const int height)
{
    /*
     * theory of operation:
     * instead of the old, fully-featured but complicated update
     * region calculation, we do an update of the whole display,
     * but check before every pixel if the pixel region is inside
     * the update region.
     */

    int r, c;
    int x0, y0;
    int x1, y1;
    XFontStruct *xfs;
    int xoffset = border + (DCOLS / XRES) * cgap + DCOLS * (pixel + pgap);
    int yoffset = border + (DROWS / YRES) * rgap;
    int yk;
    char *s;
    char unknownTxt[10];
    
    x0 = x - pixel;
    x1 = x + pixel + width;
    y0 = y - pixel;
    y1 = y + pixel + height;

    for (r = 0; r < DROWS; r++) {
	int yc = border + (r / YRES) * rgap + r * (pixel + pgap);
	if (yc < y0 || yc > y1)
	    continue;
	for (c = 0; c < DCOLS; c++) {
	    int xc = border + (c / XRES) * cgap + c * (pixel + pgap);
	    if (xc < x0 || xc > x1)
		continue;
	    drv_X11_color(drv_generic_graphic_rgb(r, c));
	    XFillRectangle(dp, w, gc, xc, yc, pixel, pixel);
	}
    }
    
    /* Keypad on the right side */
    if (x1 >= xoffset) {
        xfs = XQueryFont(dp, XGContextFromGC(DefaultGC(dp, 0)));
        drv_X11_color(FG_COL);
        for (r = 0; r < buttons; r++) {
            yk = yoffset + r * (btnheight + pgap);
            switch(r) {
                case 0:
                    s = "Up";
                    break;
                case 1:
                    s = "Down";
                    break;
                case 2:
                    s = "Left";
                    break;
                case 3:
                    s = "Right";
                    break;
                case 4:
                    s = "Confirm";
                    break;
                case 5:
                    s = "Cancel";
                    break;
                default:
                    snprintf(unknownTxt, sizeof(unknownTxt), "#%d??", r);
                    s = unknownTxt;
            }
            XDrawRectangle(dp, w, gc, xoffset, yk, btnwidth, btnheight - 2);
            XDrawString(dp, w, gc,
                        xoffset + btnwidth / 2 - (xfs->max_bounds.width * strlen(s)) / 2, yk + btnheight / 2 + xfs->max_bounds.ascent / 2,
                        s, strlen(s));    	
        }
    }
        //XSync(dp, False);
}


static void drv_X11_timer( __attribute__ ((unused))
			  void *notused)
{
    XEvent ev;
    int xoffset = border + (DCOLS / XRES) * cgap + DCOLS * (pixel + pgap);
    int yoffset = border + (DROWS / YRES) * rgap;
    static int btn = 0;
 
    if (XCheckWindowEvent(dp, w, ExposureMask | ButtonPressMask | ButtonReleaseMask, &ev) == 0)
	return;
    switch(ev.type) {
        case Expose:
            drv_X11_expose(ev.xexpose.x, ev.xexpose.y, ev.xexpose.width, ev.xexpose.height);
            break;
        case ButtonPress:
            if ( ev.xbutton.x >= xoffset && ev.xbutton.x <= xoffset + btnwidth
                 && ev.xbutton.y >= yoffset && ev.xbutton.y <= yoffset + buttons * btnheight + (buttons -1 ) *pgap ) {
                btn = (ev.xbutton.y - yoffset) / (btnheight + pgap) + 1;    /* btn 0 is unused */
                drv_X11_color(BG_COL);
                XFillRectangle(dp, w, gc, xoffset + 1, yoffset + (btn - 1) * (btnheight + pgap) + 1, btnwidth - 1, btnheight - 2 - 1);
                drv_generic_keypad_press(btn);
            }
            break;
        case ButtonRelease:
            if ( ev.xbutton.x >= xoffset && ev.xbutton.x <= xoffset + btnwidth
		 && ev.xbutton.y >= yoffset && ev.xbutton.y <= yoffset + buttons * btnheight + (buttons -1 ) *pgap ) {
                XClearArea(dp, w, xoffset, yoffset + (btn - 1) * (btnheight + pgap), btnwidth, btnheight - 2, 1 /* true */);
                btn = (ev.xbutton.y - yoffset) / (btnheight + pgap) + 1;    /* btn 0 is unused */
                info("%s: Button %d released", Name, btn);
            }
            break;
        default:
            debug("%s: unknown XEvent %d", Name, ev.type);
    }
}


static int drv_X11_start(const char *section)
{
    int i;
    char *s;
    XSetWindowAttributes wa;
    XSizeHints sh;
    XEvent ev;

    /* read display size from config */
    if (sscanf(s = cfg_get(section, "Size", "120x32"), "%dx%d", &DCOLS, &DROWS) != 2 || DCOLS < 1 || DROWS < 1) {
	error("%s: bad %s.Size '%s' from %s", Name, section, s, cfg_source());
	free(s);
	return -1;
    }
    free(s);

    if (sscanf(s = cfg_get(section, "font", "5x8"), "%dx%d", &XRES, &YRES) != 2 || XRES < 1 || YRES < 1) {
	error("%s: bad %s.font '%s' from %s", Name, section, s, cfg_source());
	free(s);
	return -1;
    }
    free(s);

    if (sscanf(s = cfg_get(section, "pixel", "4+1"), "%d+%d", &pixel, &pgap) != 2 || pixel < 1 || pgap < 0) {
	error("%s: bad %s.pixel '%s' from %s", Name, section, s, cfg_source());
	free(s);
	return -1;
    }
    free(s);

    if (sscanf(s = cfg_get(section, "gap", "-1x-1"), "%dx%d", &cgap, &rgap) != 2 || cgap < -1 || rgap < -1) {
	error("%s: bad %s.gap '%s' from %s", Name, section, s, cfg_source());
	free(s);
	return -1;
    }
    free(s);

    if (rgap < 0)
	rgap = pixel + pgap;
    if (cgap < 0)
	cgap = pixel + pgap;

    if (cfg_number(section, "border", 0, 0, -1, &border) < 0)
	return -1;

    /* we need the basecolor for the brightness early */
    s = cfg_get(section, "basecolor", "000000ff");
    if (color2RGBA(s, &BL_COL) < 0) {
	error("%s: ignoring illegal color '%s'", Name, s);
    }
    free(s);
    
    /* virtual keyboard: number of buttons (0..6) */
    if (cfg_number(section, "buttons", 0, 0, 6, &buttons) < 0)
    	return -1;

    drv_X11_FB = malloc(DCOLS * DROWS * sizeof(*drv_X11_FB));
    if (drv_X11_FB == NULL) {
	error("%s: framebuffer could not be allocated: malloc() failed", Name);
	return -1;
    }

    for (i = 0; i < DCOLS * DROWS; i++) {
	drv_X11_FB[i] = BL_COL;
    }

    if ((dp = XOpenDisplay(NULL)) == NULL) {
	error("%s: can't open display", Name);
	return -1;
    }

    sc = DefaultScreen(dp);
    gc = DefaultGC(dp, sc);
    vi = DefaultVisual(dp, sc);
    dd = DefaultDepth(dp, sc);
    rw = DefaultRootWindow(dp);
    cm = DefaultColormap(dp, sc);

    dimx = DCOLS * pixel + (DCOLS - 1) * pgap + (DCOLS / XRES - 1) * cgap;
    dimy = DROWS * pixel + (DROWS - 1) * pgap + (DROWS / YRES - 1) * rgap;
    if (buttons != 0) {
	btnwidth = (DCOLS * pixel + (DCOLS - 1) * pgap) / 10;
	btnheight = (DROWS * pixel + (DROWS - 1) * pgap) / buttons;
    }

    wa.event_mask = ExposureMask | ButtonPressMask | ButtonReleaseMask;

    sh.min_width = sh.max_width = dimx + 2 * border + btnwidth;
    sh.min_height = sh.max_height = dimy + 2 * border;
    sh.flags = PPosition | PSize | PMinSize | PMaxSize;

    w = XCreateWindow(dp, rw, 0, 0, sh.min_width, sh.min_height, 0, 0, InputOutput, vi, CWEventMask, &wa);

    pm = XCreatePixmap(dp, w, dimx, dimy, dd);

    XSetWMProperties(dp, w, NULL, NULL, NULL, 0, &sh, NULL, NULL);

    drv_X11_color(BL_COL);

    XFillRectangle(dp, pm, gc, 0, 0, sh.min_width, sh.min_height);
    XSetWindowBackground(dp, w, xc.pixel);
    XClearWindow(dp, w);

    /* set brightness (after first background painting) */
    if (cfg_number(section, "Brightness", 0, 0, 255, &i) > 0) {
	drv_X11_brightness(i);
    }

    XStoreName(dp, w, "LCD4Linux");
    XMapWindow(dp, w);

    XFlush(dp);

    while (1) {
	XNextEvent(dp, &ev);
	if (ev.type == Expose && ev.xexpose.count == 0)
	    break;
    }

    /* regularly process X events */
    /* Fixme: make 20msec configurable */
    timer_add(drv_X11_timer, NULL, 20, 0);

    return 0;
}



/****************************************/
/***            plugins               ***/
/****************************************/

static void plugin_brightness(RESULT * result, const int argc, RESULT * argv[])
{
    double brightness;
    
    switch (argc) {
        case 0:
            brightness = drv_X11_brightness(-1);
            SetResult(&result, R_NUMBER, &brightness);
            break;
        case 1:
            brightness = drv_X11_brightness(R2N(argv[0]));
            SetResult(&result, R_NUMBER, &brightness);
            break;
        default:
            error("%s.brightness(): wrong number of parameters", Name);
            SetResult(&result, R_STRING, "");
    }
}


/****************************************/
/***        exported functions        ***/
/****************************************/


/* list models */
int drv_X11_list(void)
{
    printf("any");
    return 0;
}


/* initialize driver & display */
int drv_X11_init(const char *section, const int quiet)
{
    int ret;

    info("%s: %s", Name, "$Rev$");

    /* start display */
    if ((ret = drv_X11_start(section)) != 0)
	return ret;

    /* real worker functions */
    drv_generic_graphic_real_blit = drv_X11_blit;
    drv_generic_keypad_real_press = drv_X11_keypad;

    /* initialize generic graphic driver */
    if ((ret = drv_generic_graphic_init(section, Name)) != 0)
	return ret;

    /* initialize generic key pad driver */
    if ((ret = drv_generic_keypad_init(section, Name)) != 0)
	return ret;
    
    drv_generic_graphic_clear();

    /* initially expose window */
    drv_X11_expose(0, 0, dimx + 2 * border, dimy + 2 * border);

    if (!quiet) {
	char buffer[40];
	qprintf(buffer, sizeof(buffer), "%s %dx%d", Name, DCOLS, DROWS);
	if (drv_generic_graphic_greet(buffer, NULL)) {
	    sleep(3);
	    drv_generic_graphic_clear();
	}
    }

    /* register plugins */
    AddFunction("LCD::brightness", -1, plugin_brightness);


    return 0;
}


/* close driver & display */
int drv_X11_quit(const __attribute__ ((unused))
		 int quiet)
{

    info("%s: shutting down.", Name);
    drv_generic_graphic_quit();
    drv_generic_keypad_quit();

    if (drv_X11_FB) {
	free(drv_X11_FB);
	drv_X11_FB = NULL;
    }

    return (0);
}


DRIVER drv_X11 = {
    .name = Name,
    .list = drv_X11_list,
    .init = drv_X11_init,
    .quit = drv_X11_quit,
};