Calendar/time for progman
at main 323 lines 8.0 kB view raw
1/* vim:ts=8 2 * 3 * Copyright (c) 2023 joshua stein <jcs@jcs.org> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <err.h> 19#include <getopt.h> 20#include <poll.h> 21#include <signal.h> 22#include <stdio.h> 23#include <stdlib.h> 24#include <string.h> 25#include <unistd.h> 26#include <sys/fcntl.h> 27#include <sys/types.h> 28 29#include <X11/Xlib.h> 30#include <X11/Xutil.h> 31#include <X11/xpm.h> 32 33#include "icons/calendar.xpm" 34#include "icons/digits.xpm" 35 36struct { 37 Display *dpy; 38 int screen; 39 Window win; 40 XWMHints hints; 41 GC gc; 42 Pixmap calendar_pm; 43 Pixmap calendar_pm_mask; 44 XpmAttributes calendar_pm_attrs; 45 Pixmap digits_pm; 46 Pixmap digits_pm_mask; 47 XpmAttributes digits_pm_attrs; 48 Pixmap icon_pm; 49 char *title_fmt; 50} xinfo = { 0 }; 51 52extern char *__progname; 53 54void killer(int); 55void usage(void); 56void redraw_icon(int); 57 58int exit_msg[2]; 59int last_day = 0; 60int last_min = -1; 61 62#define WINDOW_WIDTH 200 63#define WINDOW_HEIGHT 100 64#define DEFAULT_TITLE_FMT "%a %H:%M" 65 66int 67main(int argc, char* argv[]) 68{ 69 XEvent event; 70 XSizeHints *hints; 71 XGCValues gcv; 72 XWindowAttributes xgwa; 73 struct pollfd pfd[2]; 74 struct sigaction act; 75 char *display = NULL; 76 int ch; 77 78 while ((ch = getopt(argc, argv, "d:f:")) != -1) { 79 switch (ch) { 80 case 'd': 81 display = optarg; 82 break; 83 case 'f': 84 xinfo.title_fmt = strdup(optarg); 85 break; 86 default: 87 usage(); 88 } 89 } 90 argc -= optind; 91 argv += optind; 92 93 if (!(xinfo.dpy = XOpenDisplay(display))) 94 errx(1, "can't open display %s", XDisplayName(display)); 95 96#ifdef __OpenBSD_ 97 if (pledge("stdio") == -1) 98 err(1, "pledge"); 99#endif 100 101 if (xinfo.title_fmt == NULL) 102 xinfo.title_fmt = strdup(DEFAULT_TITLE_FMT); 103 104 /* setup exit handler pipe that we'll poll on */ 105 if (pipe2(exit_msg, O_CLOEXEC) != 0) 106 err(1, "pipe2"); 107 act.sa_handler = killer; 108 act.sa_flags = 0; 109 sigaction(SIGTERM, &act, NULL); 110 sigaction(SIGINT, &act, NULL); 111 sigaction(SIGHUP, &act, NULL); 112 113 xinfo.screen = DefaultScreen(xinfo.dpy); 114 xinfo.win = XCreateSimpleWindow(xinfo.dpy, 115 RootWindow(xinfo.dpy, xinfo.screen), 116 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, 117 BlackPixel(xinfo.dpy, xinfo.screen), 118 WhitePixel(xinfo.dpy, xinfo.screen)); 119 gcv.foreground = 1; 120 gcv.background = 0; 121 xinfo.gc = XCreateGC(xinfo.dpy, xinfo.win, GCForeground | GCBackground, 122 &gcv); 123 XSetFunction(xinfo.dpy, xinfo.gc, GXcopy); 124 125 /* load XPMs */ 126 if (XpmCreatePixmapFromData(xinfo.dpy, 127 RootWindow(xinfo.dpy, xinfo.screen), 128 calendar_xpm, &xinfo.calendar_pm, &xinfo.calendar_pm_mask, 129 &xinfo.calendar_pm_attrs) != 0) 130 errx(1, "XpmCreatePixmapFromData failed"); 131 132 if (XpmCreatePixmapFromData(xinfo.dpy, 133 RootWindow(xinfo.dpy, xinfo.screen), 134 digits_xpm, &xinfo.digits_pm, &xinfo.digits_pm_mask, 135 &xinfo.digits_pm_attrs) != 0) 136 errx(1, "XpmCreatePixmapFromData failed"); 137 138 XGetWindowAttributes(xinfo.dpy, xinfo.win, &xgwa); 139 xinfo.icon_pm = XCreatePixmap(xinfo.dpy, 140 RootWindow(xinfo.dpy, xinfo.screen), 141 xinfo.calendar_pm_attrs.width, 142 xinfo.calendar_pm_attrs.height, 143 xgwa.depth); 144 145 hints = XAllocSizeHints(); 146 if (!hints) 147 err(1, "XAllocSizeHints"); 148 hints->flags = PMinSize | PMaxSize; 149 hints->min_width = WINDOW_WIDTH; 150 hints->min_height = WINDOW_HEIGHT; 151 hints->max_width = WINDOW_WIDTH; 152 hints->max_height = WINDOW_HEIGHT; 153#if 0 /* disabled until progman displays minimize on non-dialog wins */ 154 XSetWMNormalHints(xinfo.dpy, xinfo.win, hints); 155#endif 156 157 redraw_icon(1); 158 159 xinfo.hints.initial_state = IconicState; 160 xinfo.hints.flags |= StateHint; 161 XSetWMHints(xinfo.dpy, xinfo.win, &xinfo.hints); 162 XMapWindow(xinfo.dpy, xinfo.win); 163 164 memset(&pfd, 0, sizeof(pfd)); 165 pfd[0].fd = ConnectionNumber(xinfo.dpy); 166 pfd[0].events = POLLIN; 167 pfd[1].fd = exit_msg[0]; 168 pfd[1].events = POLLIN; 169 170 /* we need to know when we're exposed */ 171 XSelectInput(xinfo.dpy, xinfo.win, ExposureMask); 172 173 for (;;) { 174 if (!XPending(xinfo.dpy)) { 175 poll(pfd, 2, 900); 176 if (pfd[1].revents) 177 /* exit msg */ 178 break; 179 180 if (!XPending(xinfo.dpy)) { 181 redraw_icon(0); 182 continue; 183 } 184 } 185 186 XNextEvent(xinfo.dpy, &event); 187 188 switch (event.type) { 189 case Expose: 190 redraw_icon(1); 191 break; 192 } 193 } 194 195 XFreePixmap(xinfo.dpy, xinfo.calendar_pm); 196 XFreePixmap(xinfo.dpy, xinfo.calendar_pm_mask); 197 XFreePixmap(xinfo.dpy, xinfo.digits_pm); 198 XFreePixmap(xinfo.dpy, xinfo.icon_pm); 199 XDestroyWindow(xinfo.dpy, xinfo.win); 200 XFree(hints); 201 XCloseDisplay(xinfo.dpy); 202 free(xinfo.title_fmt); 203 204 return 0; 205} 206 207void 208killer(int sig) 209{ 210 if (write(exit_msg[1], &exit_msg, 1)) 211 return; 212 213 warn("failed to exit cleanly"); 214 exit(1); 215} 216 217void 218usage(void) 219{ 220 fprintf(stderr, "usage: %s %s\n", __progname, 221 "[-d display] [-f title date format]"); 222 exit(1); 223} 224 225void 226redraw_icon(int update_win) 227{ 228 XTextProperty title_prop; 229 XWindowAttributes xgwa; 230 char title[100]; 231 char *titlep = (char *)&title; 232 int rc, dwidth, xo, yo; 233 struct tm *tm; 234 time_t now; 235 236 time(&now); 237 tm = localtime(&now); 238 239 if (tm->tm_min != last_min) { 240#ifdef DEBUG 241 printf("minute changed, updating title\n"); 242#endif 243 last_min = tm->tm_min; 244 245 strftime(title, sizeof(title), xinfo.title_fmt, tm); 246 247 /* update icon and window titles */ 248 if (!(rc = XStringListToTextProperty(&titlep, 1, &title_prop))) 249 errx(1, "XStringListToTextProperty"); 250 XSetWMIconName(xinfo.dpy, xinfo.win, &title_prop); 251 XStoreName(xinfo.dpy, xinfo.win, title); 252 } 253 254 if (tm->tm_mday == last_day && !update_win) 255 return; 256 257 if (tm->tm_mday != last_day) { 258#ifdef DEBUG 259 printf("day changed, rebuilding icon\n"); 260#endif 261 last_day = tm->tm_mday; 262 263 XCopyArea(xinfo.dpy, xinfo.calendar_pm, xinfo.icon_pm, xinfo.gc, 264 0, 0, 265 xinfo.calendar_pm_attrs.width, 266 xinfo.calendar_pm_attrs.height, 267 0, 0); 268 269 dwidth = (xinfo.digits_pm_attrs.width / 10); 270 if (tm->tm_mday >= 10) { 271 XCopyArea(xinfo.dpy, 272 xinfo.digits_pm, xinfo.icon_pm, xinfo.gc, 273 dwidth * (tm->tm_mday / 10), 274 0, 275 dwidth, 276 xinfo.digits_pm_attrs.height, 277 19, 28); 278 XCopyArea(xinfo.dpy, 279 xinfo.digits_pm, xinfo.icon_pm, xinfo.gc, 280 dwidth * (tm->tm_mday % 10), 281 0, 282 dwidth, 283 xinfo.digits_pm_attrs.height, 284 33, 28); 285 } else { 286 XCopyArea(xinfo.dpy, 287 xinfo.digits_pm, xinfo.icon_pm, xinfo.gc, 288 dwidth * tm->tm_mday, 0, 289 dwidth, 290 xinfo.digits_pm_attrs.height, 291 26, 28); 292 } 293 294 /* update the icon */ 295 xinfo.hints.icon_pixmap = xinfo.icon_pm; 296 xinfo.hints.icon_mask = xinfo.calendar_pm_mask; 297 xinfo.hints.flags = IconPixmapHint | IconMaskHint; 298 XSetWMHints(xinfo.dpy, xinfo.win, &xinfo.hints); 299 300 update_win = 1; 301 } 302 303 if (update_win) { 304 /* draw it in the center of the window */ 305#ifdef DEBUG 306 printf("updating window\n"); 307#endif 308 XGetWindowAttributes(xinfo.dpy, xinfo.win, &xgwa); 309 xo = (xgwa.width / 2) - (xinfo.calendar_pm_attrs.width / 2); 310 yo = (xgwa.height / 2) - (xinfo.calendar_pm_attrs.height / 2); 311 XClearWindow(xinfo.dpy, xinfo.win); 312 XSetClipMask(xinfo.dpy, xinfo.gc, xinfo.calendar_pm_mask); 313 XSetClipOrigin(xinfo.dpy, xinfo.gc, xo, yo); 314 XSetFunction(xinfo.dpy, xinfo.gc, GXcopy); 315 XCopyArea(xinfo.dpy, xinfo.icon_pm, xinfo.win, xinfo.gc, 316 0, 0, 317 xinfo.calendar_pm_attrs.width, 318 xinfo.calendar_pm_attrs.height, 319 xo, yo); 320 XSetClipMask(xinfo.dpy, xinfo.gc, xinfo.calendar_pm_mask); 321 XSetClipOrigin(xinfo.dpy, xinfo.gc, 0, 0); 322 } 323}