/*
 * File: abclock.c
 * Part of the ABClock package
 * (c) Peter Kleiweg
 *
 * 2000/08/15: version 1.0, 1.0a
 * 2000/08/09: version 0.9d
 * 2000/08/07: version 0.9a, 0.9b, 0.9c
 * 2000/08/06: version 0.9
 * 2000/08/03: version 0.1
 *
 * This 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.
 *
 */

/* Comment out the next line if you're using X11 older than X11R4 */
#define X11R4UP

#include <ctype.h>
#include <errno.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <values.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "abclib.h"

int
    maxwidth,
    maxheight,
    xsize,
    ysize,
    border = -1;

unsigned long
    colors [5],
    fg_color;

int
    interval = 10,
    arg_c;

char
    *geometry = NULL,
    *bgcolor = NULL,
    *fgcolor = NULL,
    *hrcolor = NULL,
    *mncolor = NULL,
    *sqcolor = NULL,
    *incolor = NULL,
    **arg_v,
    *programname,
    *no_mem_buffer,
    out_of_memory [] = "Out of memory";

Pixmap
    bitmap = (Pixmap) None;
Display
    *display;
GC
    gc,
    bit_gc;
Window
    window,
    rootwindow;
int
    depth,
    screen;
unsigned long
    black,
    white;
XEvent
    event;
#ifdef X11R4UP
Atom
    wm_delete_window;
#endif

void
    syntax (void),
    process_args (void),
    setup_clock (void),
    show_it (void),
    get_programname (char const *argv0),
    *s_malloc (size_t size),
    errit (char const *format, ...),
    start_graphics (void),
    ConnectToServer (void),
    OpenWindow (void),
#ifdef X11R4UP
    SetProtocols (void),
#endif
    SetSizeHints (void),
    SetWMHints (void),
    SetClassHints (void),
    CreateGC (void);
int
    SetUpVisual (Display *display, int screen, Visual **visual, int *depth),
    SetUpColormap (
        Display *display,
        int screen,
        Window window,
        Visual *visual,
        Colormap *colormap);
unsigned long
    AllocColor (
        Display *display,
        Colormap colormap,
        char const *colorname,
        unsigned long default_color);
char
    *get_arg (void),
    *s_strdup (char const *s);

int main (int argc, char *argv [])
{
    int
        need_setup,
	busy,
	fd;
    struct timeval
	tv;
    fd_set
	rfds;

    no_mem_buffer = (char *) malloc (1024);

    get_programname (argv [0]);

    arg_c = argc;
    arg_v = argv;
    process_args ();

    ConnectToServer ();

    start_graphics ();

    fd = ConnectionNumber (display);
    for (busy = need_setup = 1; busy;) {
	if (need_setup) {
	    setup_clock ();
	    need_setup = 0;
	}
	show_it ();
        tv.tv_sec = interval;
        tv.tv_usec = 0;
        FD_ZERO (&rfds);
        FD_SET (fd, &rfds);
	XFlush (display);
	if (select (fd + 1, &rfds, NULL, NULL, &tv))
	    while (XPending (display)) {
		XNextEvent (display, &event);
		switch (event.type) {
#ifdef X11R4UP
		    case ClientMessage:
			if (event.xclient.data.l [0] == wm_delete_window)
			    busy = 0;
			break;
#endif
		    case ConfigureNotify:
			xsize = event.xconfigure.width;
			ysize = event.xconfigure.height;
			need_setup = 1;
			break;
		}
	    }
    }

    XFreePixmap (display, bitmap);
    XCloseDisplay (display);

    return 0;
}

void process_args ()
{
    while (arg_c > 1) {
	if (! strcmp (arg_v [1], "-geometry"))
	    geometry = get_arg ();
	else if (! strcmp (arg_v [1], "-border")) {
	    border = atoi (get_arg ());
	    if (border < 0 || border > 200)
		errit ("Value for -border out of range");
	} else if (! strcmp (arg_v [1], "-interval")) {
	    interval = atoi (get_arg ());
	    if (interval < 1)
		interval = 1;
	} else if (! strcmp (arg_v [1], "-bg")) {
	    bgcolor = get_arg ();
	} else if (! strcmp (arg_v [1], "-fg")) {
	    fgcolor = get_arg ();
	} else if (! strcmp (arg_v [1], "-hc")) {
	    hrcolor = get_arg ();
	} else if (! strcmp (arg_v [1], "-mc")) {
	    mncolor = get_arg ();
	} else if (! strcmp (arg_v [1], "-sc")) {
	    sqcolor = get_arg ();
	} else if (! strcmp (arg_v [1], "-ic")) {
	    incolor = get_arg ();
	} else
	    syntax ();
	arg_c--;
	arg_v++;
    }
}

char *get_arg ()
{
    if (arg_c == 2)
        errit ("Missing argument for '%s'", arg_v [1]);

    arg_v++;
    arg_c--;
    return arg_v [1];
}

