dflyclock - Initial commit of the dflyclock project master
authorMatthew Dillon <dillon@apollo.backplane.com>
Fri, 4 Sep 2020 18:02:30 +0000 (11:02 -0700)
committerMatthew Dillon <dillon@apollo.backplane.com>
Fri, 4 Sep 2020 18:02:30 +0000 (11:02 -0700)
This project is the best X11 clock you will ever use.

dflyclock/Makefile [new file with mode: 0644]
dflyclock/dflyclock.1 [new file with mode: 0644]
dflyclock/dflyclock.c [new file with mode: 0644]

diff --git a/dflyclock/Makefile b/dflyclock/Makefile
new file mode 100644 (file)
index 0000000..a131179
--- /dev/null
@@ -0,0 +1,23 @@
+# Makefile for dflyclock
+#
+# Requires a number of regular X11 libraries and freetype2 to compile
+#
+
+PROG=  dflyclock
+MAN=   dflyclock.1
+NOMAN=TRUE
+
+
+LOCALDIR?=     /usr/local
+LIBXFT?= ${LOCALDIR}/lib/libXft.a
+LIBXEXT?= ${LOCALDIR}/lib/libXext.a
+LIBX11?= ${LOCALDIR}/lib/libX11.a
+DESTDIR?=      ${LOCALDIR}
+
+SRCS=  dflyclock.c
+DPADD= ${LIBXFT} ${LIBXEXT} ${LIBX11} ${LIBM}
+LDADD= -lXft -lXext -lX11 -lm
+CFLAGS+= -I${LOCALDIR}/include -I${LOCALDIR}/include/freetype2
+CFLAGS+= -L${LOCALDIR}/lib
+
+.include <bsd.prog.mk>
diff --git a/dflyclock/dflyclock.1 b/dflyclock/dflyclock.1
new file mode 100644 (file)
index 0000000..ee792fe
--- /dev/null
@@ -0,0 +1,164 @@
+.\"
+.\" Copyright (c) 2019-2020 The DragonFly Project.  All rights reserved.
+.\"
+.\" This code is derived from software contributed to The DragonFly Project
+.\" by Matthew Dillon <dillon@backplane.com>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\"
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in
+.\"    the documentation and/or other materials provided with the
+.\"    distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"/
+.Dd September 4, 2020
+.Dt DFLYCLOCK 1
+.Os
+.Sh NAME
+.Nm dflyclock
+.Nd The best X11-based clock
+.Sh SYNOPSIS
+.Nm
+.Op Fl rsmvh
+.Op Fl 12/24
+.Op Fl geometry Ar x11geom
+.Op Fl dom
+.Op Fl nodom
+.Op Fl strftime Ar format
+.Op Fl strftime2 Ar format
+.Op Fl font Ar name
+.Op Fl font2 Ar name
+.Op Fl S Ar scale
+.Op Fl shape
+.Op Fl noshape
+.Op Fl subsecond
+.Op Fl t Ar shape_type
+.Op Fl b Ar borderpix
+.Op Fl fg Ar ffffff
+.Op Fl bg Ar 000000
+.Op Fl t2 Ar e0e000
+.Sh DESCRIPTION
+The
+.Nm
+implements an analog or analog+digital clock on an X11 desktop.
+Written from scratch, it fixes the innumerable annoyances with most
+other X11 based clocks such as flicker, missing important date information
+such as the day of week, and so forth.  TrueType fonts are also supported.
+.Pp
+The application runs in the foreground.  If you wish to run it in the
+background, use
+.Xr notty 1 .
+.Pp
+Features include:
+.Bl -bullet -width 2
+.It
+Resizable, and the selected fonts will auto-scale.
+.It
+Double-buffered for flicker-free updates (except seconds hand).
+.It
+An XOR'd seconds hand to reduce cpu and graphics overhead to almost zero.
+.It
+Ability to use TrueType fonts.
+.It
+Automatic centering of font (no need to use a monospaced font).
+.It
+Two completely programmable text fields (strftime style).
+.It
+Shaped window support.
+.El
+.Sh OPTIONS
+.Nm
+has fairly nice defaults and implements a large number of options for
+customization.
+.Bl -tag -width Ds
+.It Fl 12/24
+Set 12-hour mode (default) or 24-hour mode.
+In 12-hour mode an 'a' or 'p' is tacked onto the default time format.
+.It Fl geometry Ar x11geom
+Specify an X11 geometry.  For example '100x100-1-1'
+.It Fl dom
+Include short text on main face (default).
+This typically uses a large font.
+.It Fl nodom
+Remove any text on the main face.
+.It Fl strftime Ar format
+Specify the bottom text using strftime formatting.
+.It Fl strftime2 Ar format
+Specify the face text using strftime formatting.
+.It Fl font Ar name
+Specify the bottom font.  The string may also include a single
+optional %f for point size, allowing
+.Nm
+to dynamically size the font.
+.It Fl font2 Ar name
+Specify the face font.  The string may also include a single
+optional %f for point size, allowing
+.Nm
+to dynamically size the font.
+.Nm
+.It Fl S Ar scale
+Scale the point size.
+This helps the user optimize any displayed text for the window.
+The default is 0.5.
+.It Fl shape
+Use a shaped circular/oval window (default).
+.It Fl noshape
+Use a normal square window.
+.It Fl subsecond
+Waste some CPU (though not much since the seconds hand is implemented
+via XOR instead of double-buffering).
+.It Fl r
+Reverse fg/bg colors.
+.It Fl s
+Include a second's hand (default).
+.It Fl m
+Do not include a second's hand.
+.It Fl v
+Print version an exit.
+.It Fl h
+Print help.
+.It Fl t Ar shape_type
+Specify the shape type.
+Currently only type '1' is supported.
+.It Fl b Ar borderpix
+Specify the border size in pixels.
+.It Fl fg Ar ffffff
+Specify forground color, 6 hex digits rrggbb.
+.It Fl bg Ar 000000
+Specify background color, 6 hex digits rrggbb.
+.It Fl t2 Ar e0e000
+Specify bottom time bar color, 6 hex digits rrggbb.
+.El
+.Sh INSTALLATION
+Generally speaking you should install the 'xorg' pkg from dports and
+then start this application up from your .xinitrc file.
+This application runs under X11.  For example:
+.Pp
+.Li /bin/notty /usr/local/bin/dflyclock -geometry 270x280-15+0
+.Sh SEE ALSO
+.Xr notty 1 ,
+.Xr pkg 8
+.Sh HISTORY
+The
+.Nm
+command was written by Matthew Dillon and first appeared in
+.Dx 5.9
+as an add-on project after the author became frustrated with all the
+other badly written X11 clock apps.
diff --git a/dflyclock/dflyclock.c b/dflyclock/dflyclock.c
new file mode 100644 (file)
index 0000000..3674eab
--- /dev/null
@@ -0,0 +1,1220 @@
+/*
+ * Copyright (c) 2019-2020 The DragonFly Project.  All rights reserved.
+ *
+ * This code is derived from software contributed to The DragonFly Project
+ * by Matthew Dillon <dillon@backplane.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+ * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+/*
+ * DragonFlyBSD X11 clock
+ *
+ * Synopsis from Matthew Dillon:
+ *
+ *     I got tired of X11 clocks that can't be resized, or don't scale,
+ *     don't include the day of week, day, and month, use all sorts of
+ *     libraries for things that nobody needs, use horrible fonts, look
+ *     cute but have no actual functionality, leave artifacts, or flicker
+ *     when updating.
+ *
+ *     This code is really quite straight-forward.  Eat, Sleep, Repeat.
+ *     There were two quirks.  First, trying to use XftDrawString8()
+ *     to erase text leaves an outline, even with alpha = 0xFFFF. That's
+ *     a bug in the function in my view.  Trying to use outlines to erase
+ *     the second, minute, or hour hand 'sorta' worked, but big windows
+ *     would leave artifacts.  This was attempted to avoid flickering but
+ *     wound up being a mess.  To deal with flickering the program uses
+ *     double-buffering as a solution.
+ *
+ *     The program uses a trick for the seconds-hand to avoid having to
+ *     double-buffer-copy every second.  However, if seconds is displayed
+ *     in the text, a double-buffer-copy will occur each second.
+ *
+ *     There are quite a few features, run dflyclock -h for a list.  The
+ *     features are smart.  Turning off the text below expands the clock
+ *     into the space.  True-type fonts are supported, you can control
+ *     the strftime string (including blanking it with "").  It centers
+ *     the text in a smart way so you don't have to use a monospace font,
+ *     the code is very smart about using a minimal amount of CPU and GPU.
+ *     And just for yuchs I added shaped window support too.  Etc.
+ *
+ *     DOUBLE BUFFER OPTIMIZATION - If the text does not contain a seconds
+ *     counter and the second-hand is turned on, we use XOR on the second-
+ *     hand directly to the window and avoid double-buffering it on a
+ *     1-second basis.  The double buffer will be used every 15 seconds in
+ *     order to be able to update the minute hand.
+ *
+ * BSD Copyright, replication is good
+ *
+ *     Many of the old xclock derivatives use the tried-and-true method of
+ *     starting with an older work and hacking it up... usually without
+ *     much improvement.
+ *
+ *     Please feel free to copy and modify the code.  Rename your clock and
+ *     add yourself to the contribution list if you like.  While not strictly
+ *     verboten to GNUize the source, please stick with the BSD copyright as
+ *     a nod to my BSD roots.
+ *
+ * cc -I/usr/local/include -I/usr/local/include/freetype2 dflyclock.c -L/usr/local/lib -o ~/bin/dflyclock -lXft -lXext -lX11 -lm
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/file.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <math.h>
+#include <X11/X.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xft/Xft.h>
+#include <X11/extensions/Xrender.h>
+#include <X11/extensions/shape.h>
+
+static void process_events(int *redrawp);
+static int process_time(int *redrawp);
+static void redraw_display(void);
+static void update_display(int draw_me);
+static void update_seconds(int draw_me);
+static void gcsetup(void);
+static int fontcheck(const char *str);
+static void usage(const char *prog, const char *badopt);
+static void drawhand(Drawable d, GC gc, int draw_me,
+               double a, double len, int thick);
+static void drawtick(GC gc, double a, double len, double thick);
+static void shaped_window_setup(void);
+static double angleof(int v1, int lim);
+
+#define DEFAULT_PIX            128
+#define DEFAULT_PIX_WTEXT      140
+
+XFontStruct *Font1;
+XFontStruct *Font2;
+Pixmap AreaPixmap;
+XftFont *XftFont1;
+XftFont *XftFont2;
+Display *Dpy;
+Window Win;
+XftDraw *XftWin;
+double Scale = 0.50;
+GC GCXor;
+GC GCFg;
+GC GCBg;
+GC GCText2;
+XftColor XftGCFg;
+XftColor XftGCBg;
+XftColor XftGCText2;
+int GCAllocated;
+int WindowWidth;
+int WindowHeight;
+int CW;                                /* clock face drawing area */
+int CH;                                /* clock face drawing area (before adj) */
+
+int ShapeTextBoxX;
+int ShapeTextBoxY;
+int ShapeTextBoxW;
+int ShapeTextBoxH;
+
+int FW1;                       /* font width in pixels */
+int FHA1;                      /* font height ascent in pixels */
+int FHD1;                      /* font height descent in pixels */
+
+int FW2;                       /* t2font width in pixels */
+int FHA2;                      /* t2font height ascent in pixels */
+int FHD2;                      /* t2font height descent in pixels */
+
+int BH;                                /* border height */
+int BW;                                /* border width */
+int CW2;                       /* 1/2 clock race drawing area */
+int CH2;                       /* 1/2 clock race drawing area */
+int AddBorder = 2;
+int Military;
+int EnableText = 3;
+int IncludeSeconds = 1;
+int TextHasSeconds;
+int ShapedWindow = 1;
+int ShapeType = 1;
+int WasteCPU = 0;
+const char *Time1NoTime2FmtMil = "%a %d-%b %l:%M";
+const char *Time1NoTime2FmtNrm = "%a %d-%b %l:%M";
+const char *Time1FmtMil = "%b %H:%M";
+const char *Time1FmtNrm = "%b %l:%M";
+const char *Time1Fmt = "%b %l:%M";
+const char *Time2Fmt = "%a %d";
+long Interval = 1000000;       /* microseconds */
+struct timeval LastTVSubsecs;
+struct timeval NextTVSubsecs;
+struct tm LastTM;
+struct tm LastTMSecs;
+struct tm NextTM;
+ulong BGColor;
+ulong FGColor;
+ulong T2Color;
+const char *FontName1 = "Droid Sans-%f";
+const char *FontName2 = "Droid Sans-%f";
+const char *BackupFontName1 = "-*-*-bold-r-*-*-%f-*-*-*-*-*-*-*";
+const char *BackupFontName2 = "-*-*-bold-r-*-*-%f-*-*-*-*-*-*-*";
+
+int
+main(int ac, char **av)
+{
+       Window root_win;
+       XSetWindowAttributes wattr;
+       int i;
+       int j;
+       int reverse = 0;
+       int didfont2 = 0;
+       int didtime1fmt = 0;
+       const char *bgcolor = NULL;
+       const char *fgcolor = NULL;
+       const char *t2color = NULL;
+       char *wname = strdup("dflyclock");
+       char *wclass = strdup(" DFlyClock");
+       char *geometry = NULL;
+       XSizeHints hints;
+       XTextProperty name;
+       XClassHint classHint;
+       bzero(&hints, sizeof(hints));
+
+       hints.width = DEFAULT_PIX;
+       hints.height = DEFAULT_PIX_WTEXT;
+
+       for (i = 1; i < ac; ++i) {
+               if (av[i][0] != '-')
+                       usage(av[0], av[i]);
+               if (strcmp(av[i], "-geometry") == 0) {
+                       if (i + 1 >= ac)
+                               usage(av[0], "(missing argument)");
+                       geometry = av[++i];
+               } else if (strcmp(av[i], "-font") == 0) {
+                       if (i + 1 >= ac)
+                               usage(av[0], "(missing argument)");
+                       FontName1 = av[++i];
+                       if (fontcheck(FontName1) < 0)
+                               usage(av[0], "(illegal font specification)");
+                       if (didfont2 == 0)
+                               FontName2 = FontName1;
+               } else if (strcmp(av[i], "-font2") == 0) {
+                       if (i + 1 >= ac)
+                               usage(av[0], "(missing argument)");
+                       FontName2 = av[++i];
+                       if (fontcheck(FontName2) < 0)
+                               usage(av[0], "(illegal t2font specification)");
+                       didfont2 = 1;
+               } else if (strcmp(av[i], "-S") == 0) {
+                       if (i + 1 >= ac)
+                               usage(av[0], "(missing argument)");
+                       Scale = strtod(av[++i], NULL);
+                       if (Scale < 0.01)
+                               Scale = 0.01;
+                       if (Scale > 1000.0)
+                               Scale = 1000.0;
+               } else if (strcmp(av[i], "-12") == 0) {
+                       Military = 0;
+                       Time1Fmt = Time1FmtNrm;
+               } else if (strcmp(av[i], "-24") == 0) {
+                       Military = 1;
+                       Time1Fmt = Time1FmtMil;
+               } else if (strcmp(av[i], "-shape") == 0) {
+                       ShapedWindow = 2;
+               } else if (strcmp(av[i], "-noshape") == 0) {
+                       ShapedWindow = 0;
+               } else if (strcmp(av[i], "-subsecond") == 0) {
+                       WasteCPU = 1;
+                       IncludeSeconds = 1;
+               } else if (strcmp(av[i], "-t") == 0) {
+                       ShapeType = strtol(av[++i], NULL, 0);
+                       if (ShapeType)
+                               ShapedWindow = 2;
+                       else
+                               ShapedWindow = 0;
+               } else if (strcmp(av[i], "-b") == 0) {
+                       if (i + 1 >= ac)
+                               usage(av[0], "(missing argument)");
+                       AddBorder = strtol(av[++i], NULL, 0);
+               } else if (strcmp(av[i], "-r") == 0) {
+                       reverse = 1;
+               } else if (strcmp(av[i], "-s") == 0) {
+                       IncludeSeconds = 1;
+               } else if (strcmp(av[i], "-m") == 0) {
+                       IncludeSeconds = 0;
+               } else if (strcmp(av[i], "-bg") == 0) {
+                       if (i + 1 >= ac)
+                               usage(av[0], "(missing argument)");
+                       bgcolor = av[++i];
+               } else if (strcmp(av[i], "-fg") == 0) {
+                       if (i + 1 >= ac)
+                               usage(av[0], "(missing argument)");
+                       fgcolor = av[++i];
+               } else if (strcmp(av[i], "-t2") == 0) {
+                       if (i + 1 >= ac)
+                               usage(av[0], "(missing argument)");
+                       t2color = av[++i];
+               } else if (strcmp(av[i], "-strftime") == 0 ||
+                          strcmp(av[i], "-strftime1") == 0) {
+                       if (i + 1 >= ac)
+                               usage(av[0], "(missing argument)");
+                       Military = 1;   /* don't tack on 'a' or 'p' */
+                       Time1Fmt = av[++i];
+                       if (Time1Fmt[0]) {
+                               EnableText |= 1;
+                               hints.height = DEFAULT_PIX_WTEXT;
+                       } else {
+                               EnableText &= ~1;
+                               hints.height = DEFAULT_PIX;
+                       }
+                       didtime1fmt = 1;
+               } else if (strcmp(av[i], "-strftime2") == 0) {
+                       if (i + 1 >= ac)
+                               usage(av[0], "(missing argument)");
+                       Time2Fmt = av[++i];
+                       if (Time2Fmt[0]) {
+                               EnableText |= 2;
+                       } else {
+                               EnableText &= ~2;
+                               if (didtime1fmt == 0) {
+                                       Time1FmtMil = Time1NoTime2FmtMil;
+                                       Time1FmtNrm = Time1NoTime2FmtNrm;
+                                       Time1Fmt = Military ?
+                                                   Time1FmtMil : Time1FmtNrm;
+                               }
+                       }
+               } else if (strcmp(av[i], "-dom") == 0) {
+                       hints.height = DEFAULT_PIX;
+                       Time2Fmt = "%a %d";
+                       EnableText |= 2;
+               } else if (strcmp(av[i], "-nodom") == 0) {
+                       hints.height = DEFAULT_PIX_WTEXT;
+                       EnableText &= ~2;
+                       Time2Fmt = "";
+                       if (didtime1fmt == 0) {
+                               Time1FmtMil = Time1NoTime2FmtMil;
+                               Time1FmtNrm = Time1NoTime2FmtNrm;
+                               Time1Fmt = Military ?
+                                           Time1FmtMil : Time1FmtNrm;
+                       }
+               } else if (strcmp(av[i], "-h") == 0) {
+                       usage(av[0], NULL);
+               } else if (strcmp(av[i], "-v") == 0) {
+                       printf("%s: version 1.0.6\n", av[0]);
+                       exit(0);
+               } else {
+                       usage(av[0], av[i]);
+               }
+       }
+
+       /*
+        * If TextHasSeconds is non-zero we are going to have to do
+        * a double-buffer area copy every second.  If not we can get
+        * away with XORing the second-hand.
+        */
+       if (strstr(Time1Fmt, "%s") || strstr(Time1Fmt, "%S") ||
+           strstr(Time1Fmt, "%T")) {
+               TextHasSeconds |= 1;
+       }
+       if (strstr(Time2Fmt, "%s") || strstr(Time2Fmt, "%S") ||
+           strstr(Time2Fmt, "%T")) {
+               TextHasSeconds |= 2;
+       }
+
+       Dpy = XOpenDisplay(NULL);
+
+       if (bgcolor)
+               BGColor = strtoul(bgcolor, NULL, 16);
+       else
+               BGColor = WhitePixel(Dpy, DefaultScreen(Dpy));
+
+       if (fgcolor)
+               FGColor = strtoul(fgcolor, NULL, 16);
+       else
+               FGColor = BlackPixel(Dpy, DefaultScreen(Dpy));
+
+       if (t2color)
+               T2Color = strtoul(t2color, NULL, 16);
+       else
+               T2Color = 0x0000FF;
+
+       if (reverse) {
+               ulong tmp = FGColor;
+               FGColor = BGColor;
+               BGColor = tmp;
+       }
+       if (geometry) {
+               int gravity = 0;
+
+               hints.flags |= USSize | USPosition;
+               XWMGeometry(Dpy, DefaultScreen(Dpy), geometry, NULL,
+                           1, &hints,
+                           &hints.x, &hints.y,
+                           &hints.width, &hints.height, &gravity);
+       }
+
+       WindowWidth = hints.width;
+       WindowHeight = hints.height;
+
+       root_win = DefaultRootWindow(Dpy);
+       Win = XCreateSimpleWindow(Dpy, root_win,
+                                 hints.x, hints.y, hints.width, hints.height,
+                                 1,            /* border width */
+                                 BGColor,      /* border */
+                                 BGColor);     /* background */
+       wattr.event_mask = ExposureMask|StructureNotifyMask;
+       XChangeWindowAttributes(Dpy, Win, CWEventMask, &wattr);
+       if (geometry)
+               XSetWMNormalHints(Dpy, Win, &hints);
+       classHint.res_name = wname;
+       classHint.res_class = wclass;
+       XSetClassHint(Dpy, Win, &classHint);
+
+       /*
+        * Shaped window setup.  Only complain on failure if option was
+        * explicitly given.
+        */
+       if (ShapedWindow) {
+               int event_base = 0;
+               int error_base = 0;
+               if (!XShapeQueryExtension(Dpy, &event_base, &error_base)) {
+                       if (ShapedWindow == 2) {
+                               fprintf(stderr,
+                                       "Warning: shape extension not avail\n");
+                       }
+                       ShapedWindow = 0;
+               }
+       }
+
+       XMapWindow(Dpy, Win);
+
+       XStringListToTextProperty(&wname, 1, &name);
+       XSetWMName(Dpy, Win, &name);
+       XSync(Dpy, 1);
+
+       tzset();
+       gcsetup();
+
+       for (;;) {
+               int redraw;
+               int us;
+
+               redraw = 0;
+               process_events(&redraw);
+               us = process_time(&redraw);
+
+               if (redraw & 2) {               /* full update */
+                       redraw_display();
+               } else if (redraw & 1) {        /* nominal update */
+                       update_display(0);
+                       LastTM = NextTM;
+                       LastTMSecs = NextTM;
+                       LastTVSubsecs = NextTVSubsecs;
+                       update_display(1);
+               } else if (redraw & 4) {        /* seconds only update */
+                       update_seconds(0);
+                       LastTMSecs = NextTM;
+                       LastTVSubsecs = NextTVSubsecs;
+                       update_seconds(1);
+               } else if (redraw & 8) {        /* CPU waster */
+                       update_seconds(0);
+                       LastTVSubsecs = NextTVSubsecs;
+                       update_seconds(1);
+               }
+               XFlush(Dpy);
+               usleep(us);
+       }
+       return 0;
+}
+
+static void
+process_events(int *redrawp)
+{
+       while (XEventsQueued(Dpy, QueuedAfterReading)) {
+               XEvent xev;
+
+               XNextEvent(Dpy, &xev);
+
+               switch(xev.type) {
+               case ConfigureNotify:
+                       WindowWidth = xev.xconfigure.width;
+                       WindowHeight = xev.xconfigure.height;
+                       gcsetup();
+                       /* fall through */
+               case Expose:
+               case VisibilityNotify:
+               case GraphicsExpose:
+               case MappingNotify:
+                       *redrawp |= 2;
+                       break;
+               }
+       }
+}
+
+static int
+process_time(int *redrawp)
+{
+       static int FirstTime = 1;
+       struct timeval tv;
+       int target_us = 1000000;
+
+       gettimeofday(&tv, NULL);
+       localtime_r(&tv.tv_sec, &NextTM);
+       NextTVSubsecs = tv;
+
+       /*
+        * Nominal update (1) or full redraw (2) (mask).  These will
+        * cause a double-buffered copy.  Mode 4 does not.
+        *
+        * every 1 min  - no second-hand, no seconds in text.
+        * every 15 sec - second-hand, no seconds in text.
+        * every 1 sec  - seconds displayed in text.
+        */
+       if (FirstTime) {
+               FirstTime = 0;
+               LastTM = NextTM;
+               LastTMSecs = NextTM;
+               LastTVSubsecs = NextTVSubsecs;
+               *redrawp |= 2;
+       } else if (IncludeSeconds) {
+               if (LastTM.tm_sec != NextTM.tm_sec &&
+                   TextHasSeconds) {
+                       *redrawp |= 1;
+               } else if (WasteCPU && LastTM.tm_sec != NextTM.tm_sec) {
+                       /*
+                        * Be really silly
+                        */
+                       *redrawp |= 1;
+               } else if (LastTM.tm_sec != NextTM.tm_sec &&
+                          NextTM.tm_sec % 15 == 0) {
+                       *redrawp |= 1;
+               } else if (LastTM.tm_sec != NextTM.tm_sec) {
+                       *redrawp |= 4;
+               } else if (WasteCPU) {
+                       /*
+                        * Be really silly
+                        */
+                       *redrawp |= 8;
+               }
+               if (WasteCPU) {
+                       target_us = tv.tv_usec + (100000 - tv.tv_usec % 100000);
+               }
+       } else {
+               if (LastTM.tm_sec != NextTM.tm_sec &&
+                   TextHasSeconds) {
+                       *redrawp |= 1;
+               } else if (LastTM.tm_min != NextTM.tm_min) {
+                       *redrawp |= 1;
+               }
+       }
+
+       /*
+        * Full redraw once a minute to catch any artifacting that
+        * might build up (it isn't supposed to but...)
+        */
+       if (LastTM.tm_sec == 0 && NextTM.tm_sec != 0)
+               *redrawp |= 2;
+
+       return target_us - tv.tv_usec + 1000;
+}
+
+static void
+redraw_display(void)
+{
+       XFillRectangle(Dpy, AreaPixmap, GCBg, 0, 0, WindowWidth, WindowHeight);
+       /*XClearWindow(Dpy, Win);*/
+       LastTM = NextTM;
+       LastTMSecs = NextTM;
+       LastTVSubsecs = NextTVSubsecs;
+       update_display(2);
+}
+
+static void
+update_display(int draw_me)
+{
+       double min_ang;
+       double hour_ang;
+       GC gc1 = (draw_me) ? GCFg : GCBg;
+       GC gc2 = (draw_me) ? GCText2 : GCBg;
+       XftColor *xftgc1 = (draw_me) ? &XftGCFg : &XftGCBg;
+       XftColor *xftgc2 = (draw_me) ? &XftGCText2 : &XftGCBg;
+       size_t len;
+       char buf[256];
+       char nbuf[256];
+       int workaround = 0;
+       int i;
+
+       /*
+        * The seconds hand uses an XOR operation in order to erase and
+        * update itself in the Window directly (see the direct call to
+        * update_seconds in main()).  In order to remain compatible with
+        * this direct call, the clearing operation must be called first
+        * and the setting operation must be called last to properly XOR
+        * the hand.
+        */
+       if (draw_me == 0)
+               update_seconds(draw_me);
+       min_ang = angleof(LastTM.tm_min * 60 + LastTM.tm_sec, 3600);
+       hour_ang = angleof(LastTM.tm_hour * 60 + LastTM.tm_min, 720);
+       drawhand(AreaPixmap, gc1, draw_me, min_ang, 0.8, 1);
+       drawhand(AreaPixmap, gc1, draw_me, hour_ang, 0.5, 1);
+
+       /*
+        * Static elements of the clock which do not get
+        * messed up by the second, minute, or hour-hand.
+        */
+       if (draw_me == 2) {
+               for (i = 0; i < 12; ++i) {
+                       drawtick(gc1, angleof(i, 12), 0.10, 0.02);
+               }
+               for (i = 0; i < 60; ++i) {
+                       drawtick(gc1, angleof(i, 60), 0.05, 0.01);
+               }
+       }
+
+       /*
+        * Static elements of the clock which do get messed up
+        * by hand or text movement.
+        */
+       if (draw_me) {
+               int w;
+               int h;
+
+               w = CW * 5 / 100;
+               h = CH * 5 / 100;
+
+               XFillArc(Dpy, AreaPixmap, gc1,
+                              BW + CW2 - w/2, BH + CH2 - h/2, w, h,
+                              0, 360 * 64);
+       }
+
+       /*
+        * Text underneath
+        */
+       if (EnableText & 1) {
+               int w;
+               int h;
+               int x;
+               int y;
+
+               if (Military) {
+                       strftime(nbuf, sizeof(nbuf), Time1Fmt, &NextTM);
+                       strftime(buf, sizeof(buf), Time1Fmt, &LastTM);
+                       len = strlen(buf);
+               } else {
+                       strftime(nbuf, sizeof(nbuf), Time1Fmt, &NextTM);
+                       len = strlen(nbuf);
+                       len += snprintf(nbuf + len, sizeof(buf) - len,
+                                       "%s",
+                                       ((NextTM.tm_hour >= 12) ? "p" : "a"));
+
+                       strftime(buf, sizeof(buf), Time1Fmt, &LastTM);
+                       len = strlen(buf);
+                       len += snprintf(buf + len, sizeof(buf) - len,
+                                       "%s",
+                                       ((LastTM.tm_hour >= 12) ? "p" : "a"));
+               }
+               if (XftFont1) {
+                       XGlyphInfo ext;
+                       XftTextExtents8(Dpy, XftFont1, buf, len, &ext);
+                       w = ext.xOff;
+                       h = ext.height;
+               } else if (Font1) {
+                       w = XTextWidth(Font1, buf, len);
+               } else {
+                       w = FW1 * len;
+               }
+
+               x = BW + CW2 - w / 2;
+               y = CH + FHA1 + BH + BH / 2;
+
+               if (ShapedWindow && (EnableText & 1) &&
+                   (ShapeTextBoxX != x - (w/20) ||
+                    ShapeTextBoxY != CH + 1 ||
+                    ShapeTextBoxW != w + 2*(w/20) ||
+                    ShapeTextBoxH != FHA1 + FHD1)) {
+                       ShapeTextBoxX = x - (w/20);
+                       ShapeTextBoxY = CH + 1;
+                       ShapeTextBoxW = w + 2*(w/20);
+                       ShapeTextBoxH = FHA1 + FHD1;
+                       shaped_window_setup();
+               }
+
+               if (XftFont1) {
+                       /*
+                        * Xft fonts have an alpha channel for blending edges
+                        * which we can't turn off, which means that we cannot
+                        * erase text via XftDrawString8() (it will leave a
+                        * light outline).  Reset the background instead.
+                        */
+                       if (draw_me == 0) {
+                               XFillRectangle(Dpy, AreaPixmap, GCBg,
+                                              x, y - h, w, h + FHD1);
+                       } else {
+                               XftDrawString8(XftWin, xftgc1, XftFont1,
+                                              x, y, buf, len);
+                       }
+               } else {
+                       XDrawString(Dpy, AreaPixmap, gc1, x, y, buf, len);
+                       workaround = 1;
+               }
+       }
+
+       /*
+        * In-face time display.  Displays in the lower half of the
+        * face when the hour hand is in the upper half, and vise-versa.
+        */
+       if ((EnableText & 2) && Time2Fmt[0]) {
+               int w;
+               int h;
+               int x;
+               int y;
+               double ang;
+
+               strftime(nbuf, sizeof(nbuf), Time2Fmt, &NextTM);
+               strftime(buf, sizeof(buf), Time2Fmt, &LastTM);
+               len = strlen(buf);
+
+               if (XftFont2) {
+                       XGlyphInfo ext;
+                       XftTextExtents8(Dpy, XftFont2, buf, len, &ext);
+                       w = ext.xOff;
+                       h = ext.height;
+               } else if (Font2) {
+                       w = XTextWidth(Font2, buf, len);
+                       h = Font2->ascent;
+               } else {
+                       w = FW2 * len;
+               }
+               if (LastTM.tm_hour % 12 >= 9 ||
+                   LastTM.tm_hour % 12 < 3) {
+                       ang = M_PI / 2.0;
+                       x = BW + CW2;
+                       y = BH + CH2 + h;
+               } else {
+                       ang = -M_PI / 2.0;
+                       x = BW + CW2;
+                       y = BH + CH2;
+               }
+
+               x += cos(ang) * (CW2 / 3) - w / 2;
+               y += sin(ang) * (CH2 / 3);
+
+               /*
+                * Because Text2 is in the face, it gets corrupted by the
+                * hands and we have to redraw it every time.
+                */
+               if (XftFont2) {
+                       /*
+                        * Xft fonts have an alpha channel for blending edges
+                        * which we can't turn off, which means that we cannot
+                        * erase text via XftDrawString8() (it will leave a
+                        * light outline).  Reset the background instead.
+                        */
+                       if (draw_me == 0) {
+                               XFillRectangle(Dpy, AreaPixmap, GCBg,
+                                              x, y - h, w, h + FHD2);
+                       } else {
+                               XftDrawString8(XftWin, xftgc2, XftFont2,
+                                              x, y, buf, len);
+                       }
+               } else {
+                       XDrawString(Dpy, AreaPixmap, gc2, x, y, buf, len);
+                       workaround = 1;
+               }
+       }
+
+       if (draw_me) {
+               /*
+                * The XSync() works around certain broken gpu drivers when
+                * a XDrawString() is followed by a GXxor XDrawLine().
+                */
+               if (workaround)
+                       XSync(Dpy, 0);
+               update_seconds(draw_me);
+       }
+
+       if (draw_me) {
+               XCopyArea(Dpy, AreaPixmap, Win, GCFg,
+                         0, 0, WindowWidth, WindowHeight,
+                         0, 0);
+       }
+}
+
+/*
+ * This function is called as part of a general update and also called
+ * as part of a special update that avoids the double-buffer copy.
+ */
+static void
+update_seconds(int draw_me)
+{
+       double sec_ang;
+
+       if (IncludeSeconds) {
+               if (WasteCPU)
+                       sec_ang = angleof(LastTMSecs.tm_sec * 10 +
+                                         LastTVSubsecs.tv_usec / 100000, 600);
+               else
+                       sec_ang = angleof(LastTMSecs.tm_sec, 60);
+               drawhand(AreaPixmap, GCXor, draw_me, sec_ang, 0.895, 0);
+               drawhand(Win, GCXor, draw_me, sec_ang, 0.895, 0);
+       }
+}
+
+/*
+ * X Drawing support
+ */
+static void
+gcsetup(void)
+{
+       XGCValues gcinfo;
+       XRenderColor xrc;
+       Colormap cmap;
+       char fontbuf[256];
+       char fmtbuf[256];
+       double tw;
+
+       cmap = XDefaultColormap(Dpy, DefaultScreen(Dpy));
+
+       /*
+        * Make sure this stuff is deallocated in the correct order,
+        * reverse of dependencies.
+        */
+       if (XftWin)
+               XftDrawDestroy(XftWin);
+
+       if (GCAllocated) {
+               XFreeGC(Dpy, GCFg);
+               XFreeGC(Dpy, GCBg);
+               XFreeGC(Dpy, GCXor);
+               XFreeGC(Dpy, GCText2);
+
+               XftColorFree(Dpy, DefaultVisual(Dpy, DefaultScreen(Dpy)),
+                            cmap, &XftGCFg);
+               XftColorFree(Dpy, DefaultVisual(Dpy, DefaultScreen(Dpy)),
+                            cmap, &XftGCBg);
+               XftColorFree(Dpy, DefaultVisual(Dpy, DefaultScreen(Dpy)),
+                            cmap, &XftGCText2);
+       }
+
+       if (AreaPixmap)
+               XFreePixmap(Dpy, AreaPixmap);
+
+       if (Font1) {
+               XUnloadFont(Dpy, Font1->fid);
+               Font1 = NULL;
+       }
+
+       if (Font2) {
+               XUnloadFont(Dpy, Font2->fid);
+               Font2 = NULL;
+       }
+
+       if (XftFont1) {
+               XftFontClose(Dpy, XftFont1);
+               XftFont1 = NULL;
+       }
+
+       if (XftFont2) {
+               XftFontClose(Dpy, XftFont2);
+               XftFont2 = NULL;
+       }
+
+       CW = WindowWidth;
+       CH = WindowHeight;
+
+       AreaPixmap = XCreatePixmap(Dpy, Win, CW, CH,
+                                  DefaultDepth(Dpy, DefaultScreen(Dpy)));
+       XftWin = XftDrawCreate(Dpy, AreaPixmap, 
+                       DefaultVisual(Dpy, DefaultScreen(Dpy)),
+                       XDefaultColormap(Dpy, DefaultScreen(Dpy)));
+
+       /*
+        * 12 characters in text string.  Use smaller dimension
+        */
+       if (CW <= CH) {
+               tw = (CW - AddBorder * 2) / 12.0;
+       } else {
+               tw = (CH - AddBorder * 2) / 12.0;
+       }
+
+retry_font1:
+       snprintf(fmtbuf, sizeof(fmtbuf), "%s", FontName1);
+       if (strstr(fmtbuf, "%f"))
+               strstr(fmtbuf, "%f")[1] = 'd';
+       snprintf(fontbuf, sizeof(fontbuf), fmtbuf, (int)(tw + 0.5));
+       Font1 = XLoadQueryFont(Dpy, fontbuf);
+       if (Font1 == NULL) {
+               snprintf(fontbuf, sizeof(fontbuf),
+                        FontName1, tw * Scale);
+               XftFont1 = XftFontOpenName(Dpy, DefaultScreen(Dpy), fontbuf);
+               if (XftFont1 == NULL && BackupFontName1) {
+                       FontName1 = BackupFontName1;
+                       BackupFontName1 = NULL;
+                       goto retry_font1;
+               }
+       }
+
+       if (XftFont1) {
+               FW1 = XftFont1->max_advance_width;
+               FHA1 = XftFont1->ascent;
+               FHD1 = XftFont1->descent;
+       } else if (Font1) {
+               FW1 = Font1->max_bounds.width;
+               FHA1 = Font1->max_bounds.ascent;
+               FHD1 = Font1->max_bounds.descent + 1;
+       } else {
+               static int not_found_reported;
+
+               FW1 = (int)tw;
+               FHA1 = (int)tw;
+               FHD1 = 0;
+               if (not_found_reported == 0) {
+                       not_found_reported = 1;
+                       fprintf(stderr,
+                               "Warning: font '%s' not found\n", fontbuf);
+               }
+       }
+
+       BH = CH / 50 + AddBorder;
+       BH = AddBorder;
+       BW = BH;
+       if (EnableText == 0) {
+               FW1 = 0;
+               FHA1 = 0;
+               FHD1 = 0;
+       }
+
+       CH -= FHA1 + FHD1;
+       CH -= BH * 2;
+       CW -= BW * 2;
+       CW2 = CW / 2;
+       CH2 = CH / 2;
+
+retry_font2:
+       snprintf(fmtbuf, sizeof(fmtbuf), "%s", FontName2);
+       if (strstr(fmtbuf, "%f"))
+               strstr(fmtbuf, "%f")[1] = 'd';
+       snprintf(fontbuf, sizeof(fontbuf), fmtbuf, (int)(tw * 2.0 + 0.5));
+       Font2 = XLoadQueryFont(Dpy, fontbuf);
+
+       if (Font2 == NULL) {
+               snprintf(fontbuf, sizeof(fontbuf),
+                        FontName2, tw * Scale * 2.0);
+               XftFont2 = XftFontOpenName(Dpy, DefaultScreen(Dpy), fontbuf);
+               if (XftFont2 == NULL && BackupFontName2) {
+                       FontName2 = BackupFontName2;
+                       BackupFontName2 = NULL;
+                       goto retry_font2;
+               }
+       }
+       if (XftFont2) {
+               FW2 = XftFont2->max_advance_width;
+               FHA2 = XftFont2->ascent;
+               FHD2 = XftFont2->descent;
+       } else if (Font2) {
+               FW2 = Font2->max_bounds.width;
+               FHA2 = Font2->max_bounds.ascent;
+               FHD2 = Font2->max_bounds.descent;
+       } else {
+               static int not_found_reported2;
+
+               FW2 = tw;
+               FHA2 = tw;
+               FHD2 = 0;
+               if (not_found_reported2 == 0) {
+                       not_found_reported2 = 1;
+                       fprintf(stderr,
+                               "Warning: t2font '%s' not found\n", fontbuf);
+               }
+       }
+
+       gcinfo.plane_mask = AllPlanes;
+       gcinfo.fill_style = FillSolid;
+       if (Font1)
+               gcinfo.font = Font1->fid;
+
+       gcinfo.function = GXxor;
+       gcinfo.foreground = WhitePixel(Dpy, DefaultScreen(Dpy));
+       GCXor = XCreateGC(Dpy, Win,
+                           GCFunction|GCPlaneMask|GCForeground|
+                           GCBackground|GCFillStyle, &gcinfo);
+
+       gcinfo.function = GXcopy;
+       gcinfo.foreground = FGColor;
+       gcinfo.background = BGColor;
+       GCFg = XCreateGC(Dpy, AreaPixmap,
+                           GCFunction|GCPlaneMask|GCForeground|
+                           GCBackground|GCFillStyle|
+                           (Font1 ? GCFont : 0),
+                           &gcinfo);
+
+       gcinfo.foreground = BGColor;
+       gcinfo.background = BGColor;
+       GCBg = XCreateGC(Dpy, AreaPixmap,
+                           GCFunction|GCPlaneMask|GCForeground|
+                           GCBackground|GCFillStyle|
+                           (Font1 ? GCFont : 0),
+                           &gcinfo);
+
+       gcinfo.font = 0;
+       gcinfo.foreground = T2Color;
+       gcinfo.background = BGColor;
+       if (Font2)
+               gcinfo.font = Font2->fid;
+       GCText2 = XCreateGC(Dpy, AreaPixmap,
+                           GCFunction|GCPlaneMask|GCForeground|
+                           GCBackground|GCFillStyle|
+                           (Font2 ? GCFont : 0),
+                           &gcinfo);
+
+       bzero(&xrc, sizeof(xrc));
+
+       xrc.red = ((FGColor >> 16) & 0xFF) << 8;
+       xrc.green = ((FGColor >> 8) & 0xFF) << 8;
+       xrc.blue = (FGColor & 0xFF) << 8;
+       xrc.alpha = 0xFFFF;
+       XftColorAllocValue(Dpy, DefaultVisual(Dpy, DefaultScreen(Dpy)),
+                          cmap, &xrc, &XftGCFg);
+
+       xrc.red = ((BGColor >> 16) & 0xFF) << 8;
+       xrc.green = ((BGColor >> 8) & 0xFF) << 8;
+       xrc.blue = (BGColor & 0xFF) << 8;
+       XftColorAllocValue(Dpy, DefaultVisual(Dpy, DefaultScreen(Dpy)),
+                          cmap, &xrc, &XftGCBg);
+
+       xrc.red = ((T2Color >> 16) & 0xFF) << 8;
+       xrc.green = ((T2Color >> 8) & 0xFF) << 8;
+       xrc.blue = (T2Color & 0xFF) << 8;
+       XftColorAllocValue(Dpy, DefaultVisual(Dpy, DefaultScreen(Dpy)),
+                          cmap, &xrc, &XftGCText2);
+       GCAllocated = 1;
+
+       /*
+        * Update our shape params when we know the text dimensions.
+        */
+       ShapeTextBoxX = 0;
+       ShapeTextBoxY = 0;
+       ShapeTextBoxW = 0;
+       ShapeTextBoxH = 0;
+       if (ShapedWindow && (EnableText & 1) == 0)
+               shaped_window_setup();
+}
+
+static void
+drawhand(Drawable d, GC gc, int draw_me, double a, double len, int thick)
+{
+       XPoint points[5];
+       double dta = M_PI / 2.0;        /* 90 degrees in rad */
+       double mid;
+       double dthick = thick * CW2 / 20.0;
+
+       if (dthick < 1.0)
+               dthick = 1.0;
+
+       a = -a + M_PI / 2.0;    /* direction of movement + top = 0 deg */
+
+       dta = M_PI / 2;
+       mid = len / 10.0;
+
+       points[0].x = BW+CW2;
+       points[0].y = BH+CH2;
+       points[1].x = BW+CW2 + cos(a) * (CW2 * mid) + cos(a - dta) * dthick;
+       points[1].y = BH+CH2 - sin(a) * (CH2 * mid) - sin(a - dta) * dthick;
+       points[2].x = BW+CW2 + cos(a) * (CW2 * len);
+       points[2].y = BH+CH2 - sin(a) * (CH2 * len);
+       points[3].x = BW+CW2 + cos(a) * (CW2 * mid) + cos(a + dta) * dthick;
+       points[3].y = BH+CH2 - sin(a) * (CH2 * mid) - sin(a + dta) * dthick;
+       points[4].x = BW+CW2;   /* back to origin for XDrawLines() */
+       points[4].y = BH+CH2;
+
+       if (thick) {
+               XFillPolygon(Dpy, d, gc, points, 4,
+                            Convex, CoordModeOrigin);
+       } else {
+               XDrawLine(Dpy, d, gc, points[0].x, points[0].y,
+                                       points[2].x, points[2].y);
+       }
+}
+
+static void
+drawtick(GC gc, double a, double len, double thick)
+{
+       XPoint points[4];
+       double dta = M_PI / 2.0;        /* 90 degrees in rad */
+       double beg;
+       double dthick = thick * CW2;
+
+       if (dthick < 1.0)
+               dthick = 1.0;
+
+       a = -a + M_PI / 2.0;    /* direction of movement + top = 0 deg */
+
+       dta = M_PI / 2;
+       beg = 1.0;
+       len = beg - len;
+
+       points[0].x = BW+CW2 + cos(a) * (CW2 * beg) + cos(a - dta) * dthick;
+       points[0].y = BH+CH2 - sin(a) * (CH2 * beg) - sin(a - dta) * dthick;
+       points[1].x = BW+CW2 + cos(a) * (CW2 * beg) + cos(a + dta) * dthick;
+       points[1].y = BH+CH2 - sin(a) * (CH2 * beg) - sin(a + dta) * dthick;
+       points[2].x = BW+CW2 + cos(a) * (CW2 * len) + cos(a + dta) * dthick;
+       points[2].y = BH+CH2 - sin(a) * (CH2 * len) - sin(a + dta) * dthick;
+       points[3].x = BW+CW2 + cos(a) * (CW2 * len) + cos(a - dta) * dthick;
+       points[3].y = BH+CH2 - sin(a) * (CH2 * len) - sin(a - dta) * dthick;
+
+       XFillPolygon(Dpy, AreaPixmap, gc, points, 4, Convex, CoordModeOrigin);
+}
+
+/*
+ * A font specification may have at most one '%f' in it, and
+ * no other '%' specifications.  Return -1 on failure.
+ */
+static int
+fontcheck(const char *str)
+{
+       int found = 0;
+
+       while (*str) {
+               if (*str == '%') {
+                       if (found || str[1] != 'f')
+                               return -1;
+                       found = 1;
+               }
+               ++str;
+       }
+}
+
+static void
+usage(const char *prg, const char *badopt)
+{
+       FILE *fp;
+
+       if (badopt) {
+               fprintf(stderr, "%s: malformed option: %s\n", prg, badopt);
+               fp = stderr;
+       } else {
+               fp = stdout;
+       }
+       fprintf(fp, "%s",
+               "Available options:\n"
+               "    -geometry <geom>.\n"
+               "    -12       \t\t- (default) 12-hour mode, tack on a/p\n"
+               "    -24       \t\t- 24-hour mode\n"
+               "    -dom      \t\t- (default) Include short text on face.\n"
+               "    -nodom    \t\t- Remove text in face.\n"
+               "    -strftime <fmt>\t- Replace bottom format string.\n"
+               "    -strftime2 <fmt>\t- Replace face format string.\n"
+               "    -font <id>\t\t- specify font, the string may contain\n"
+               "              \t\t  a single (optional) %f for point size.\n"
+               "              \t\t  (also changes -font2's default).\n"
+               "              \t\t  (example: \"Droid Sans-%f\"\n"
+               "    -font2 <id>\t\t- similarly, specify font for dom/time2.\n"
+               "    -S scale  \t\t- Scale point size.\n"
+               "    -shape    \t\t- (default) Use shaped window.\n"
+               "    -noshape  \t\t- Use normal square window.\n"
+               "    -subsecond\t\t- Waste some CPU.\n"
+               "    -r        \t\t- reverse fg/bg.\n"
+               "    -s        \t\t- (default) include a second's hand.\n"
+               "    -m        \t\t- do not include a second's hand.\n"
+               "    -v        \t\t- print version and exit.\n"
+               "    -h        \t\t- print this help text and exit.\n"
+               "    -t <shape>\t\t- Shape type (only 1 right now).\n"
+               "    -b <border>\t\t- Specify border size in pixels.\n"
+               "    -fg ffffff\t\t- choose color, 6 hex digits.\n"
+               "    -bg 000000\t\t- choose color, 6 hex digits.\n"
+               "    -t2 e0e000\t\t- choose color, 6 hex digits.\n"
+               "\n"
+               "    NOTE: If the text contains seconds (%s, %S, or %T)\n"
+               "          then the clock window double-buffer updates\n"
+               "          on a 1-second basis.  With a second-hand it only\n"
+               "          double-buffer updates once every 15 seconds and\n"
+               "          with neither only every 60 seconds.\n"
+       );
+       exit(1);
+}
+
+/*
+ * Shaped window setup or refresh
+ */
+static void
+shaped_window_setup(void)
+{
+       static GC ShapeGC;
+       static Pixmap ShapeMask;
+       static int ShapeWidth;
+       static int ShapeHeight;
+       XGCValues xgcv;
+       int ww = WindowWidth;
+       int wh = WindowHeight;
+
+       if (ShapeMask && ShapeWidth == ww && ShapeHeight == wh)
+               return;
+       if (ShapeGC)
+               XFreeGC(Dpy, ShapeGC);
+       if (ShapeMask) {
+               XFreePixmap(Dpy, ShapeMask);
+               ShapeMask = 0;
+       }
+       ShapeWidth = ww;
+       ShapeHeight = wh;
+       ShapeMask = XCreatePixmap(Dpy, Win, ww, wh, 1);
+       ShapeGC = XCreateGC(Dpy, ShapeMask, 0, &xgcv);
+       XSetForeground(Dpy, ShapeGC, 0);
+       XFillRectangle(Dpy, ShapeMask, ShapeGC, 0, 0, ww, wh);
+       XSetForeground(Dpy, ShapeGC, 1);
+
+       switch(ShapeType) {
+       case 1:
+       default:
+               XFillArc(Dpy, ShapeMask, ShapeGC,
+                        0, 0, CW + BW, CH + BH,
+                        0, 360 * 64);
+               if (EnableText & 1) {
+                       XFillRectangle(Dpy, ShapeMask, ShapeGC,
+                                      ShapeTextBoxX - 1,
+                                      ShapeTextBoxY,
+                                      ShapeTextBoxW + 2,
+                                      ShapeTextBoxH);
+               }
+               break;
+       }
+
+       XShapeCombineMask(Dpy, Win, ShapeBounding, 0, 0, ShapeMask, ShapeSet);
+       XShapeCombineMask(Dpy, Win, ShapeClip, 0, 0, ShapeMask, ShapeSet);
+}
+
+/*
+ * Geometry support
+ */
+static double
+angleof(int v1, int lim)
+{
+       double a;
+
+       a = (double)v1 / (double)lim * (M_PI * 2);
+
+       return a;
+}