a small clock for X11 that displays time as pixels
1/* vim:ts=8
2 * $Id: pixelclock.c,v 1.8 2009/03/09 06:35:26 jcs Exp $
3 *
4 * pixelclock
5 * a different way of looking at time
6 *
7 * Copyright (c) 2005,2008-2009 joshua stein <jcs@jcs.org>
8 * Copyright (c) 2005 Federico G. Schwindt
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 *
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. The name of the author may not be used to endorse or promote products
20 * derived from this software without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34#include <err.h>
35#include <getopt.h>
36#include <signal.h>
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40#include <time.h>
41#include <unistd.h>
42#include <sys/types.h>
43
44#include <X11/Xlib.h>
45#include <X11/Xutil.h>
46
47/* default clock size */
48#define DEFSIZE 3
49
50/* default position is along the right side */
51#define DEFPOS 'r'
52
53/* so our window manager knows us */
54char* win_name = "pixelclock";
55
56/* default hours to highlight (9am, noon, 5pm) */
57const float defhours[3] = { 9.0, 12.0, 17.0 };
58
59struct xinfo {
60 Display* dpy;
61 int dpy_width, dpy_height;
62 int screen;
63 Window win;
64 int size;
65 char position;
66 GC gc;
67 Colormap win_colormap;
68} x;
69
70const struct option longopts[] = {
71 { "display", required_argument, NULL, 'd' },
72 { "size", required_argument, NULL, 's' },
73 { "left", no_argument, NULL, 'l' },
74 { "right", no_argument, NULL, 'r' },
75 { "top", no_argument, NULL, 't' },
76 { "bottom", no_argument, NULL, 'b' },
77
78 { NULL, 0, NULL, 0 }
79};
80
81extern char *__progname;
82
83long getcolor(const char *);
84void handler(int sig);
85void init_x(const char *);
86void usage(void);
87
88int
89main(int argc, char* argv[])
90{
91 char *display = NULL, *p;
92 int c, i, y;
93 int hourtick, lastpos = -1, newpos = 0;
94 struct timeval tv[2];
95 time_t now;
96 struct tm *t;
97
98 float *hihours;
99 int nhihours;
100
101 XEvent event;
102
103 bzero(&x, sizeof(struct xinfo));
104 x.size = DEFSIZE;
105 x.position = NULL;
106
107 while ((c = getopt_long_only(argc, argv, "", longopts, NULL)) != -1) {
108 switch (c) {
109 case 'd':
110 display = optarg;
111 break;
112
113 case 'b':
114 case 't':
115 case 'l':
116 case 'r':
117 if (x.position)
118 errx(1, "only one of -top, -bottom, -left, "
119 "-right allowed");
120 /* NOTREACHED */
121
122 x.position = c;
123 break;
124
125 case 's':
126 x.size = strtol(optarg, &p, 10);
127 if (*p || x.size < 1)
128 errx(1, "illegal value -- %s", optarg);
129 /* NOTREACHED */
130 break;
131
132 default:
133 usage();
134 /* NOTREACHED */
135 }
136 }
137
138 if (!x.position)
139 x.position = DEFPOS;
140
141 argc -= optind;
142 argv += optind;
143
144 if (argc == 0) {
145 /* use default times */
146 nhihours = sizeof(defhours) / sizeof(defhours[0]);
147 if ((hihours = alloca(sizeof(defhours))) == NULL)
148 err(1, NULL);
149
150 for (i = 0; i < nhihours; i++)
151 hihours[i] = defhours[i];
152 } else {
153 /* get times from args */
154 nhihours = argc;
155 if ((hihours = alloca(nhihours * sizeof(float))) == NULL)
156 err(1, NULL);
157
158 for (i = 0; i < argc; ++i) {
159 int h, m;
160 char *p = argv[i];
161
162 /* parse times like 14:12 */
163 h = atoi(p);
164 if ((p = strchr(p, ':')) == NULL)
165 errx(1, "invalid time %s", argv[i]);
166 m = atoi(p + 1);
167
168 if (h > 23 || h < 0 || m > 59 || m < 0)
169 errx(1, "Invalid time %s", argv[i]);
170
171 hihours[i] = h + (m / 60.0);
172 }
173 }
174
175 init_x(display);
176
177 signal(SIGINT, handler);
178 signal(SIGTERM, handler);
179
180 /* each hour will be this many pixels away */
181 hourtick = ((x.position == 'b' || x.position == 't') ? x.dpy_width :
182 x.dpy_height) / 24;
183
184 for (;;) {
185 if (gettimeofday(&tv[0], NULL))
186 errx(1, "gettimeofday");
187 /* NOTREACHED */
188
189 now = tv[0].tv_sec;
190 if ((t = localtime(&now)) == NULL)
191 errx(1, "localtime");
192 /* NOTREACHED */
193
194 newpos = (hourtick * t->tm_hour) +
195 (float)(((float)t->tm_min / 60.0) * hourtick) - 3;
196
197 /* check if we just got exposed */
198 bzero(&event, sizeof(XEvent));
199 XCheckWindowEvent(x.dpy, x.win, ExposureMask, &event);
200
201 /* only redraw if our time changed enough to move the box or if
202 * we were just exposed */
203 if ((newpos != lastpos) || (event.type == Expose)) {
204 XClearWindow(x.dpy, x.win);
205
206 /* draw the current time */
207 XSetForeground(x.dpy, x.gc, getcolor("yellow"));
208 if (x.position == 'b' || x.position == 't')
209 XFillRectangle(x.dpy, x.win, x.gc,
210 newpos, 0, 6, x.size);
211 else
212 XFillRectangle(x.dpy, x.win, x.gc,
213 0, newpos, x.size, 6);
214
215 /* draw the hour ticks */
216 XSetForeground(x.dpy, x.gc, getcolor("blue"));
217 for (y = 1; y <= 23; y++)
218 if (x.position == 'b' || x.position == 't')
219 XFillRectangle(x.dpy, x.win, x.gc,
220 (y * hourtick), 0, 2, x.size);
221 else
222 XFillRectangle(x.dpy, x.win, x.gc,
223 0, (y * hourtick), x.size, 2);
224
225 /* highlight requested times */
226 XSetForeground(x.dpy, x.gc, getcolor("green"));
227 for (i = 0; i < nhihours; i++)
228 if (x.position == 'b' || x.position == 't')
229 XFillRectangle(x.dpy, x.win, x.gc,
230 (hihours[i] * hourtick), 0,
231 2, x.size);
232 else
233 XFillRectangle(x.dpy, x.win, x.gc,
234 0, (hihours[i] * hourtick),
235 x.size, 2);
236
237 lastpos = newpos;
238
239 XFlush(x.dpy);
240 }
241
242 sleep(1);
243 }
244
245 exit(1);
246}
247
248void
249init_x(const char *display)
250{
251 int rc;
252 int left = 0, top = 0, width = 0, height = 0;
253 XGCValues values;
254 XSetWindowAttributes attributes;
255 XTextProperty win_name_prop;
256
257 if (!(x.dpy = XOpenDisplay(display)))
258 errx(1, "unable to open display %s", XDisplayName(display));
259 /* NOTREACHED */
260
261 x.screen = DefaultScreen(x.dpy);
262
263 x.dpy_width = DisplayWidth(x.dpy, x.screen);
264 x.dpy_height = DisplayHeight(x.dpy, x.screen);
265
266 x.win_colormap = DefaultColormap(x.dpy, DefaultScreen(x.dpy));
267
268 switch (x.position) {
269 case 'b':
270 left = 0;
271 height = x.size;
272 top = x.dpy_height - height;
273 width = x.dpy_width;
274 break;
275 case 't':
276 left = 0;
277 top = 0;
278 height = x.size;
279 width = x.dpy_width;
280 break;
281 case 'l':
282 left = 0;
283 top = 0;
284 height = x.dpy_height;
285 width = x.size;
286 break;
287 case 'r':
288 width = x.size;
289 left = x.dpy_width - width;
290 top = 0;
291 height = x.dpy_height;
292 break;
293 }
294
295 x.win = XCreateSimpleWindow(x.dpy, RootWindow(x.dpy, x.screen),
296 left, top, width, height,
297 0,
298 BlackPixel(x.dpy, x.screen),
299 BlackPixel(x.dpy, x.screen));
300
301 if (!(rc = XStringListToTextProperty(&win_name, 1, &win_name_prop)))
302 errx(1, "XStringListToTextProperty");
303 /* NOTREACHED */
304
305 XSetWMName(x.dpy, x.win, &win_name_prop);
306
307 /* remove all window manager decorations and force our position/size */
308 /* XXX: apparently this is not very nice */
309 attributes.override_redirect = True;
310 XChangeWindowAttributes(x.dpy, x.win, CWOverrideRedirect, &attributes);
311
312 if (!(x.gc = XCreateGC(x.dpy, x.win, 0, &values)))
313 errx(1, "XCreateGC");
314 /* NOTREACHED */
315
316 XMapWindow(x.dpy, x.win);
317
318 /* we want to know when we're exposed */
319 XSelectInput(x.dpy, x.win, ExposureMask);
320
321 XFlush(x.dpy);
322 XSync(x.dpy, False);
323}
324
325long
326getcolor(const char *color)
327{
328 int rc;
329
330 XColor tcolor;
331
332 if (!(rc = XAllocNamedColor(x.dpy, x.win_colormap, color, &tcolor,
333 &tcolor)))
334 errx(1, "can't allocate %s", color);
335
336 return tcolor.pixel;
337}
338
339void
340handler(int sig)
341{
342 XCloseDisplay(x.dpy);
343
344 exit(0);
345 /* NOTREACHED */
346}
347
348void
349usage(void)
350{
351 fprintf(stderr, "usage: %s %s\n", __progname,
352 "[-display host:dpy] [-left|-right|-top|-bottom] [-size <pixels>] "
353 "[time time2 ...]");
354 exit(1);
355}