void setup_clock ()
{
    XGCValues
        values;

    if (bitmap != (Pixmap) None) {
	XFreePixmap (display, bitmap);
	XFreeGC (display, bit_gc);
    }
    bitmap = XCreatePixmap (display, window, xsize, ysize, depth);
    if (bitmap == (Pixmap) None)
        errit ("Creating bitmap");
    bit_gc = XCreateGC (display, bitmap, 0, &values);
}

void ABC_SetColor (int color)
{
    XSetForeground (display, bit_gc, colors [color]);
}

void ABC_Rect (int x1, int y1, int x2, int y2)
{
    if (x1 <= x2 && y1 <= y2)
	XFillRectangle (
            display,
	    bitmap,
	    bit_gc,
	    x1,
	    ysize - y2 - 1,
	    x2 - x1 + 1,
	    y2 - y1 + 1
        );
}

void show_it ()
{
    time_t
	tp;
    struct tm
	*tm;

    time (&tp);
    tm = localtime (&tp);
    ABC_Make (tm->tm_hour, tm->tm_min, tm->tm_sec, xsize, ysize, border);
    XCopyArea (display, bitmap, window, gc, 0, 0, xsize, ysize, 0, 0);
} 

void ConnectToServer ()
{
    display = XOpenDisplay (NULL);
    if (display == (Display *) NULL) {
        fprintf (
            stderr,
            "\nCannot connect to X server [%s]\n\n",
            XDisplayName (NULL)
        );
        exit (1);
    }
    screen = DefaultScreen (display);
    rootwindow = RootWindow (display, screen);
    black = BlackPixel (display, screen);
    white = WhitePixel (display, screen);
    maxwidth = DisplayWidth (display, screen);
    maxheight = DisplayHeight (display, screen);
}

int SetUpVisual (Display *display, int screen, Visual **visual, int *depth)
{
    XVisualInfo
        *visual_array,
        visual_info_template;
    int
        number_visuals;

    if (DefaultVisual (display, screen)->class == PseudoColor
     || DefaultVisual (display, screen)->class == TrueColor) {
        *visual = DefaultVisual (display, screen);
        *depth = DefaultDepth (display, screen);
        return True;
    }

    visual_info_template.class  = PseudoColor;
    visual_info_template.screen = screen;
    visual_array = XGetVisualInfo (
        display,
        VisualClassMask | VisualScreenMask,
        &visual_info_template,
        &number_visuals
    );
    if ((number_visuals > 0) && (visual_array != NULL)) {
        *visual = visual_array [0].visual;
        *depth  = visual_array [0].depth;
        XFree (visual_array);
        return True;
    }
    visual_info_template.class  = TrueColor;
    visual_info_template.screen = screen;
    visual_array = XGetVisualInfo (
        display,
        VisualClassMask | VisualScreenMask,
        &visual_info_template,
        &number_visuals
    );
    if ((number_visuals > 0) && (visual_array != NULL)) {
        *visual = visual_array [0].visual;
        *depth  = visual_array [0].depth;
        XFree (visual_array);
        return True;
    }

    *visual = CopyFromParent;
    return False;
}

void OpenWindow ()
{
    int
	i,
	x,
	y,
	margin;

    XSetWindowAttributes
        attributes;

    margin = (border > 0) ? (2 * border) : 0;

    xsize = ysize = 80;
    x = (maxwidth - xsize) / 2;
    y = (maxheight - ysize) / 2;

    if (geometry) {
	i = XParseGeometry (geometry, &x, &y, &xsize, &ysize);
	if (i & XNegative)
	    x += maxwidth - xsize;
	if (i & YNegative)
	    y += maxheight - ysize;
    }

    if (xsize < ABC_MINSIZE + margin)
	xsize = ABC_MINSIZE + margin;
    if (xsize > maxwidth)
	xsize = maxwidth;
    if (ysize < ABC_MINSIZE + margin)
	ysize = ABC_MINSIZE + margin;
    if (ysize > maxheight)
	ysize = maxheight;

    if (x < 0)
	x = 0;
    if (x + xsize > maxwidth)
	x = maxwidth - xsize;
    if (y < 0)
	y = 0;
    if (y + ysize > maxheight)
	y = maxheight - ysize;

    attributes.event_mask = ExposureMask | StructureNotifyMask;
    attributes.border_pixel = black;
    attributes.background_pixel = white;
    window = XCreateWindow (
        display,           /* display */
        rootwindow,        /* parent */
        x, y,              /* location */
        xsize,             /* size */
	ysize,
        0,                 /* borderwidth */
        CopyFromParent,    /* depth */
        InputOutput,       /* class */
        CopyFromParent,    /* visual */
        CWEventMask | CWBackPixel | CWBorderPixel, /* valuemask */
        &attributes         /* attributes */
    );
}

#ifdef X11R4UP
void SetProtocols ()
{
    wm_delete_window = XInternAtom (display, "WM_DELETE_WINDOW", False);
    XSetWMProtocols (display, window, &wm_delete_window, 1);
}
#endif

