/*compile-command: set -x # Remove -lXrender if compiling with HAVE_XRENDER disabled gcc -O3 -Wall -Wshadow "$0" -L/usr/X11/lib -lXext -lX11 -lXau -lXdmcp -lXrender -lm -otclock2 ${1+"$@"} exit $? */ /* * tclock2.c -- Faux-transparent (or alpha-transparent) clock for X11. * * This program is public domain. * Written by Andrew Church * * Revision history: * 2019/12/30 -- Fixed rendering on R10G10B10 (high color depth) visuals. * 2019/12/19 -- Fixed rendering of antialiased lines on ARGB visuals * when the line color is not white or black. * 2008/9/14 -- Added real transparency for use with ARGB visuals, and * cleaned out various unused code. * 2008/9/20 -- Added an InputHint to the window, to hint to the window * manager that the window should not receive focus. */ /* Comment this out if you don't have the XRender extension (alpha * transparency support will be disabled). */ #define HAVE_XRENDER #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_XRENDER # include #endif #define min(a,b) ((a)<(b) ? (a) : (b)) #define max(a,b) ((a)>(b) ? (a) : (b)) /*************************************************************************/ /* Data structure for the clock window. */ typedef struct ClockWindow_ { /* Window size. */ int width, height; /* Clock radius (determined by the smaller of width and height). */ double size; /* X display connection. */ Display *display; /* X window. */ Window window; /* GC for operating on the window/pixmaps. */ GC gc; /* Base image (without the hands). */ XImage *base_image; /* Image for drawing the hands. */ XImage *hands_image; #ifdef HAVE_XRENDER /* Pixmap and Picture for alpha transparency operations. */ Pixmap alpha_pixmap; Picture window_picture; Picture alpha_picture; #endif } ClockWindow; /* Window title. */ #define WINDOW_TITLE "tclock" /* Default window geometry. */ #define DEFAULT_GEOMETRY "97x97-0+0" /* Upper-right corner */ /* Padding from edge of window, as a fraction of the clock radius. */ #define WINDOW_PADDING 0.1 /* Local routine declarations: */ static int open_window(ClockWindow *cw, const char *display, const char *geometry, int use_alpha); static void draw_face(ClockWindow *cw, uint32_t color); static void draw_hands(ClockWindow *cw, uint32_t color, const struct tm *tm); static void draw_line(XImage *img, uint32_t color, double x1, double y1, double x2, double y2, double width); /*************************************************************************/ /*************************************************************************/ /** * main: The ubiquitous main routine. * * Parameters: * argc: Command-line argument count. * argv: Command-line arguments. * Return value: * Program exit code (0 on normal exit, 1 on initialization error). */ int main(int argc, char **argv) { const char *display = NULL, *geometry = NULL; uint32_t color = 0x000000; /* Default to black */ int use_alpha = 0; /* Nonzero = use alpha transparency */ ClockWindow window; time_t last_time; int i; /* Parse command-line arguments. */ for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-display") == 0) { i++; if (i >= argc) { fprintf(stderr, "Missing argument to \"-display\"!\n"); return 1; } display = argv[i]; } else if (strcmp(argv[i], "-geometry") == 0) { i++; if (i >= argc) { fprintf(stderr, "Missing argument to \"-geometry\"!\n"); return 1; } geometry = argv[i]; } else if (strcmp(argv[i], "-color") == 0) { i++; if (i >= argc) { fprintf(stderr, "Missing argument to \"-color\"!\n"); return 1; } if (strcmp(argv[i], "white") == 0) { color = 0xFFFFFF; } else if (strcmp(argv[i], "black") == 0) { color = 0x000000; } else if (argv[i][0] == '#' && strspn(argv[i]+1, "0123456789ABCDEFabcdef") == 6 && argv[i][7] == 0 ) { color = strtoul(argv[i]+1, NULL, 16); } else { fprintf(stderr, "Argument to \"-color\" must be \"white\"," " \"-black\", or in the #RRGGBB format.\n"); return 1; } } else if (strcmp(argv[i], "-alpha") == 0) { #ifndef HAVE_XRENDER fprintf(stderr, "Alpha transparency is not compiled in.\n"); return 1; #else use_alpha = 1; #endif } else if (*argv[i] == '-') { if (strcmp(argv[i], "-h") != 0 && strcmp(argv[i], "--help") != 0) { fprintf(stderr, "Unknown option \"%s\"!\n", argv[i]); } fprintf(stderr, "Usage: %s [-display display] [-geometry geometry]\n" " [-color color] [-alpha]\n", argv[0]); return 1; } else { fprintf(stderr, "Non-option arguments not allowed!\n"); return 1; } } if (use_alpha) { color |= 0xFF000000; } /* Create the clock window. */ if (!open_window(&window, display, geometry, use_alpha)) { fprintf(stderr, "Unable to open clock window, exiting.\n"); return 1; } draw_face(&window, color); /* Update the clock immediately, then once a second. */ time(&last_time); for (;;) { struct timeval tv; draw_hands(&window, color, localtime(&last_time)); while (gettimeofday(&tv, NULL), tv.tv_sec == last_time) { /* Sleep for only 9/10 the remaining time until the next * second, so we update the clock display as close to the * actual second as possible without using up too much CPU * time. */ usleep((1000000 - tv.tv_usec) * 0.9); } last_time = tv.tv_sec; } /* Never reached, but just in case... */ return 0; } /*************************************************************************/ /** * open_window: Connect to the X server and open a window for the clock. * * Parameters: * cw: Pointer to the ClockWindow structure to initialize. * display: The X display to use, or NULL for the default display. * geometry: A standard X-style geometry string ("1x2+3-4", etc.), or * NULL to use the defaults. * use_alpha: Nonzero to create a window supporting alpha transparency. * Requires corresponding support in the X server. * Return value: * Nonzero on success, zero on error. */ static int open_window(ClockWindow *cw, const char *display, const char *geometry, int use_alpha) { int x, y, width, height; /* Window position and size */ XSetWindowAttributes win_attrs; unsigned long attrmask; XSizeHints size_hints; XWMHints wm_hints; XTextProperty text_prop; Visual *visual; int depth; char *s; /* Check parameters for validity. */ if (!cw) { fprintf(stderr, "BUG: cw == NULL in open_window()!\n"); return 0; } /* Initialize structure fields. */ cw->width = cw->height = 0; cw->display = NULL; cw->window = None; cw->gc = None; cw->base_image = cw->hands_image = NULL; #ifdef HAVE_XRENDER cw->alpha_pixmap = None; cw->window_picture = cw->alpha_picture = None; #endif /* Attempt to connect to the X display. */ cw->display = XOpenDisplay(display); if (!cw->display) { if (display) { fprintf(stderr, "Unable to connect to display \"%s\".\n", display); } else { fprintf(stderr, "Unable to connect to default display.\n"); } return 0; } /* Parse the geometry string supplied, if any, and determine the * window's size and position. */ x = y = width = height = 0; memset(&size_hints, 0, sizeof(size_hints)); XWMGeometry( /* display */ cw->display, /* screen */ DefaultScreen(cw->display), /* user_geom */ geometry, /* def_geom */ DEFAULT_GEOMETRY, /* bwidth */ 0, /* hints */ &size_hints, /* x_return */ &x, /* y_return */ &y, /* width_return */ &width, /* height_return */ &height, /* gravity_return */ &size_hints.win_gravity ); if (width == 0 || height == 0) { fprintf(stderr, "Unable to parse window geometry.\n"); XCloseDisplay(cw->display); cw->display = NULL; return 0; } if (geometry) { size_hints.flags |= USPosition | USSize; } else { size_hints.flags |= PPosition | PSize; } memset(&win_attrs, 0, sizeof(win_attrs)); win_attrs.backing_store = Always; attrmask = CWBackingStore; if (use_alpha) { XVisualInfo visualinfo; depth = 32; if (!XMatchVisualInfo(cw->display, DefaultScreen(cw->display), depth, TrueColor, &visualinfo)) { fprintf(stderr, "Unable to find a visual supporting alpha" " transparency.\n"); XCloseDisplay(cw->display); cw->display = NULL; return 0; } visual = visualinfo.visual; win_attrs.background_pixel = 0x00000000; win_attrs.border_pixel = 0x00000000; win_attrs.colormap = XCreateColormap(cw->display, DefaultRootWindow(cw->display), visual, AllocNone); attrmask |= CWBackPixel | CWBorderPixel | CWColormap; } else { depth = CopyFromParent; visual = CopyFromParent; } if (!use_alpha) { /* Create a borderless, unresizeable window that inherits the root * window as its background, so we can grab the background image. */ XSetWindowAttributes temp_attrs = win_attrs; temp_attrs.background_pixmap = ParentRelative; temp_attrs.override_redirect = True; cw->window = XCreateWindow( /* display */ cw->display, /* parent */ DefaultRootWindow(cw->display), /* x, y, w, h */ x, y, width, height, /* border_width */ 0, /* depth */ depth, /* class */ InputOutput, /* visual */ visual, /* valuemask */ attrmask | CWBackPixmap | CWOverrideRedirect, /* attributes */ &temp_attrs ); if (!cw->window) { fprintf(stderr, "Unable to create window for clock.\n"); XCloseDisplay(cw->display); cw->display = NULL; return 0; } size_hints.width = size_hints.min_width = size_hints.max_width = width; size_hints.height = size_hints.min_height = size_hints.max_height = height; size_hints.flags |= PMinSize | PMaxSize; s = (char *)WINDOW_TITLE; if (!XStringListToTextProperty(&s, 1, &text_prop)) { text_prop.value = NULL; } XSetWMProperties(/* display */ cw->display, /* w */ cw->window, /* window_name */ text_prop.value ? &text_prop : NULL, /* icon_name */ text_prop.value ? &text_prop : NULL, /* argv, argc */ NULL, 0, /* normal_hints */ &size_hints, /* wm_hints */ NULL, /* class_hints */ NULL ); if (text_prop.value) { XFree(text_prop.value); text_prop.value = NULL; } XMapWindow(cw->display, cw->window); /* Create images for drawing the face and hands. */ cw->base_image = XGetImage(cw->display, cw->window, 0, 0, width, height, AllPlanes, ZPixmap); cw->hands_image = XGetImage(cw->display, cw->window, 0, 0, width, height, AllPlanes, ZPixmap); if (!cw->base_image || !cw->hands_image) { fprintf(stderr, "Unable to create work image buffers.\n"); if (cw->base_image) { XDestroyImage(cw->base_image); } if (cw->hands_image) { XDestroyImage(cw->hands_image); } cw->base_image = cw->hands_image = NULL; XDestroyWindow(cw->display, cw->window); cw->window = None; XCloseDisplay(cw->display); cw->display = NULL; return 0; } /* Destroy the temporary window. */ XDestroyWindow(cw->display, cw->window); } /* if (!use_alpha) */ /* Create the (final) clock window. */ cw->window = XCreateWindow( /* display */ cw->display, /* parent */ DefaultRootWindow(cw->display), /* x, y, w, h */ x, y, width, height, /* border_width */ 0, /* depth */ depth, /* class */ InputOutput, /* visual */ visual, /* valuemask */ attrmask, /* attributes */ &win_attrs ); if (!cw->window) { fprintf(stderr, "Unable to create window for clock.\n"); if (cw->base_image) { XDestroyImage(cw->base_image); cw->base_image = NULL; } if (cw->hands_image) { XDestroyImage(cw->hands_image); cw->hands_image = NULL; } cw->window = None; XCloseDisplay(cw->display); cw->display = NULL; return 0; } s = (char *)WINDOW_TITLE; if (!XStringListToTextProperty(&s, 1, &text_prop)) { text_prop.value = NULL; } wm_hints.flags = InputHint; wm_hints.input = False; XSetWMProperties( /* display */ cw->display, /* w */ cw->window, /* window_name */ text_prop.value ? &text_prop : NULL, /* icon_name */ text_prop.value ? &text_prop : NULL, /* argv, argc */ NULL, 0, /* normal_hints */ &size_hints, /* wm_hints */ &wm_hints, /* class_hints */ NULL ); if (text_prop.value) { XFree(text_prop.value); text_prop.value = NULL; } XMapWindow(cw->display, cw->window); #ifdef HAVE_XRENDER /* In alpha transparency mode, we still have to create the images, so * do that now. We'll also need a pixmap for alpha processing. */ if (use_alpha) { cw->base_image = XCreateImage(cw->display, visual, depth, ZPixmap, 0, 0, width, height, depth, 0); if (cw->base_image) { cw->base_image->data = malloc(cw->base_image->bytes_per_line * height); } cw->hands_image = XCreateImage(cw->display, visual, depth, ZPixmap, 0, 0, width, height, depth, 0); if (cw->hands_image) { cw->hands_image->data = malloc(cw->hands_image->bytes_per_line * height); } if (!cw->base_image || !cw->hands_image) { fprintf(stderr, "Unable to create work image buffers.\n"); if (cw->base_image) { XDestroyImage(cw->base_image); } if (cw->hands_image) { XDestroyImage(cw->hands_image); } cw->base_image = cw->hands_image = NULL; XDestroyWindow(cw->display, cw->window); cw->window = None; XCloseDisplay(cw->display); cw->display = NULL; return 0; } cw->alpha_pixmap = XCreatePixmap(cw->display, cw->window, width, height, 32); if (!cw->alpha_pixmap) { fprintf(stderr, "Unable to create transparency pixmap.\n"); XDestroyImage(cw->base_image); XDestroyImage(cw->hands_image); cw->base_image = cw->hands_image = NULL; XDestroyWindow(cw->display, cw->window); cw->window = None; XCloseDisplay(cw->display); cw->display = NULL; return 0; } cw->window_picture = XRenderCreatePicture( cw->display, cw->window, XRenderFindStandardFormat(cw->display, PictStandardARGB32), 0, 0 ); cw->alpha_picture = XRenderCreatePicture( cw->display, cw->alpha_pixmap, XRenderFindStandardFormat(cw->display, PictStandardARGB32), 0, 0 ); if (!cw->window_picture || !cw->alpha_picture) { fprintf(stderr, "Unable to create transparency picture.\n"); if (cw->window_picture) { XRenderFreePicture(cw->display, cw->window_picture); } if (cw->alpha_picture) { XRenderFreePicture(cw->display, cw->alpha_picture); } XFreePixmap(cw->display, cw->alpha_pixmap); XDestroyImage(cw->base_image); XDestroyImage(cw->hands_image); cw->base_image = cw->hands_image = NULL; XDestroyWindow(cw->display, cw->window); cw->window = None; XCloseDisplay(cw->display); cw->display = NULL; return 0; } } #endif /* Create a GC for operating on the new window. */ cw->gc = XCreateGC(cw->display, cw->window, 0, NULL); if (!cw->gc) { fprintf(stderr, "Unable to initialize graphics context.\n"); #ifdef HAVE_XRENDER if (cw->window_picture) { XRenderFreePicture(cw->display, cw->window_picture); } if (cw->alpha_picture) { XRenderFreePicture(cw->display, cw->alpha_picture); } if (cw->alpha_pixmap) { XFreePixmap(cw->display, cw->alpha_pixmap); } #endif XDestroyImage(cw->base_image); XDestroyImage(cw->hands_image); XDestroyWindow(cw->display, cw->window); cw->window = None; XCloseDisplay(cw->display); cw->display = NULL; return 0; } /* Success! */ cw->width = width; cw->height = height; cw->size = (double)(widthsize / (i%5==0 ? 20 : 40); const double len = cw->size / (i%5==0 ? 10 : 20); const double angle = (double)i/60 * (2*M_PI); const double x1 = (double)cw->width /2 + (cw->size )*cos(angle); const double y1 = (double)cw->height/2 - (cw->size )*sin(angle); const double x2 = (double)cw->width /2 + (cw->size-len)*cos(angle); const double y2 = (double)cw->height/2 - (cw->size-len)*sin(angle); draw_line(cw->base_image, color, x1, y1, x2, y2, width); } } /*************************************************************************/ /** * draw_hands: Draw the clock hands according to the given time. * * Parameters: * cw: ClockWindow structure pointer. * color: Color for drawing, in 0xRRGGBB format. * tm: Time structure containing the current time. * Return value: * None. */ static void draw_hands(ClockWindow *cw, uint32_t color, const struct tm *tm) { unsigned int seconds; double angle, x, y; /* Check parameters for validity. */ if (!cw) { fprintf(stderr, "BUG: cw == NULL in draw_hands()!\n"); return; } if (!tm) { fprintf(stderr, "BUG: tm == NULL in draw_hands()!\n"); return; } /* Copy the base image to the current image. */ memcpy(cw->hands_image->data, cw->base_image->data, cw->base_image->bytes_per_line * cw->height); /* Calculate the number of seconds since 0:00/12:00. */ seconds = (tm->tm_hour%12)*3600 + tm->tm_min*60 + tm->tm_sec; /* Draw the hour hand. */ angle = (0.25 - (double)seconds / 43200) * (2*M_PI); x = (double)cw->width /2 + (cw->size*0.4 * cos(angle)); y = (double)cw->height/2 - (cw->size*0.4 * sin(angle)); draw_line(cw->hands_image, color, (double)cw->width/2, (double)cw->height/2, x, y, cw->size/20); /* Draw the minute hand. */ angle = (0.25 - (double)(seconds%3600) / 3600) * (2*M_PI); x = (double)cw->width /2 + (cw->size*0.75 * cos(angle)); y = (double)cw->height/2 - (cw->size*0.75 * sin(angle)); draw_line(cw->hands_image, color, (double)cw->width/2, (double)cw->height/2, x, y, cw->size/20); /* Draw the second hand. */ angle = (0.25 - (double)(seconds%60) / 60) * (2*M_PI); x = (double)cw->width /2 + (cw->size*0.9 * cos(angle)); y = (double)cw->height/2 - (cw->size*0.9 * sin(angle)); draw_line(cw->hands_image, color, (double)cw->width/2, (double)cw->height/2, x, y, cw->size/40); /* Copy the new image to the window. */ if (color & 0xFF000000) { /* i.e. alpha transparency is in use */ #ifdef HAVE_XRENDER XPutImage(cw->display, cw->window, cw->gc, cw->base_image, 0, 0, 0, 0, cw->width, cw->height); XPutImage(cw->display, cw->alpha_pixmap, cw->gc, cw->hands_image, 0, 0, 0, 0, cw->width, cw->height); XRenderComposite(cw->display, PictOpOver, cw->alpha_picture, None, cw->window_picture, 0, 0, 0, 0, 0, 0, cw->width, cw->height); #endif } else { /* i.e. faux transparency is in use */ XPutImage(cw->display, cw->window, cw->gc, cw->hands_image, 0, 0, 0, 0, cw->width, cw->height); } /* Flush the event queue to make sure the updated hands are displayed * immediately. */ XFlush(cw->display); } /*************************************************************************/ /** * draw_line: Draw an anti-aliased line from (x1,y1) to (x2,y2). * * Parameters: * img: Image to draw to. * color: Color for drawing, in 0xAARRGGBB format. * x1, y1: One endpoint of the line. * x2, y2: The other endpoint of the line. * width: The width of the line in pixels. * Return value: * None. */ static void draw_line(XImage *img, uint32_t color, double x1, double y1, double x2, double y2, double width) { unsigned long cA = (color>>24) & 0xFF; unsigned long cR = (color>>16) & 0xFF; unsigned long cG = (color>> 8) & 0xFF; unsigned long cB = (color>> 0) & 0xFF; if (y2 < y1) { double tt; tt = x1; x1 = x2; x2 = tt; tt = y1; y1 = y2; y2 = tt; } width = (width-1)/2; /* Turn it into the radius from the central line */ const double dx = x2 - x1; const double dy = y2 - y1; const double d2 = dx*dx + dy*dy; const int xmin = floor(min(x1,x2)-width); const int xmax = ceil (max(x1,x2)+width); const int ymin = floor(y1-width); const int ymax = ceil (y2+width); int Rbits, Gbits, Bbits, Abits; int Rsh, Gsh, Bsh, Ash; unsigned long Rmax, Gmax, Bmax, Amax; unsigned long Rmask, Gmask, Bmask, Amask; int x, y; unsigned long temp; for (temp = img->red_mask, Rsh = 0; temp && !(temp & 1); temp >>= 1) { Rsh++; } for (Rbits = 0; temp; temp >>= 1) { Rbits++; } for (temp = img->green_mask, Gsh = 0; temp && !(temp & 1); temp >>= 1) { Gsh++; } for (Gbits = 0; temp; temp >>= 1) { Gbits++; } for (temp = img->blue_mask, Bsh = 0; temp && !(temp & 1); temp >>= 1) { Bsh++; } for (Bbits = 0; temp; temp >>= 1) { Bbits++; } if (Rsh > Bsh) { Ash = Rsh + Rbits; } else { Ash = Bsh + Bbits; } assert(Ash <= img->depth); Abits = img->depth - Ash; /* Validate various assumptions made below. */ assert(Rbits >= 8); assert(Rbits <= 16); assert(Gbits >= 8); assert(Gbits <= 16); assert(Bbits >= 8); assert(Bbits <= 16); assert(Abits <= 16); assert(cA == 0 || Abits > 0); Rmax = (1UL << Rbits) - 1; Rmask = Rmax << Rsh; Gmax = (1UL << Gbits) - 1; Gmask = Gmax << Gsh; Bmax = (1UL << Bbits) - 1; Bmask = Bmax << Bsh; Amax = (1UL << Abits) - 1; Amask = Amax << Ash; cR = cR<<(Rbits-8) | cR>>(16-Rbits); cG = cG<<(Gbits-8) | cG>>(16-Gbits); cB = cB<<(Bbits-8) | cB>>(16-Bbits); cA = (cA * Amax + 127) / 255; for (y = ymin; y <= ymax; y++) { /* FIXME: assuming 32bpp */ uint32_t *dest = (uint32_t *)(img->data + y * img->bytes_per_line); const double y3 = y + 0.5; for (x = xmin; x <= xmax; x++) { /* * The projection P (x,y) of a point C (x3,y3) onto a line AB * (x1,y1)-(x2,y2) can be given as * x = x1 + p(x2-x1), y = y1 + p(y2-y1) * where p is equal to * (x3-x1)(x2-x1) + (y3-y1)(y2-y1) * ------------------------------- * (x2-x1)^2 + (y2-y1)^2 * The distance CP (the distance of C from the line) is then * d = sqrt((x3-x)^2 + (y3-y)^2) * or * d = sqrt((x3-x1-p(x2-x1))^2 + (y3-y1-p(y2-y1))^2) * However, we want the distance to the specific line segment * AB, not the infinite line; thus we clamp the value of p to * [0,1] before computing the distance. */ const double x3 = x + 0.5; const double pp = ((x3-x1)*(x2-x1) + (y3-y1)*(y2-y1)) / d2; const double p = max(0, min(1, pp)); const double xx = x3-x1-p*dx, yy = y3-y1-p*dy; const double d = sqrt(xx*xx + yy*yy); const double A = 1 - (d - width); if (A >= 1) { dest[x] = cR< 0) { unsigned int newR, newG, newB, newA; unsigned int oldR = (dest[x] & Rmask) >> Rsh; unsigned int oldG = (dest[x] & Gmask) >> Gsh; unsigned int oldB = (dest[x] & Bmask) >> Bsh; if (cA > 0) { /* i.e., alpha transparency is in use */ unsigned int oldA = (dest[x] & Amask) >> Ash; unsigned int addA = round(cA*A); newA = oldA + addA; if (newA > Amax) { oldR = (oldR * Amax + newA/2) / newA; oldG = (oldG * Amax + newA/2) / newA; oldB = (oldB * Amax + newA/2) / newA; addA = (addA * Amax + newA/2) / newA; newA = Amax; } /* Note that Xrender assumes premultiplied alpha. */ newR = oldR + (cR*addA + Amax/2) / Amax; newG = oldG + (cG*addA + Amax/2) / Amax; newB = oldB + (cB*addA + Amax/2) / Amax; } else { /* i.e. faux transparency is in use */ newR = round(oldR*(1-A) + cR*A); newG = round(oldG*(1-A) + cG*A); newB = round(oldB*(1-A) + cB*A); newA = 0; } if (newR > Rmax) { newR = Rmax; } if (newG > Gmax) { newG = Gmax; } if (newB > Bmax) { newB = Bmax; } dest[x] = newR<