1 /* wmclock.c: a dockable clock applet for Window Maker
2  * created 1999-Apr-09 jmk
3  *
4  * by Jim Knoble <jmknoble@pobox.com>
5  * Copyright (C) 1999 Jim Knoble
6  *
7  * Significant portions of this software are derived from asclock by
8  * Beat Christen <spiff@longstreet.ch>.  Such portions are copyright
9  * by Beat Christen and the other authors of asclock.
10  *
11  * Disclaimer:
12  *
13  * The software is provided "as is", without warranty of any kind,
14  * express or implied, including but not limited to the warranties of
15  * merchantability, fitness for a particular purpose and
16  * noninfringement. In no event shall the author(s) be liable for any
17  * claim, damages or other liability, whether in an action of
18  * contract, tort or otherwise, arising from, out of or in connection
19  * with the software or the use or other dealings in the software.
20  */
21 
22 #include <sys/select.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <sys/time.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30 #include <unistd.h>
31 #include <X11/Xatom.h>
32 #include <X11/Xlib.h>
33 #include <X11/xpm.h>
34 #include <X11/extensions/shape.h>
35 
36 #include "dynlist.h"
37 
38 /**********************************************************************/
39 #define ONLY_SHAPED_WINDOW	1
40 
41 #define NUM_TIME_POSITIONS	5
42 #define NUM_X_POSITIONS		11
43 #define NUM_Y_POSITIONS		4
44 
45 #define DIGIT_1_X_POS		0
46 #define DIGIT_2_X_POS		1
47 #define DIGIT_3_X_POS		3
48 #define DIGIT_4_X_POS		4
49 #define DIGIT_Y_POS		0
50 #define LED_NUM_Y_OFFSET	0
51 #define LED_THIN_1_X_OFFSET	13
52 #define LED_NUM_WIDTH		9
53 #define LED_NUM_HEIGHT		11
54 #define LED_THIN_1_WIDTH	5
55 
56 #define COLON_X_POS		2
57 #define COLON_Y_POS		DIGIT_Y_POS
58 #define COLON_X_OFFSET		90
59 #define COLON_Y_OFFSET		0
60 #define BLANK_X_OFFSET		119
61 #define BLANK_Y_OFFSET		COLON_Y_OFFSET
62 #define COLON_WIDTH		3
63 #define COLON_HEIGHT		11
64 
65 #define AMPM_X_POS		5
66 #define AM_X_OFFSET		94
67 #define AM_Y_OFFSET		5
68 #define PM_X_OFFSET		107
69 #define PM_Y_OFFSET		5
70 #define AM_WIDTH		12
71 #define AM_HEIGHT		6
72 #define PM_WIDTH		11
73 #define PM_HEIGHT		6
74 
75 #define MONTH_X_POS		10
76 #define MONTH_Y_POS		3
77 #define MONTH_X_OFFSET		0
78 #define MONTH_WIDTH		22
79 #define MONTH_HEIGHT		6
80 
81 #define DATE_LEFT_X_POS		7
82 #define DATE_CENTER_X_POS	8
83 #define DATE_RIGHT_X_POS	9
84 #define DATE_Y_POS		2
85 #define DATE_Y_OFFSET		0
86 #define DATE_NUM_WIDTH		9
87 #define DATE_NUM_HEIGHT		13
88 
89 #define WEEKDAY_X_POS		6
90 #define WEEKDAY_Y_POS		1
91 #define WEEKDAY_X_OFFSET	0
92 #define WEEKDAY_WIDTH		21
93 #define WEEKDAY_HEIGHT		6
94 
95 #define OUR_WINDOW_EVENTS	(ExposureMask | ButtonPressMask | StructureNotifyMask)
96 
97 #define LED_XPM_BRIGHT_LINE_INDEX	3
98 #define LED_XPM_BRIGHT_CHAR		'+'
99 #define LED_XPM_DIM_LINE_INDEX		4
100 #define LED_XPM_DIM_CHAR		'@'
101 
102 #define DEFAULT_XPM_CLOSENESS	40000
103 
104 #define DIM_NUMERATOR	5
105 #define DIM_DENOMINATOR	10
106 #define makeDimColor(c)	(((c) * DIM_NUMERATOR) / DIM_DENOMINATOR)
107 
108 /**********************************************************************/
109 #ifndef ONLY_SHAPED_WINDOW
110 # include "clk.xpm"
111 #endif /* !ONLY_SHAPED_WINDOW */
112 #include MONTH_XPM
113 #include WEEKDAY_XPM
114 #include "xpm/date.xpm"
115 #include "xpm/led.xpm"
116 #include "xpm/mask.xbm"
117 #include "xpm/mask.xpm"
118 
119 typedef struct _XpmIcon {
120     Pixmap        pixmap;
121     Pixmap        mask;
122     XpmAttributes attributes;
123 } XpmIcon;
124 
125 void showUsage(void);
126 void showVersion(void);
127 int buildCommand(char *, char **, int *, int *);
128 void executeCommand(char *);
129 void showError(const char *, const char*);
130 void showFatalError(const char *, const char*);
131 void GetXpms(void);
132 int flushExposeEvents(Window);
133 void redrawWindow(XpmIcon *);
134 Pixel GetColor(const char *);
135 int mytime(void);
136 void showYear(void);
137 void showTime12(void);
138 void showTime24(void);
139 void showTime(void);
140 char* extractProgName(char *);
141 int processArgs(int, char **);
142 
143 /**********************************************************************/
144 int enable12HourClock = 0;	/* default value is 24h format */
145 int enableShapedWindow = 1;	/* default value is noshape */
146 int enableBlinking = 1;		/* default is blinking */
147 int startIconified = 0;		/* default is not iconified */
148 int enableYearDisplay = 0;	/* default is to show time, not year */
149 unsigned int blinkInterval = 2;          /* default is a 2-second blink cycle */
150 
151 int timePos12[NUM_TIME_POSITIONS]  = { 5, 14, 24, 28, 37 };
152 int timePos24[NUM_TIME_POSITIONS]  = { 4,  8, 17, 22, 31 };
153 /* with shape */
154 int xPosShaped[NUM_X_POSITIONS] = { 0, 0, 0, 0, 0, 40, 17, 17, 22, 27, 15 };
155 int yPosShaped[NUM_Y_POSITIONS] = { 3, 21, 30, 45 };
156 
157 #ifndef ONLY_SHAPED_WINDOW
158 /* no shape */
159 int xPosUnshaped[NUM_X_POSITIONS] = { 5, 5, 5, 5, 5, 45, 21, 21, 26, 31, 19 };
160 int yPosUnshaped[NUM_Y_POSITIONS] = { 7, 25, 34, 49 };
161 #endif /* !ONLY_SHAPED_WINDOW */
162 
163 int xPos[NUM_X_POSITIONS];
164 int yPos[NUM_Y_POSITIONS];
165 
166 Display    *dpy;
167 Window     rootWindow;
168 int        screen;
169 int        xFd;
170 fd_set     xFdSet;
171 int        displayDepth;
172 XSizeHints sizeHints;
173 XWMHints   wmHints;
174 Pixel      bgPixel, fgPixel;
175 GC         normalGC;
176 Window     iconWin, win;
177 
178 char *progName;
179 char *className = "WMClock";
180 char *geometry = "";
181 char *ledColor = "LightSeaGreen";
182 
183 char *commandToExec = NULL;
184 char *commandBuf = NULL;
185 int   commandLength = 0;
186 int   commandIndex = 0;
187 
188 char *errColorCells = "not enough free color cells or xpm not found\n";
189 
190 char *userClockXpm;
191 char *userMonthXpm;
192 char *userWeekdayXpm;
193 int   useUserClockXpm = 0;
194 int   useUserMonthXpm = 0;
195 int   useUserWeekdayXpm = 0;
196 
197 XpmIcon clockBg, led, months, dateNums, weekdays;
198 XpmIcon visible;
199 
200 time_t actualTime;
201 long   actualMinutes;
202 
203 static struct tm *localTime;
204 
205 char *usageText[] = {
206 "Options:",
207 "    -12                     show 12-hour time (am/pm)",
208 "    -24                     show 24-hour time",
209 "    -year                   show year instead of time",
210 "    -noblink                don't blink",
211 "    -interval <seconds>     set blink interval",
212 "    -exe <command>          start <command> on mouse click",
213 "    -led <color>            use <color> as color of led",
214 #ifndef ONLY_SHAPED_WINDOW
215 "    -clockxpm <filename>    get clock background from pixmap in <filename>",
216 #endif /* !ONLY_SHAPED_WINDOW */
217 "    -monthxpm <filename>    get month names from pixmap in <filename>",
218 "    -weekdayxpm <filename>  get weekday names from pixmap in <filename>",
219 "    -version                display the version",
220 NULL
221 };
222 
223 char *version = VERSION;
224 
225 /**********************************************************************/
226 /* Display usage information */
showUsage(void)227 void showUsage(void)
228 {
229    char **cpp;
230 
231    fprintf(stderr, "Usage:  %s [option [option ...]]\n\n", progName);
232    for (cpp = usageText; *cpp; cpp++)
233     {
234        fprintf(stderr, "%s\n", *cpp);
235     }
236    fprintf(stderr,"\n");
237    exit(1);
238 }
239 
240 /* Display the program version */
showVersion()241 void showVersion()
242 {
243    fprintf(stderr, "%s version %s\n", progName, version);
244    exit(1);
245 }
246 
247 /* Build the shell command to execute */
buildCommand(char * command,char ** buf,int * buf_len,int * i)248 int buildCommand(char *command, char **buf, int *buf_len, int *i)
249 {
250    int status;
251 
252    status = append_string_to_buf(buf, buf_len, i, command);
253    if (APPEND_FAILURE == status)
254     {
255        return (0);
256     }
257    status = append_string_to_buf(buf, buf_len, i, " &");
258    return ((APPEND_FAILURE == status) ? 0 : 1);
259 }
260 
261 /* Execute the given shell command */
executeCommand(char * command)262 void executeCommand(char *command)
263 {
264    int status;
265 
266    if (NULL == command)
267     {
268        return;
269     }
270    status = system(command);
271 
272    if (-1 == status)
273     {
274        perror("system");
275     }
276 }
277 
278 /* Display an error message */
showError(const char * message,const char * data)279 void showError(const char *message, const char *data)
280 {
281    fprintf(stderr,"%s: can't %s %s\n", progName, message, data);
282 }
283 
284 /* Display an error message and exit */
showFatalError(const char * message,const char * data)285 void showFatalError(const char *message, const char *data)
286 {
287    showError(message, data);
288    exit(1);
289 }
290 
291 /* Konvertiere XPMIcons nach Pixmaps */
GetXpms(void)292 void GetXpms(void)
293 {
294    static char     **clock_xpm;
295    XColor            color;
296    XWindowAttributes attributes;
297    char              ledBright[64];
298    char              ledDim[64];
299    int               status;
300 
301 #ifdef ONLY_SHAPED_WINDOW
302    clock_xpm = mask_xpm;
303 #else /* !ONLY_SHAPED_WINDOW */
304    clock_xpm = enableShapedWindow ? mask_xpm : clk_xpm;
305 #endif /* ONLY_SHAPED_WINDOW */
306 
307    /* for the colormap */
308    XGetWindowAttributes(dpy, rootWindow, &attributes);
309 
310    /* get user-defined color */
311    if (!XParseColor(dpy, attributes.colormap, ledColor, &color))
312     {
313        showError("parse color", ledColor);
314     }
315 
316    sprintf(ledBright, "%c      c #%04X%04X%04X", LED_XPM_BRIGHT_CHAR,
317 	   color.red, color.green, color.blue);
318    led_xpm[LED_XPM_BRIGHT_LINE_INDEX] = &ledBright[0];
319 
320    color.red   = makeDimColor(color.red);
321    color.green = makeDimColor(color.green);
322    color.blue  = makeDimColor(color.blue);
323    sprintf(&ledDim[0], "%c      c #%04X%04X%04X", LED_XPM_DIM_CHAR,
324 	   color.red, color.green, color.blue);
325    led_xpm[LED_XPM_DIM_LINE_INDEX] = &ledDim[0];
326 
327    clockBg.attributes.closeness = DEFAULT_XPM_CLOSENESS;
328    clockBg.attributes.valuemask |=
329       (XpmReturnPixels | XpmReturnExtensions | XpmCloseness);
330 
331    if (useUserClockXpm)
332     {
333        status = XpmReadFileToPixmap(dpy, rootWindow, userClockXpm,
334 				    &clockBg.pixmap, &clockBg.mask,
335 				    &clockBg.attributes);
336     }
337    else
338     {
339        status = XpmCreatePixmapFromData(dpy, rootWindow, clock_xpm,
340 					&clockBg.pixmap, &clockBg.mask,
341 					&clockBg.attributes);
342     }
343    if (XpmSuccess != status)
344     {
345        showFatalError("create clock pixmap:", errColorCells);
346     }
347 
348 #ifdef ONLY_SHAPED_WINDOW
349    visible.attributes.depth  = displayDepth;
350    visible.attributes.width  = clockBg.attributes.width;
351    visible.attributes.height = clockBg.attributes.height;
352    visible.pixmap = XCreatePixmap(dpy, rootWindow, visible.attributes.width,
353 				  visible.attributes.height,
354 				  visible.attributes.depth);
355 #else /* !ONLY_SHAPED_WINDOW */
356    visible.attributes.closeness = DEFAULT_XPM_CLOSENESS;
357    visible.attributes.valuemask |=
358       (XpmReturnPixels | XpmReturnExtensions | XpmCloseness);
359    status = XpmCreatePixmapFromData(dpy, rootWindow, clk_xpm,
360 				    &visible.pixmap, &visible.mask,
361 				    &visible.attributes);
362 #endif /* ONLY_SHAPED_WINDOW */
363 
364    led.attributes.closeness = DEFAULT_XPM_CLOSENESS;
365    led.attributes.valuemask |=
366       (XpmReturnPixels | XpmReturnExtensions | XpmCloseness);
367    status = XpmCreatePixmapFromData(dpy, rootWindow, led_xpm,
368 				    &led.pixmap, &led.mask,
369 				    &led.attributes);
370    if (XpmSuccess != status)
371     {
372        showFatalError("create led pixmap:", errColorCells);
373     }
374 
375    months.attributes.closeness = DEFAULT_XPM_CLOSENESS;
376    months.attributes.valuemask |=
377       (XpmReturnPixels | XpmReturnExtensions | XpmCloseness);
378    if (useUserMonthXpm)
379     {
380        status = XpmReadFileToPixmap(dpy, rootWindow, userMonthXpm,
381 				    &months.pixmap, &months.mask,
382 				    &months.attributes);
383     }
384    else
385     {
386        status = XpmCreatePixmapFromData(dpy, rootWindow, month_xpm,
387 					&months.pixmap, &months.mask,
388 					&months.attributes);
389     }
390    if (XpmSuccess != status)
391     {
392        showFatalError("create month pixmap:", errColorCells);
393     }
394 
395    dateNums.attributes.closeness = DEFAULT_XPM_CLOSENESS;
396    dateNums.attributes.valuemask |=
397       (XpmReturnPixels | XpmReturnExtensions | XpmCloseness);
398    status = XpmCreatePixmapFromData(dpy, rootWindow, date_xpm,
399 				    &dateNums.pixmap, &dateNums.mask,
400 				    &dateNums.attributes);
401    if (XpmSuccess != status)
402     {
403        showFatalError("create date pixmap:", errColorCells);
404     }
405 
406    weekdays.attributes.closeness = DEFAULT_XPM_CLOSENESS;
407    weekdays.attributes.valuemask |=
408       (XpmReturnPixels | XpmReturnExtensions | XpmCloseness);
409    if (useUserWeekdayXpm)
410     {
411        status = XpmReadFileToPixmap(dpy, rootWindow, userWeekdayXpm,
412 				    &weekdays.pixmap, &weekdays.mask,
413 				    &weekdays.attributes);
414     }
415    else
416     {
417        status = XpmCreatePixmapFromData(dpy, rootWindow, weekday_xpm,
418 					&weekdays.pixmap, &weekdays.mask,
419 					&weekdays.attributes);
420     }
421    if (XpmSuccess != status)
422     {
423        showFatalError("create weekday pixmap:", errColorCells);
424     }
425 }
426 
427 /* Remove expose events for a specific window from the queue */
flushExposeEvents(Window w)428 int flushExposeEvents(Window w)
429 {
430    XEvent dummy;
431    int    i = 0;
432 
433    while (XCheckTypedWindowEvent(dpy, w, Expose, &dummy))
434     {
435        i++;
436     }
437    return(i);
438 }
439 
440 /* (Re-)Draw the main window and the icon window */
redrawWindow(XpmIcon * v)441 void redrawWindow(XpmIcon *v)
442 {
443    flushExposeEvents(iconWin);
444    XCopyArea(dpy, v->pixmap, iconWin, normalGC,
445 	     0, 0, v->attributes.width, v->attributes.height, 0, 0);
446    flushExposeEvents(win);
447    XCopyArea(dpy, v->pixmap, win, normalGC,
448 	     0, 0, v->attributes.width, v->attributes.height, 0, 0);
449 }
450 
451 /* Get a Pixel for the given color name */
GetColor(const char * colorName)452 Pixel GetColor(const char *colorName)
453 {
454    XColor            color;
455    XWindowAttributes attributes;
456 
457    XGetWindowAttributes(dpy, rootWindow, &attributes);
458    color.pixel = 0;
459    if (!XParseColor(dpy, attributes.colormap, colorName, &color))
460     {
461        showError("parse color", colorName);
462     }
463    else if (!XAllocColor(dpy, attributes.colormap, &color))
464     {
465        showError("allocate color", colorName);
466     }
467    return(color.pixel);
468 }
469 
470 /* Fetch the system time and time zone */
mytime(void)471 int mytime(void)
472 {
473    struct timeval  tv;
474    struct timezone tz;
475 
476    gettimeofday(&tv, &tz);
477 
478    return(tv.tv_sec);
479 }
480 
481 /* Display the current year in the LED display */
showYear(void)482 void showYear(void)
483 {
484    int year;
485    int digitXOffset;
486    int digitYOffset;
487 
488    year = localTime->tm_year + 1900;
489 
490    digitYOffset = LED_NUM_Y_OFFSET;
491    digitXOffset = LED_NUM_WIDTH * (year / 1000);
492    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
493 	     digitXOffset , digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
494 	     xPos[DIGIT_1_X_POS], yPos[DIGIT_Y_POS]);
495    digitXOffset = LED_NUM_WIDTH * ((year / 100) % 10);
496    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
497 	     digitXOffset , digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
498 	     xPos[DIGIT_2_X_POS], yPos[DIGIT_Y_POS]);
499    digitXOffset = LED_NUM_WIDTH * ((year / 10) % 10);
500    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
501 	     digitXOffset , digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
502 	     xPos[DIGIT_3_X_POS], yPos[DIGIT_Y_POS]);
503    digitXOffset = LED_NUM_WIDTH * (year % 10);
504    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
505 	     digitXOffset , digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
506 	     xPos[DIGIT_4_X_POS], yPos[DIGIT_Y_POS]);
507 }
508 
509 /* Display time in twelve-hour mode, with am/pm indicator */
showTime12(void)510 void showTime12(void)
511 {
512    int digitXOffset;
513    int digitYOffset;
514    int localHour = localTime->tm_hour % 12;
515 
516    if (0 == localHour)
517     {
518        localHour = 12;
519     }
520    if (localTime->tm_hour < 12)
521     {
522        /* AM */
523        XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
524 		 AM_X_OFFSET, AM_Y_OFFSET, AM_WIDTH, AM_HEIGHT,
525 		 xPos[AMPM_X_POS], yPos[DIGIT_Y_POS] + AM_Y_OFFSET);
526     }
527    else
528     {
529        /* PM */
530        XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
531 		 PM_X_OFFSET, PM_Y_OFFSET, PM_WIDTH, PM_HEIGHT,
532 		 xPos[AMPM_X_POS], yPos[DIGIT_Y_POS] + PM_Y_OFFSET);
533     }
534 
535    digitYOffset = LED_NUM_Y_OFFSET;
536    if (localHour > 9)
537     {
538        digitXOffset = LED_THIN_1_X_OFFSET;
539        XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
540 		 digitXOffset, digitYOffset, LED_THIN_1_WIDTH, LED_NUM_HEIGHT,
541 		 xPos[DIGIT_1_X_POS], yPos[DIGIT_Y_POS]);
542     }
543    digitXOffset = LED_NUM_WIDTH * (localHour % 10);
544    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
545 	     digitXOffset, digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
546 	     xPos[DIGIT_2_X_POS], yPos[DIGIT_Y_POS]);
547    digitXOffset = LED_NUM_WIDTH * (localTime->tm_min / 10);
548    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
549 	     digitXOffset, digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
550 	     xPos[DIGIT_3_X_POS], yPos[DIGIT_Y_POS]);
551    digitXOffset = LED_NUM_WIDTH * (localTime->tm_min % 10);
552    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
553 	     digitXOffset, digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
554 	     xPos[DIGIT_4_X_POS], yPos[DIGIT_Y_POS]);
555 }
556 
557 /* Display time in 24-hour mode, without am/pm indicator */
showTime24(void)558 void showTime24(void)
559 {
560    int digitXOffset;
561    int digitYOffset;
562 
563    digitYOffset = LED_NUM_Y_OFFSET;
564    digitXOffset = LED_NUM_WIDTH * (localTime->tm_hour / 10);
565    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
566 	     digitXOffset, digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
567 	     xPos[DIGIT_1_X_POS], yPos[DIGIT_Y_POS]);
568    digitXOffset = LED_NUM_WIDTH * (localTime->tm_hour % 10);
569    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
570 	     digitXOffset, digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
571 	     xPos[DIGIT_2_X_POS], yPos[DIGIT_Y_POS]);
572    digitXOffset = LED_NUM_WIDTH * (localTime->tm_min / 10);
573    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
574 	     digitXOffset, digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
575 	     xPos[DIGIT_3_X_POS], yPos[DIGIT_Y_POS]);
576    digitXOffset = LED_NUM_WIDTH * (localTime->tm_min % 10);
577    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
578 	     digitXOffset, digitYOffset, LED_NUM_WIDTH, LED_NUM_HEIGHT,
579 	     xPos[DIGIT_4_X_POS], yPos[DIGIT_Y_POS]);
580 }
581 
showTime(void)582 void showTime(void)
583 {
584    int xOffset;
585    int yOffset;
586 
587    /* Zeit auslesen */
588    actualTime = mytime();
589    actualMinutes = actualTime / 60;
590 
591    localTime = localtime(&actualTime);
592 
593    /* leere clock holen */
594    XCopyArea(dpy, clockBg.pixmap, visible.pixmap, normalGC,
595 	     0, 0, sizeHints.width, sizeHints.height, 0, 0);
596 
597    if (enableYearDisplay)
598     {
599        showYear();
600     }
601    else if (enable12HourClock)
602     {
603        showTime12();
604     }
605    else
606     {
607        showTime24();
608     }
609 
610    /* Monat */
611    xOffset = MONTH_X_OFFSET;
612    yOffset = MONTH_HEIGHT * (localTime->tm_mon);
613    XCopyArea(dpy, months.pixmap, visible.pixmap, normalGC,
614 	     xOffset, yOffset, MONTH_WIDTH, MONTH_HEIGHT,
615 	     xPos[MONTH_X_POS], yPos[MONTH_Y_POS]);
616 
617    /* Datum */
618    yOffset = DATE_Y_OFFSET;
619    if (localTime->tm_mday > 9)
620     {
621        xOffset = DATE_NUM_WIDTH * (((localTime->tm_mday / 10) + 9) % 10);
622        XCopyArea(dpy, dateNums.pixmap, visible.pixmap, normalGC,
623 		 xOffset, yOffset, DATE_NUM_WIDTH, DATE_NUM_HEIGHT,
624 		 xPos[DATE_LEFT_X_POS], yPos[DATE_Y_POS]);
625        xOffset = DATE_NUM_WIDTH * (((localTime->tm_mday % 10) + 9) % 10);
626        XCopyArea(dpy, dateNums.pixmap, visible.pixmap, normalGC,
627 		 xOffset, yOffset, DATE_NUM_WIDTH, DATE_NUM_HEIGHT,
628 		 xPos[DATE_RIGHT_X_POS], yPos[DATE_Y_POS]);
629     }
630    else
631     {
632        xOffset = DATE_NUM_WIDTH * (localTime->tm_mday - 1);
633        XCopyArea(dpy, dateNums.pixmap, visible.pixmap, normalGC,
634 		 xOffset, yOffset, DATE_NUM_WIDTH, DATE_NUM_HEIGHT,
635 		 xPos[DATE_CENTER_X_POS], yPos[DATE_Y_POS]);
636     }
637 
638    /* Wochentag */
639    xOffset = WEEKDAY_X_OFFSET;
640    yOffset = WEEKDAY_HEIGHT * ((localTime->tm_wday + 6) % 7);
641    XCopyArea(dpy, weekdays.pixmap, visible.pixmap, normalGC,
642 	     xOffset, yOffset, WEEKDAY_WIDTH, WEEKDAY_HEIGHT,
643 	     xPos[WEEKDAY_X_POS], yPos[WEEKDAY_Y_POS]);
644 
645    if ((!enableBlinking) && (!enableYearDisplay))
646     {
647        /* Sekunden Doppelpunkt ein */
648        xOffset = COLON_X_OFFSET;
649        yOffset = COLON_Y_OFFSET;
650        XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
651 		 xOffset, yOffset, COLON_WIDTH, COLON_HEIGHT,
652 		 xPos[COLON_X_POS], yPos[COLON_Y_POS]);
653     }
654 }
655 
656 /* Extract program name from the zeroth program argument */
extractProgName(char * argv0)657 char *extractProgName(char *argv0)
658 {
659    char *prog_name = NULL;
660 
661    if (NULL != argv0)
662     {
663        prog_name = strrchr(argv0, '/');
664        if (NULL == prog_name)
665 	{
666 	   prog_name = argv0;
667 	}
668        else
669 	{
670 	   prog_name++;
671 	}
672     }
673    return (prog_name);
674 }
675 
676 /* Process program arguments and set corresponding options */
processArgs(int argc,char ** argv)677 int processArgs(int argc, char **argv)
678 {
679    int i;
680 
681    for (i = 1; i < argc; i++)
682     {
683        if (0 == strcmp(argv[i], "--"))
684 	{
685 	   break;
686 	}
687        else if ((0 == strcmp(argv[i], "-12")) ||
688 		(0 == strcmp(argv[i], "-1")) ||
689 		(0 == strcmp(argv[i], "--12")))
690 	{
691 	   enable12HourClock = 1;
692 	}
693        else if ((0 == strcmp(argv[i], "-24")) ||
694 		(0 == strcmp(argv[i], "-2")) ||
695 		(0 == strcmp(argv[i], "--24")))
696 	{
697 	   enable12HourClock = 0;
698 	}
699        else if ((0 == strcmp(argv[i], "-exe")) ||
700 		(0 == strcmp(argv[i], "-e")) ||
701 		(0 == strcmp(argv[i], "--exe")))
702 	{
703 	   if (++i >= argc)
704 	    {
705 	       showUsage();
706 	    }
707 	   commandToExec = argv[i];
708 	}
709        else if ((0 == strcmp(argv[i], "-led")) ||
710 		(0 == strcmp(argv[i], "-l")) ||
711 		(0 == strcmp(argv[i], "--led")))
712 	{
713 	   if (++i >= argc)
714 	    {
715 	       showUsage();
716 	    }
717 	   ledColor = argv[i];
718 	}
719        else if ((0 == strcmp(argv[i], "-clockxpm")) ||
720 		(0 == strcmp(argv[i], "-c")) ||
721 		(0 == strcmp(argv[i], "--clockxpm")))
722 	{
723 #ifndef ONLY_SHAPED_WINDOW
724 	   if (++i >= argc)
725 	    {
726 	       showUsage();
727 	    }
728 	   userClockXpm = argv[i];
729 	   useUserClockXpm = 1;
730 #endif /* !ONLY_SHAPED_WINDOW */
731 	}
732        else if ((0 == strcmp(argv[i], "-monthxpm")) ||
733 		(0 == strcmp(argv[i], "-m")) ||
734 		(0 == strcmp(argv[i], "--monthxpm")))
735 	{
736 	   if (++i >= argc)
737 	    {
738 	       showUsage();
739 	    }
740 	   userMonthXpm = argv[i];
741 	   useUserMonthXpm = 1;
742 	}
743        else if ((0 == strcmp(argv[i], "-weekdayxpm")) ||
744 		(0 == strcmp(argv[i], "-w")) ||
745 		(0 == strcmp(argv[i], "--weekdayxpm")))
746 	{
747 	   if (++i >= argc)
748 	    {
749 	       showUsage();
750 	    }
751 	   userWeekdayXpm = argv[i];
752 	   useUserWeekdayXpm = 1;
753 	}
754        else if ((0 == strcmp(argv[i], "-noblink")) ||
755 		(0 == strcmp(argv[i], "-n")) ||
756 		(0 == strcmp(argv[i], "--noblink")))
757 	{
758 	   enableBlinking = 0;
759 	}
760        else if ((0 == strcmp(argv[i], "-year")) ||
761 		(0 == strcmp(argv[i], "-y")) ||
762 		(0 == strcmp(argv[i], "--year")))
763 	{
764 	   enableYearDisplay = 1;
765 	}
766        else if ((0 == strcmp(argv[i], "-position")) ||
767 		(0 == strcmp(argv[i], "-p")) ||
768 		(0 == strcmp(argv[i], "--position")))
769 	{
770 #ifndef ONLY_SHAPED_WINDOW
771 	   if (++i >= argc)
772 	    {
773 	       showUsage();
774 	    }
775 	   geometry = argv[i];
776 #endif /* !ONLY_SHAPED_WINDOW */
777 	}
778        else if ((0 == strcmp(argv[i], "-shape")) ||
779 		(0 == strcmp(argv[i], "-s")) ||
780 		(0 == strcmp(argv[i], "--shape")))
781 	{
782 	   enableShapedWindow = 1;
783 	}
784        else if ((0 == strcmp(argv[i], "-iconic")) ||
785 		(0 == strcmp(argv[i], "-i")) ||
786 		(0 == strcmp(argv[i], "--iconic")))
787 	{
788 #ifndef ONLY_SHAPED_WINDOW
789 	   startIconified = 1;
790 #endif /* !ONLY_SHAPED_WINDOW */
791 	}
792        else if ((0 == strcmp(argv[i], "-version")) ||
793 		(0 == strcmp(argv[i], "-V")) ||
794 		(0 == strcmp(argv[i], "--version")))
795 	{
796 	   showVersion();
797 	}
798        else if ((0 == strcmp(argv[i], "-help")) ||
799 		(0 == strcmp(argv[i], "-h")) ||
800 		(0 == strcmp(argv[i], "--help")))
801 	{
802 	   showUsage();
803 	}
804        else if ((0 == strcmp(argv[i], "-interval")) ||
805 		(0 == strcmp(argv[i], "--interval")))
806 	{
807 	   if (++i >= argc)
808 	    {
809 	       showUsage();
810 	    }
811 	   blinkInterval = atoi(argv[i]);
812 	}
813 
814        else
815 	{
816 	   fprintf(stderr, "%s: unrecognized option `%s'\n",
817 		   progName, argv[i]);
818 	   showUsage();
819 	}
820     }
821    return (i);
822 }
823 
824 /**********************************************************************/
main(int argc,char ** argv)825 int main(int argc, char **argv)
826 {
827    int           i;
828    unsigned int  borderWidth = 0;
829    char          *displayName = NULL;
830    XGCValues     gcValues;
831    unsigned long gcMask;
832    XEvent        event;
833    XTextProperty wmName;
834    XClassHint    classHint;
835    Pixmap        shapeMask;
836    struct timeval nextEvent;
837    unsigned int   blinkCounter = 0;
838 
839    /* Parse command line options */
840    progName = extractProgName(argv[0]);
841    processArgs(argc, argv);
842 
843    /* init led position */
844 #ifndef ONLY_SHAPED_WINDOW
845    for (i = 0; i < NUM_Y_POSITIONS; i++)
846     {
847        yPos[i] = enableShapedWindow ? yPosShaped[i] : yPosUnshaped[i];
848     }
849    for (i = 0; i < NUM_X_POSITIONS; i++)
850     {
851        xPos[i] = enableShapedWindow ? xPosShaped[i] : xPosUnshaped[i];
852     }
853 #else /* ONLY_SHAPED_WINDOW */
854    for (i = 0; i < NUM_Y_POSITIONS; i++)
855     {
856        yPos[i] = yPosShaped[i];
857     }
858    for (i = 0; i < NUM_X_POSITIONS; i++)
859     {
860        xPos[i] = xPosShaped[i];
861     }
862 #endif /* !ONLY_SHAPED_WINDOW */
863    for (i = 0; i < NUM_TIME_POSITIONS; i++)
864     {
865       if (enable12HourClock && (!enableYearDisplay))
866        {
867          xPos[i] += timePos24[i];
868        }
869       else
870        {
871          xPos[i] += timePos12[i];
872        }
873     }
874 
875    /* Open the display */
876    dpy = XOpenDisplay(displayName);
877    if (NULL == dpy)
878     {
879        fprintf(stderr, "%s: can't open display %s\n", progName,
880 	       XDisplayName(displayName));
881        exit(1);
882     }
883    screen       = DefaultScreen(dpy);
884    rootWindow   = RootWindow(dpy, screen);
885    displayDepth = DefaultDepth(dpy, screen);
886    xFd          = XConnectionNumber(dpy);
887 
888    /* Icon Daten nach XImage konvertieren */
889    GetXpms();
890 
891    /* Create a window to hold the banner */
892    sizeHints.x = 0;
893    sizeHints.y = 0;
894    sizeHints.min_width  = clockBg.attributes.width;
895    sizeHints.min_height = clockBg.attributes.height;
896    sizeHints.max_width  = clockBg.attributes.width;
897    sizeHints.max_height = clockBg.attributes.height;
898    sizeHints.base_width  = clockBg.attributes.width;
899    sizeHints.base_height = clockBg.attributes.height;
900    sizeHints.flags = USSize | USPosition | PMinSize | PMaxSize | PBaseSize;
901 
902    bgPixel = GetColor("white");
903    fgPixel = GetColor("black");
904 
905    XWMGeometry(dpy, screen, geometry, NULL, borderWidth, &sizeHints,
906 	       &sizeHints.x, &sizeHints.y, &sizeHints.width, &sizeHints.height,
907 	       &sizeHints.win_gravity);
908    sizeHints.width  = clockBg.attributes.width;
909    sizeHints.height = clockBg.attributes.height;
910 
911    win = XCreateSimpleWindow(dpy, rootWindow, sizeHints.x, sizeHints.y,
912 			     sizeHints.width, sizeHints.height,
913 			     borderWidth, fgPixel, bgPixel);
914    iconWin = XCreateSimpleWindow(dpy, win, sizeHints.x, sizeHints.y,
915 				 sizeHints.width, sizeHints.height,
916 				 borderWidth, fgPixel, bgPixel);
917 
918    /* Hints aktivieren */
919    XSetWMNormalHints(dpy, win, &sizeHints);
920    classHint.res_name = progName;
921    classHint.res_class = className;
922    XSetClassHint(dpy, win, &classHint);
923 
924    XSelectInput(dpy, win, OUR_WINDOW_EVENTS);
925    XSelectInput(dpy, iconWin, OUR_WINDOW_EVENTS);
926 
927    if (0 == XStringListToTextProperty(&progName, 1, &wmName))
928     {
929        fprintf(stderr, "%s: can't allocate window name text property\n",
930 	       progName);
931        exit(-1);
932     }
933    XSetWMName(dpy, win, &wmName);
934 
935    /* Create a GC for drawing */
936    gcMask = GCForeground | GCBackground | GCGraphicsExposures;
937    gcValues.foreground = fgPixel;
938    gcValues.background = bgPixel;
939    gcValues.graphics_exposures = False;
940    normalGC = XCreateGC(dpy, rootWindow, gcMask, &gcValues);
941 
942    if (enableShapedWindow)
943     {
944        shapeMask = XCreateBitmapFromData(dpy, win, (char *)mask_bits,
945 					 mask_width, mask_height);
946        XShapeCombineMask(dpy, win, ShapeBounding, 0, 0, shapeMask, ShapeSet);
947        XShapeCombineMask(dpy, iconWin, ShapeBounding, 0, 0, shapeMask,
948 			 ShapeSet);
949     }
950 
951    wmHints.initial_state = WithdrawnState;
952    wmHints.icon_window = iconWin;
953    wmHints.icon_x = sizeHints.x;
954    wmHints.icon_y = sizeHints.y;
955    wmHints.window_group = win;
956    wmHints.flags = StateHint | IconWindowHint | IconPositionHint |
957       WindowGroupHint;
958    XSetWMHints(dpy, win, &wmHints);
959 
960    XSetCommand(dpy, win, argv, argc);
961    XMapWindow(dpy,win);
962 
963    showTime();
964    redrawWindow(&visible);
965    while (1)
966     {
967        if (actualTime != mytime())
968 	{
969 	   actualTime = mytime();
970 	   if (actualMinutes != (actualTime / 60))
971 	    {
972 	       showTime();
973 	       if (!enableBlinking)
974 		{
975 		  redrawWindow(&visible);
976 		}
977 	    }
978 	   if (0 == (actualTime % 2))
979 	    {
980 	       /* Clean up zombie processes */
981 #ifdef DEBUG
982 	       fprintf(stderr, "%s: cleaning up zombies (time %ld)\n",
983 		       progName, actualTime);
984 #endif /* DEBUG */
985 	       if (NULL != commandToExec)
986 		{
987 		   waitpid(0, NULL, WNOHANG);
988 		}
989 	    }
990 	}
991        if (enableBlinking && (!enableYearDisplay))
992         {
993             blinkCounter++;
994 #ifdef SYSV
995             if (blinkCounter >= 20*blinkInterval)
996 #else
997             if (blinkCounter >= 2*blinkInterval)
998 #endif
999                 blinkCounter = 0;
1000             if (blinkCounter == 0)
1001 	     {
1002                 /* Sekunden Doppelpunkt ein */
1003 		XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
1004 			  COLON_X_OFFSET, COLON_Y_OFFSET,
1005 			  COLON_WIDTH, COLON_HEIGHT,
1006 			  xPos[COLON_X_POS], yPos[COLON_Y_POS]);
1007 	     }
1008 #ifdef SYSV
1009 	    if (blinkCounter == 10*blinkInterval)
1010 #else
1011 	    if (blinkCounter == blinkInterval)
1012 #endif
1013 	     {
1014 		    /* Sekunden Doppelpunkt aus */
1015 		    XCopyArea(dpy, led.pixmap, visible.pixmap, normalGC,
1016 			      BLANK_X_OFFSET, BLANK_Y_OFFSET,
1017 			      COLON_WIDTH, COLON_HEIGHT,
1018 			      xPos[COLON_X_POS], yPos[COLON_Y_POS]);
1019 	     }
1020 	    redrawWindow(&visible);
1021         }
1022 
1023        /* read a packet */
1024        while (XPending(dpy))
1025 	{
1026 	   XNextEvent(dpy, &event);
1027 	   switch(event.type)
1028 	    {
1029 	     case Expose:
1030 	       if (0 == event.xexpose.count)
1031 		{
1032 		   redrawWindow(&visible);
1033 		}
1034 	       break;
1035 	    case ButtonPress:
1036 	       if (NULL != commandToExec)
1037 		{
1038 		   pid_t fork_pid;
1039 
1040 		   if ((NULL == commandBuf) &&
1041 		       (!buildCommand(commandToExec, &commandBuf,
1042 				      &commandLength, &commandIndex)))
1043 		    {
1044 		       break;
1045 		    }
1046 		   fork_pid = fork();
1047 		   switch (fork_pid)
1048 		    {
1049 		     case 0:
1050 		       /* We're the child process;
1051 			* run the command and exit.
1052 			*/
1053 		       executeCommand(commandBuf);
1054 		       /* When the system() call finishes, we're done. */
1055 		       exit(0);
1056 		       break;
1057 		     case -1:
1058 		       /* We're the parent process, but
1059 			* fork() gave an error.
1060 			*/
1061 		       perror("fork");
1062 		       break;
1063 		     default:
1064 		       /* We're the parent process;
1065 			* keep on doing what we normally do.
1066 			*/
1067 		       break;
1068 		    }
1069 		}
1070 	       break;
1071 	    case DestroyNotify:
1072 #if 0
1073 	       XFreeGC(dpy, normalGC);
1074 	       XDestroyWindow(dpy, win);
1075 	       XDestroyWindow(dpy, iconWin);
1076 #endif /* 0 */
1077 #ifdef ONLY_SHAPED_WINDOW
1078 	       XFreePixmap(dpy, visible.pixmap);
1079 #endif /* ONLY_SHAPED_WINDOW */
1080 	       XCloseDisplay(dpy);
1081 	       exit(0);
1082 	     default:
1083 	       break;
1084 	    }
1085 	}
1086        XFlush(dpy);
1087 #ifdef SYSV
1088        if (enableYearDisplay)
1089 	{
1090 	   poll((struct poll *) 0, (size_t) 0, 200);	/* 1/5 sec */
1091 	}
1092        else
1093 	{
1094 	   poll((struct poll *) 0, (size_t) 0, 50);	/* 5/100 sec */
1095 	}
1096 #else
1097        /* We compute the date of next event, in order to avoid polling */
1098        if (enableBlinking)
1099 	 {
1100 	   gettimeofday(&nextEvent,NULL);
1101 	   nextEvent.tv_sec = 0;
1102 	   if (nextEvent.tv_usec < 500000)
1103 		   nextEvent.tv_usec = 500000-nextEvent.tv_usec;
1104 	   else
1105 		   nextEvent.tv_usec = 1000000-nextEvent.tv_usec;
1106 	 }
1107        else
1108 	 {
1109 	   if (enableYearDisplay)
1110 	     {
1111 	       nextEvent.tv_sec = 86400-actualTime%86400;
1112 	       nextEvent.tv_usec = 0;
1113 	     }
1114 	   else
1115 	     {
1116 	       nextEvent.tv_sec = 60-actualTime%60;
1117 	       nextEvent.tv_usec = 0;
1118 	     }
1119 	 }
1120        FD_ZERO(&xFdSet);
1121        FD_SET(xFd,&xFdSet);
1122        select(FD_SETSIZE,&xFdSet,NULL,NULL,&nextEvent);
1123 #endif
1124     }
1125    return (0);
1126 }
1127 
1128