void SetSizeHints ()
{
    int
	i;
    unsigned int
	margin,
	width_return,
	height_return;
    XSizeHints
        hints;

    margin = (border > 0) ? (2 * border) : 0;

    hints.flags = USSize | PMinSize | PMaxSize;

    if (geometry) {
	i = XParseGeometry (geometry, &hints.x, &hints.y, &width_return, &height_return);
	if ((i & XValue) && (i & YValue))
	    hints.flags |= USPosition;
    }

    hints.width = xsize;
    hints.height = ysize;
    hints.min_width = ABC_MINSIZE + margin;
    hints.min_height = ABC_MINSIZE + margin;
    hints.max_width = maxwidth;
    hints.max_height = maxheight;
#ifdef X11R4UP
    hints.base_width = xsize;
    hints.base_height = ysize;
    hints.flags |= PBaseSize;
    XSetWMNormalHints (display, window, &hints);
#else  /* up to X11R3  */
    XSetNormalHints (display, window, &hints);
#endif /* X11R4UP  */
}

void SetWMHints ()
{
    XWMHints
        hints;

    hints.flags = InputHint | StateHint;
    hints.initial_state = NormalState;
    hints.input = False;
    XSetWMHints (display, window, &hints);
}

void SetClassHints ()
{
    XClassHint
        hints;
    char
        *s;

    s = s_strdup (programname);
    s [0] = toupper (s [0]);
    hints.res_name = programname;
    hints.res_class = s;
    XSetClassHint (display, window, &hints);
    free (s);
}

void CreateGC ()
{
    XGCValues
        values;

    values.graphics_exposures = False;

    gc = XCreateGC (
        display,
        window,
        GCGraphicsExposures,
        &values
    );
}

unsigned long AllocColor (
    Display *display,
    Colormap colormap,
    char const *colorname,
    unsigned long default_color
)
{
    XColor
        hardwarecolor,
        exactcolor;

    if (! colorname)
	return default_color;

    if (XAllocNamedColor (display, colormap, colorname, &hardwarecolor, &exactcolor))
        return hardwarecolor.pixel;

    return default_color;
}

int SetUpColormap (
    Display *display,
    int screen,
    Window window,
    Visual *visual,
    Colormap *colormap
)
{
    if (visual == DefaultVisual (display, screen)) {
        *colormap = DefaultColormap (display, screen);
        return True;
    }

    if ((*colormap = XCreateColormap (display, window, visual, AllocNone)) != None) {
        XSetWindowColormap (display, window, *colormap);
        return True;
    }

    *colormap = DefaultColormap (display, screen);
    return False;
}

void start_graphics ()
{
    Visual
        *visual = CopyFromParent;
    Colormap
        colormap;
    int
        status;

    status = SetUpVisual (display, screen, &visual, &depth);
    OpenWindow ();

#ifdef X11R4UP
    SetProtocols ();
#endif
    SetSizeHints ();
    SetWMHints ();
    SetClassHints ();
    XStoreName (display, window, programname);
    XMapWindow (display, window);
    CreateGC ();

    if (status == True)
        status = SetUpColormap (
            display, screen, window, visual, &colormap);
    if (status == True) {
        fg_color = AllocColor (
            display, colormap, fgcolor, BlackPixel (display, screen));
        colors [0] = AllocColor (
            display, colormap, bgcolor, WhitePixel (display, screen));
	colors [3] =  AllocColor (display, colormap, hrcolor, fg_color);
	colors [4] =  AllocColor (display, colormap, mncolor, fg_color);
	colors [2] =  AllocColor (display, colormap, sqcolor, fg_color);
	colors [1] =  AllocColor (display, colormap, incolor, colors [0]);
    } else {
        fg_color = colors [3] = colors [4] = colors [2] = BlackPixel (display, screen);
        colors [0] = colors [1] = WhitePixel (display, screen);
    }
}

void *s_malloc (size_t size)
{
    void
	*p;

    p = malloc (size);
    if (! p) {
        free (no_mem_buffer);
	errit (out_of_memory);
    }
    return p;
}

char *s_strdup (char const *s)
{
    char
	*s1;

    if (s) {
	s1 = (char *) s_malloc (strlen (s) + 1);
	strcpy (s1, s);
    } else {
	s1 = (char *) s_malloc (1);
	s1 [0] = '\0';
    }
    return s1;
}

void get_programname (char const *argv0)
{
    char
        *p;
    p = strrchr (argv0, '/');
    if (p)
        programname = strdup (p + 1);
    else
        programname = strdup (argv0);
}

void errit (char const *format, ...)
{
    va_list
	list;

    fprintf (stderr, "\nError %s: ", programname);

    va_start (list, format);
    vfprintf (stderr, format, list);

    fprintf (stderr, "\n\n");

    exit (1);
}

void syntax ()
{
    fprintf (
	stderr,
	"\n"
	"Analogue Bitmap Clock -- version 1.0a\n"
        "(C) Peter Kleiweg 2000\n"
	"\n"
	"Usage: %s [-geometry <geo>] [-border <borderwidth>]\n"
	"\t[-interval <seconds>]\n"
	"\t[-bg <colour>] [-fg <colour>]\n"
	"\t[-hc <colour>] [-mc <colour>] [-sc <colour>] [-ic <colour>]\n"
	"\n",
	programname
    );
    exit (1);
}
