1 #include <ctype.h>
2 #include <err.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <poll.h>
7 #include <signal.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <time.h>
11 #include <unistd.h>
12 #include <X11/Xlib.h>
13 #include <X11/Xatom.h>
14 #include <X11/Xresource.h>
15 #include <X11/Xft/Xft.h>
16 #include <X11/extensions/Xinerama.h>
17 #include <Imlib2.h>
18 #include "xnotify.h"
19 
20 /* macros */
21 #define DEFWIDTH            350     /* default width of a notification */
22 #define MIN(x,y)            ((x)<(y)?(x):(y))
23 #define MAX(x,y)            ((x)>(y)?(x):(y))
24 #define BETWEEN(x, a, b)    ((a) <= (x) && (x) <= (b))
25 
26 /* X stuff */
27 static Display *dpy;
28 static Colormap colormap;
29 static Visual *visual;
30 static Window root;
31 static XrmDatabase xdb;
32 static char *xrm;
33 static int screen;
34 static int depth;
35 static int xfd;
36 static struct Monitor mon;
37 static Atom utf8string;
38 static Atom netatom[NetLast];
39 static struct DC dc;
40 static struct Fonts titlefnt, bodyfnt;
41 
42 /* flags */
43 static int oflag = 0;   /* whether only one notification must exist at a time */
44 volatile sig_atomic_t usrflag;  /* 1 if for SIGUSR1, 2 for SIGUSR2, 0 otherwise */
45 
46 /* include configuration structure */
47 #include "config.h"
48 
49 /* show usage */
50 void
usage(void)51 usage(void)
52 {
53 	(void)fprintf(stderr, "usage: xnotify [-o] [-G gravity] [-b button] [-g geometry] [-m monitor] [-s seconds]\n");
54 	exit(1);
55 }
56 
57 /* get configuration from X resources */
58 static void
getresources(void)59 getresources(void)
60 {
61 	XrmValue xval;
62 	unsigned long n;
63 	char *type;
64 
65 	if (xrm == NULL || xdb == NULL)
66 		return;
67 
68 	if (XrmGetResource(xdb, "xnotify.title.font", "*", &type, &xval) == True)
69 		config.titlefont = xval.addr;
70 	if (XrmGetResource(xdb, "xnotify.body.font", "*", &type, &xval) == True)
71 		config.bodyfont = xval.addr;
72 	if (XrmGetResource(xdb, "xnotify.background", "*", &type, &xval) == True)
73 		config.background_color = xval.addr;
74 	if (XrmGetResource(xdb, "xnotify.foreground", "*", &type, &xval) == True)
75 		config.foreground_color = xval.addr;
76 	if (XrmGetResource(xdb, "xnotify.border", "*", &type, &xval) == True)
77 		config.border_color = xval.addr;
78 	if (XrmGetResource(xdb, "xnotify.geometry", "*", &type, &xval) == True)
79 		config.geometryspec = xval.addr;
80 	if (XrmGetResource(xdb, "xnotify.gravity", "*", &type, &xval) == True)
81 		config.gravityspec = xval.addr;
82 	if (XrmGetResource(xdb, "xnotify.borderWidth", "*", &type, &xval) == True)
83 		if ((n = strtoul(xval.addr, NULL, 10)) < INT_MAX)
84 			config.border_pixels = n;
85 	if (XrmGetResource(xdb, "xnotify.gap", "*", &type, &xval) == True)
86 		if ((n = strtoul(xval.addr, NULL, 10)) < INT_MAX)
87 			config.gap_pixels = n;
88 	if (XrmGetResource(xdb, "xnotify.imageWidth", "*", &type, &xval) == True)
89 		if ((n = strtoul(xval.addr, NULL, 10)) < INT_MAX)
90 			config.image_pixels = n;
91 	if (XrmGetResource(xdb, "xnotify.leading", "*", &type, &xval) == True)
92 		if ((n = strtoul(xval.addr, NULL, 10)) < INT_MAX)
93 			config.leading_pixels = n;
94 	if (XrmGetResource(xdb, "xnotify.padding", "*", &type, &xval) == True)
95 		if ((n = strtoul(xval.addr, NULL, 10)) < INT_MAX)
96 			config.padding_pixels = n;
97 	if (XrmGetResource(xdb, "xnotify.shrink", "*", &type, &xval) == True)
98 		config.shrink = (strcasecmp(xval.addr, "true") == 0 ||
99 		                strcasecmp(xval.addr, "on") == 0 ||
100 		                strcasecmp(xval.addr, "1") == 0);
101 	if (XrmGetResource(xdb, "xnotify.alignment", "*", &type, &xval) == True) {
102 		if (strcasecmp(xval.addr, "center") == 0)
103 			config.alignment = CenterAlignment;
104 		else if (strcasecmp(xval.addr, "left") == 0)
105 			config.alignment = LeftAlignment;
106 		else if (strcasecmp(xval.addr, "right") == 0)
107 			config.alignment = RightAlignment;
108 	}
109 }
110 
111 /* get configuration from commmand-line */
112 static void
getoptions(int argc,char * argv[])113 getoptions(int argc, char *argv[])
114 {
115 	unsigned long n;
116 	int ch;
117 
118 	while ((ch = getopt(argc, argv, "G:b:g:m:os:")) != -1) {
119 		switch (ch) {
120 		case 'G':
121 			config.gravityspec = optarg;
122 			break;
123 		case 'b':
124 			if (*(optarg+1) != '\0')
125 				break;
126 			switch (*optarg) {
127 			case '1':
128 				config.actionbutton = Button1;
129 				break;
130 			case '2':
131 				config.actionbutton = Button2;
132 				break;
133 			case '3':
134 				config.actionbutton = Button3;
135 				break;
136 			case '4':
137 				config.actionbutton = Button4;
138 				break;
139 			case '5':
140 				config.actionbutton = Button5;
141 				break;
142 			default:
143 				break;
144 			}
145 			break;
146 		case 'g':
147 			config.geometryspec = optarg;
148 			break;
149 		case 'm':
150 			mon.num = atoi(optarg);
151 			break;
152 		case 'o':
153 			oflag = 1;
154 			break;
155 		case 's':
156 			if ((n = strtoul(optarg, NULL, 10)) < INT_MAX)
157 				config.sec = n;
158 			break;
159 		default:
160 			usage();
161 			break;
162 		}
163 	}
164 	argc -= optind;
165 	argv += optind;
166 
167 	if (argc)
168 		usage();
169 }
170 
171 /* get XftColor *color from color string s */
172 static int
ealloccolor(const char * s,XftColor * color,int exitonerror)173 ealloccolor(const char *s, XftColor *color, int exitonerror)
174 {
175 	if (!XftColorAllocName(dpy, visual, colormap, s, color)) {
176 		if (exitonerror)
177 			errx(1, "could not allocate color: %s", s);
178 		warnx("could not allocate color: %s", s);
179 		return -1;
180 	}
181 	return 0;
182 }
183 
184 /* get number from *s into n, return 1 if error, update s to end of number */
185 static int
getnum(const char ** s,int * n)186 getnum(const char **s, int *n)
187 {
188 	int retval;
189 	long num;
190 	char *endp;
191 
192 	num = strtol(*s, &endp, 10);
193 	errno = 0;
194 	retval = (errno == ERANGE || num > INT_MAX || num < 0 || endp == *s);
195 	*s = endp;
196 	*n = num;
197 	return retval;
198 }
199 
200 /* parse geometry specification and return geometry values */
201 static void
parsegeometryspec(int * x,int * y,int * w,int * h)202 parsegeometryspec(int *x, int *y, int *w, int *h)
203 {
204 	int sign;
205 	int n;
206 	const char *s;
207 
208 	*x = *y = *w = *h = 0;
209 	s = config.geometryspec;
210 
211 	if (*s != '+' && *s != '-') {
212 		/* get *w */
213 		if (getnum(&s, &n))
214 			goto error;
215 		if (*s == '%') {
216 			if (n > 100)
217 				goto error;
218 			*w = (n * (mon.w - config.border_pixels * 2))/100;
219 			s++;
220 		} else {
221 			*w = n;
222 		}
223 		if (*s++ != 'x')
224 			goto error;
225 
226 		/* get *h */
227 		if (getnum(&s, &n))
228 			goto error;
229 		if (*s == '%') {
230 			if (n > 100)
231 				goto error;
232 			*h = (n * (mon.h - config.border_pixels * 2))/100;
233 			s++;
234 		} else {
235 			*h = n;
236 		}
237 	}
238 
239 	if (*s == '+' || *s == '-') {
240 		/* get *x */
241 		sign = (*s++ == '-') ? -1 : 1;
242 		if (getnum(&s, &n))
243 			goto error;
244 		*x = n * sign;
245 		if (*s != '+' && *s != '-')
246 			goto error;
247 
248 		/* get *y */
249 		sign = (*s++ == '-') ? -1 : 1;
250 		if (getnum(&s, &n))
251 			goto error;
252 		*y = n * sign;
253 	}
254 	if (*s != '\0')
255 		goto error;
256 
257 	return;
258 
259 error:
260 	errx(1, "improper geometry specification %s", config.geometryspec);
261 }
262 
263 /* parse gravity specification and return gravity value */
264 static void
parsegravityspec(int * gravity,int * direction)265 parsegravityspec(int *gravity, int *direction)
266 {
267 	if (config.gravityspec == NULL || strcmp(config.gravityspec, "N") == 0) {
268 		*gravity = NorthGravity;
269 		*direction = DownWards;
270 	} else if (strcmp(config.gravityspec, "NW") == 0) {
271 		*gravity = NorthWestGravity;
272 		*direction = DownWards;
273 	} else if (strcmp(config.gravityspec, "NE") == 0) {
274 		*gravity = NorthEastGravity;
275 		*direction = DownWards;
276 	} else if (strcmp(config.gravityspec, "W") == 0) {
277 		*gravity = WestGravity;
278 		*direction = DownWards;
279 	} else if (strcmp(config.gravityspec, "C") == 0) {
280 		*gravity = CenterGravity;
281 		*direction = DownWards;
282 	} else if (strcmp(config.gravityspec, "E") == 0) {
283 		*gravity = EastGravity;
284 		*direction = DownWards;
285 	} else if (strcmp(config.gravityspec, "SW") == 0) {
286 		*gravity = SouthWestGravity;
287 		*direction = UpWards;
288 	} else if (strcmp(config.gravityspec, "S") == 0) {
289 		*gravity = SouthGravity;
290 		*direction = UpWards;
291 	} else if (strcmp(config.gravityspec, "SE") == 0) {
292 		*gravity = SouthEastGravity;
293 		*direction = UpWards;
294 	} else {
295 		errx(EXIT_FAILURE, "Unknown gravity %s", config.gravityspec);
296 	}
297 }
298 
299 /* parse font string */
300 static void
parsefonts(struct Fonts * fnt,const char * s)301 parsefonts(struct Fonts *fnt, const char *s)
302 {
303 	const char *p;
304 	char buf[1024];
305 	size_t nfont = 0;
306 
307 	fnt->nfonts = 1;
308 	for (p = s; *p; p++)
309 		if (*p == ',')
310 			fnt->nfonts++;
311 
312 	if ((fnt->fonts = calloc(fnt->nfonts, sizeof *fnt->fonts)) == NULL)
313 		err(1, "calloc");
314 
315 	p = s;
316 	while (*p != '\0') {
317 		size_t i;
318 
319 		i = 0;
320 		while (isspace(*p))
321 			p++;
322 		while (i < sizeof buf && *p != '\0' && *p != ',')
323 			buf[i++] = *p++;
324 		if (i >= sizeof buf)
325 			errx(1, "font name too long");
326 		if (*p == ',')
327 			p++;
328 		buf[i] = '\0';
329 		if (nfont == 0)
330 			if ((fnt->pattern = FcNameParse((FcChar8 *)buf)) == NULL)
331 				errx(1, "the first font in the cache must be loaded from a font string");
332 		if ((fnt->fonts[nfont++] = XftFontOpenName(dpy, screen, buf)) == NULL)
333 			errx(1, "cannot load font");
334 	}
335 	fnt->texth = fnt->fonts[0]->height;
336 }
337 
338 /* signal SIGUSR1 handler (close all notifications) */
339 static void
sigusr1handler(int sig)340 sigusr1handler(int sig)
341 {
342 	(void)sig;
343 	usrflag = 1;
344 }
345 
346 /* signal SIGUSR2 handler (print cmd of first notification) */
347 static void
sigusr2handler(int sig)348 sigusr2handler(int sig)
349 {
350 	(void)sig;
351 	usrflag = 2;
352 }
353 
354 /* init signal  */
355 static void
initsignal(void)356 initsignal(void)
357 {
358 	struct sigaction sa;
359 
360 	sa.sa_handler = sigusr1handler;
361 	sa.sa_flags = 0;
362 	sigemptyset(&sa.sa_mask);
363 	if (sigaction(SIGUSR1, &sa, NULL) == -1)
364 		err(1, "sigaction");
365 
366 	sa.sa_handler = sigusr2handler;
367 	sa.sa_flags = 0;
368 	sigemptyset(&sa.sa_mask);
369 	if (sigaction(SIGUSR2, &sa, NULL) == -1)
370 		err(1, "sigaction");
371 }
372 
373 /* query monitor information */
374 static void
initmonitor(void)375 initmonitor(void)
376 {
377 	XineramaScreenInfo *info = NULL;
378 	int nmons;
379 
380 	mon.x = mon.y = 0;
381 	mon.w = DisplayWidth(dpy, screen);
382 	mon.h = DisplayHeight(dpy, screen);
383 	if ((info = XineramaQueryScreens(dpy, &nmons)) != NULL) {
384 		int selmon;
385 
386 		selmon = (mon.num >= 0 && mon.num < nmons) ? mon.num : 0;
387 		mon.x = info[selmon].x_org;
388 		mon.y = info[selmon].y_org;
389 		mon.w = info[selmon].width;
390 		mon.h = info[selmon].height;
391 		XFree(info);
392 	}
393 }
394 
395 /* init draw context structure */
396 static void
initdc(void)397 initdc(void)
398 {
399 	/* get colors */
400 	ealloccolor(config.background_color,    &dc.background, 1);
401 	ealloccolor(config.foreground_color,    &dc.foreground, 1);
402 	ealloccolor(config.border_color,        &dc.border, 1);
403 
404 	/* create common GC */
405 	dc.gc = XCreateGC(dpy, root, 0, NULL);
406 
407 	/* try to get font */
408 	parsefonts(&titlefnt, config.titlefont);
409 	parsefonts(&bodyfnt, config.bodyfont);
410 }
411 
412 /* Intern the used atoms */
413 static void
initatoms(void)414 initatoms(void)
415 {
416 	utf8string = XInternAtom(dpy, "UTF8_STRING", False);
417 	netatom[NetWMName] = XInternAtom(dpy, "_NET_WM_NAME", False);
418 	netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", False);
419 	netatom[NetWMWindowTypeNotification] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE_NOTIFICATION", False);
420 	netatom[NetWMState] = XInternAtom(dpy, "_NET_WM_STATE", False);
421 	netatom[NetWMStateAbove] = XInternAtom(dpy, "_NET_WM_STATE_ABOVE", False);
422 }
423 
424 /* watch ConfigureNotify on root window so we're notified when monitors change */
425 static void
initstructurenotify(void)426 initstructurenotify(void)
427 {
428 	XSelectInput(dpy, root, StructureNotifyMask);
429 }
430 
431 /* allocate queue and set its members */
432 static struct Queue *
setqueue(void)433 setqueue(void)
434 {
435 	struct Queue *queue;
436 
437 	if ((queue = malloc(sizeof *queue)) == NULL)
438 		err(1, "malloc");
439 
440 	queue->head = NULL;
441 	queue->tail = NULL;
442 	queue->change = 0;
443 
444 	/* set geometry of notification queue */
445 	parsegravityspec(&queue->gravity, &queue->direction);
446 	parsegeometryspec(&queue->x, &queue->y, &queue->w, &queue->h);
447 	if (queue->w == 0)
448 		queue->w = DEFWIDTH;
449 	if (queue->h == 0)
450 		queue->h = titlefnt.texth + config.padding_pixels * 2;
451 
452 	if (config.image_pixels <= 0)
453 		config.image_pixels = queue->h - config.padding_pixels;
454 	if (config.image_pixels < 0)
455 		config.image_pixels = 0;
456 
457 	return queue;
458 }
459 
460 /* get item of given window */
461 static struct Item *
getitem(struct Queue * queue,Window win)462 getitem(struct Queue *queue, Window win)
463 {
464 	struct Item *item;
465 
466 	for (item = queue->head; item; item = item->next)
467 		if (item->win == win)
468 			return item;
469 	return NULL;
470 }
471 
472 /* copy area from item's pixmap into item's window */
473 static void
copypixmap(struct Item * item)474 copypixmap(struct Item *item)
475 {
476 	XCopyArea(dpy, item->pixmap, item->win, dc.gc, 0, 0, item->w, item->h, 0, 0);
477 }
478 
479 /* load and scale image */
480 static Imlib_Image
loadimage(const char * file,int * width_ret,int * height_ret)481 loadimage(const char *file, int *width_ret, int *height_ret)
482 {
483 	Imlib_Image image;
484 	Imlib_Load_Error errcode;
485 	const char *errstr;
486 	int width, height;
487 
488 	image = imlib_load_image_with_error_return(file, &errcode);
489 	if (*file == '\0') {
490 		warnx("could not load image (file name is blank)");
491 		return NULL;
492 	} else if (image == NULL) {
493 		switch (errcode) {
494 		case IMLIB_LOAD_ERROR_FILE_DOES_NOT_EXIST:
495 			errstr = "file does not exist";
496 			break;
497 		case IMLIB_LOAD_ERROR_FILE_IS_DIRECTORY:
498 			errstr = "file is directory";
499 			break;
500 		case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_READ:
501 		case IMLIB_LOAD_ERROR_PERMISSION_DENIED_TO_WRITE:
502 			errstr = "permission denied";
503 			break;
504 		case IMLIB_LOAD_ERROR_NO_LOADER_FOR_FILE_FORMAT:
505 			errstr = "unknown file format";
506 			break;
507 		case IMLIB_LOAD_ERROR_PATH_TOO_LONG:
508 			errstr = "path too long";
509 			break;
510 		case IMLIB_LOAD_ERROR_PATH_COMPONENT_NON_EXISTANT:
511 		case IMLIB_LOAD_ERROR_PATH_COMPONENT_NOT_DIRECTORY:
512 		case IMLIB_LOAD_ERROR_PATH_POINTS_OUTSIDE_ADDRESS_SPACE:
513 			errstr = "improper path";
514 			break;
515 		case IMLIB_LOAD_ERROR_TOO_MANY_SYMBOLIC_LINKS:
516 			errstr = "too many symbolic links";
517 			break;
518 		case IMLIB_LOAD_ERROR_OUT_OF_MEMORY:
519 			errstr = "out of memory";
520 			break;
521 		case IMLIB_LOAD_ERROR_OUT_OF_FILE_DESCRIPTORS:
522 			errstr = "out of file descriptors";
523 			break;
524 		default:
525 			errstr = "unknown error";
526 			break;
527 		}
528 		warnx("could not load image (%s): %s", errstr, file);
529 		return NULL;
530 	}
531 
532 	imlib_context_set_image(image);
533 
534 	width = imlib_image_get_width();
535 	height = imlib_image_get_height();
536 
537 	if (width > height) {
538 		*width_ret = config.image_pixels;
539 		*height_ret = (height * config.image_pixels) / width;
540 	} else {
541 		*width_ret = (width * config.image_pixels) / height;
542 		*height_ret = config.image_pixels;
543 	}
544 
545 	image = imlib_create_cropped_scaled_image(0, 0, width, height, *width_ret, *height_ret);
546 
547 	return image;
548 }
549 
550 /* create window for item */
551 static void
createwindow(struct Item * item)552 createwindow(struct Item *item)
553 {
554 	XClassHint classhint = {"XNotify", "XNotify"};
555 	XSetWindowAttributes swa;
556 
557 	swa.override_redirect = True;
558 	swa.background_pixel = dc.background.pixel;
559 	swa.border_pixel = dc.border.pixel;
560 	swa.save_under = True;  /* pop-up windows should save_under */
561 	swa.event_mask = ExposureMask | ButtonPressMask | PointerMotionMask;
562 
563 	/* windows are created at 0,0 position for they'll be moved later */
564 	item->win = XCreateWindow(dpy, root, 0, 0, item->w, item->h, config.border_pixels,
565 	                          CopyFromParent, CopyFromParent, CopyFromParent,
566 	                          CWOverrideRedirect | CWBackPixel | CWBorderPixel |
567 	                          CWSaveUnder | CWEventMask, &swa);
568 
569 	XSetClassHint(dpy, item->win, &classhint);
570 
571 	XStoreName(dpy, item->win, "XNotify");
572 	XChangeProperty(dpy, item->win, netatom[NetWMName], utf8string, 8, PropModeReplace,
573 	                (unsigned char *)"XNotify", strlen("XNotify"));
574 	XChangeProperty(dpy, item->win, netatom[NetWMWindowType], XA_ATOM, 32, PropModeReplace,
575 	                (unsigned char *)&netatom[NetWMWindowTypeNotification], 1L);
576 	XChangeProperty(dpy, item->win, netatom[NetWMState], XA_ATOM, 32, PropModeReplace,
577 	                (unsigned char *)&netatom[NetWMStateAbove], 1L);
578 }
579 
580 /* get next utf8 char from s return its codepoint and set next_ret to pointer to end of character */
581 static FcChar32
getnextutf8char(const char * s,const char ** next_ret)582 getnextutf8char(const char *s, const char **next_ret)
583 {
584 	static const unsigned char utfbyte[] = {0x80, 0x00, 0xC0, 0xE0, 0xF0};
585 	static const unsigned char utfmask[] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
586 	static const FcChar32 utfmin[] = {0, 0x00,  0x80,  0x800,  0x10000};
587 	static const FcChar32 utfmax[] = {0, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
588 	/* 0xFFFD is the replacement character, used to represent unknown characters */
589 	static const FcChar32 unknown = 0xFFFD;
590 	FcChar32 ucode;         /* FcChar32 type holds 32 bits */
591 	size_t usize = 0;       /* n' of bytes of the utf8 character */
592 	size_t i;
593 
594 	*next_ret = s+1;
595 
596 	/* get code of first byte of utf8 character */
597 	for (i = 0; i < sizeof utfmask; i++) {
598 		if (((unsigned char)*s & utfmask[i]) == utfbyte[i]) {
599 			usize = i;
600 			ucode = (unsigned char)*s & ~utfmask[i];
601 			break;
602 		}
603 	}
604 
605 	/* if first byte is a continuation byte or is not allowed, return unknown */
606 	if (i == sizeof utfmask || usize == 0)
607 		return unknown;
608 
609 	/* check the other usize-1 bytes */
610 	s++;
611 	for (i = 1; i < usize; i++) {
612 		*next_ret = s+1;
613 		/* if byte is nul or is not a continuation byte, return unknown */
614 		if (*s == '\0' || ((unsigned char)*s & utfmask[0]) != utfbyte[0])
615 			return unknown;
616 		/* 6 is the number of relevant bits in the continuation byte */
617 		ucode = (ucode << 6) | ((unsigned char)*s & ~utfmask[0]);
618 		s++;
619 	}
620 
621 	/* check if ucode is invalid or in utf-16 surrogate halves */
622 	if (!BETWEEN(ucode, utfmin[usize], utfmax[usize])
623 	    || BETWEEN (ucode, 0xD800, 0xDFFF))
624 		return unknown;
625 
626 	return ucode;
627 }
628 
629 /* get which font contains a given code point */
630 static XftFont *
getfontucode(struct Fonts * fnt,FcChar32 ucode)631 getfontucode(struct Fonts *fnt, FcChar32 ucode)
632 {
633 	FcCharSet *fccharset = NULL;
634 	FcPattern *fcpattern = NULL;
635 	FcPattern *match = NULL;
636 	XftFont *retfont = NULL;
637 	XftResult result;
638 	size_t i;
639 
640 	for (i = 0; i < fnt->nfonts; i++)
641 		if (XftCharExists(dpy, fnt->fonts[i], ucode) == FcTrue)
642 			return fnt->fonts[i];
643 
644 	/* create a charset containing our code point */
645 	fccharset = FcCharSetCreate();
646 	FcCharSetAddChar(fccharset, ucode);
647 
648 	/* create a pattern akin to the fnt->pattern but containing our charset */
649 	if (fccharset) {
650 		fcpattern = FcPatternDuplicate(fnt->pattern);
651 		FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset);
652 	}
653 
654 	/* find pattern matching fcpattern */
655 	if (fcpattern) {
656 		FcConfigSubstitute(NULL, fcpattern, FcMatchPattern);
657 		FcDefaultSubstitute(fcpattern);
658 		match = XftFontMatch(dpy, screen, fcpattern, &result);
659 	}
660 
661 	/* if found a pattern, open its font */
662 	if (match) {
663 		retfont = XftFontOpenPattern(dpy, match);
664 		if (retfont && XftCharExists(dpy, retfont, ucode) == FcTrue) {
665 			if ((fnt->fonts = realloc(fnt->fonts, fnt->nfonts+1)) == NULL)
666 				err(1, "realloc");
667 			fnt->fonts[fnt->nfonts] = retfont;
668 			return fnt->fonts[fnt->nfonts++];
669 		} else {
670 			XftFontClose(dpy, retfont);
671 		}
672 	}
673 
674 	/* in case no fount was found, return the first one */
675 	return fnt->fonts[0];
676 }
677 
678 /* draw text into draw (if draw != NULL); return width of text glyphs */
679 static int
drawtext(struct Fonts * fnt,XftDraw * draw,XftColor * color,int x,int y,const char * text)680 drawtext(struct Fonts *fnt, XftDraw *draw, XftColor *color, int x, int y, const char *text)
681 {
682 	int textwidth = 0;
683 
684 	while (*text) {
685 		XftFont *currfont;
686 		XGlyphInfo ext;
687 		FcChar32 ucode;
688 		const char *next;
689 		size_t len;
690 
691 		ucode = getnextutf8char(text, &next);
692 		currfont = getfontucode(fnt, ucode);
693 
694 		len = next - text;
695 		XftTextExtentsUtf8(dpy, currfont, (XftChar8 *)text, len, &ext);
696 		textwidth += ext.xOff;
697 
698 		if (draw) {
699 			int texty;
700 
701 			texty = y + (fnt->texth - (currfont->ascent + currfont->descent))/2 + currfont->ascent;
702 			XftDrawStringUtf8(draw, color, currfont, x, texty, (XftChar8 *)text, len);
703 			x += ext.xOff;
704 		}
705 
706 		text = next;
707 	}
708 
709 	return textwidth;
710 }
711 
712 /* draw contents of notification item on item->pixmap */
713 static void
drawitem(struct Item * item)714 drawitem(struct Item *item)
715 {
716 	XftDraw *draw;
717 	int x, y;
718 	int xaligned, oldx;
719 
720 	x = config.padding_pixels;
721 	y = config.padding_pixels;
722 	item->pixmap = XCreatePixmap(dpy, item->win, item->w, item->h, depth);
723 	draw = XftDrawCreate(dpy, item->pixmap, visual, colormap);
724 
725 	/* draw background */
726 	XSetForeground(dpy, dc.gc, item->background.pixel);
727 	XFillRectangle(dpy, item->pixmap, dc.gc, 0, 0, item->w, item->h);
728 
729 	/* draw image */
730 	if (item->image) {
731 		imlib_context_set_image(item->image);
732 		imlib_context_set_drawable(item->pixmap);
733 		imlib_render_image_on_drawable((x + config.image_pixels - item->imgw) / 2,
734 		                                (y + config.image_pixels - item->imgh) / 2);
735 		imlib_free_image();
736 		x += config.image_pixels;
737 	}
738 
739 	/* draw text */
740 	oldx = x;
741 	switch (config.alignment) {
742 	case LeftAlignment:
743 		break;
744 	case CenterAlignment:
745 		xaligned = drawtext(&titlefnt, NULL, NULL, 0, 0, item->title);
746 		xaligned = x + (item->w - x - titlefnt.texth - xaligned) / 2;
747 		x = MAX(x, xaligned);
748 		break;
749 	case RightAlignment:
750 		xaligned = drawtext(&titlefnt, NULL, NULL, 0, 0, item->title);
751 		xaligned = x + (item->w - x - titlefnt.texth - xaligned);
752 		x = MAX(x, xaligned);
753 		break;
754 	}
755 	if (item->body)
756 		y = (item->h - config.leading_pixels) / 2 - titlefnt.texth;
757 	else
758 		y = (item->h - titlefnt.texth) / 2;
759 	drawtext(&titlefnt, draw, &item->foreground, x, y, item->title);
760 
761 	/* draw text body */
762 	if (item->body) {
763 		y += titlefnt.texth + config.leading_pixels;
764 		x = oldx;
765 		switch (config.alignment) {
766 		case LeftAlignment:
767 			break;
768 		case CenterAlignment:
769 			xaligned = drawtext(&bodyfnt, NULL, NULL, 0, 0, item->body);
770 			xaligned = x + (item->w - x - bodyfnt.texth - xaligned) / 2;
771 			x = MAX(x, xaligned);
772 			break;
773 		case RightAlignment:
774 			xaligned = drawtext(&bodyfnt, NULL, NULL, 0, 0, item->body);
775 			xaligned = x + (item->w - x - bodyfnt.texth - xaligned);
776 			x = MAX(x, xaligned);
777 			break;
778 		}
779 		drawtext(&bodyfnt, draw, &item->foreground, x, y, item->body);
780 	}
781 
782 	/* change border color */
783 	XSetWindowBorder(dpy, item->win, item->border.pixel);
784 
785 	XftDrawDestroy(draw);
786 }
787 
788 /* reset time of item */
789 static void
resettime(struct Item * item)790 resettime(struct Item *item)
791 {
792 	item->time = time(NULL);
793 }
794 
795 /* add item notification item and set its window and contents */
796 static void
additem(struct Queue * queue,struct Itemspec * itemspec)797 additem(struct Queue *queue, struct Itemspec *itemspec)
798 {
799 	struct Item *item;
800 	int titlew, bodyw;
801 	int w, h;
802 
803 	if ((item = malloc(sizeof *item)) == NULL)
804 		err(1, "malloc");
805 	item->next = NULL;
806 	item->title = strdup(itemspec->title);
807 	item->body = (itemspec->body) ? strdup(itemspec->body) : NULL;
808 	item->image = (itemspec->file) ? loadimage(itemspec->file, &item->imgw, &item->imgh) : NULL;
809 	item->tag = (itemspec->tag) ? strdup(itemspec->tag) : NULL;
810 	item->cmd = (itemspec->cmd) ? strdup(itemspec->cmd) : NULL;
811 	item->sec = itemspec->sec;
812 	if (!queue->head)
813 		queue->head = item;
814 	else
815 		queue->tail->next = item;
816 	item->prev = queue->tail;
817 	queue->tail = item;
818 
819 	/* allocate colors */
820 	if (!itemspec->background || ealloccolor(itemspec->background, &item->background, 0) == -1)
821 		item->background = dc.background;
822 	if (!itemspec->foreground || ealloccolor(itemspec->foreground, &item->foreground, 0) == -1)
823 		item->foreground = dc.foreground;
824 	if (!itemspec->border || ealloccolor(itemspec->border, &item->border, 0) == -1)
825 		item->border = dc.border;
826 
827 	/* compute notification height */
828 	if (item->body)
829 		h = titlefnt.texth + bodyfnt.texth + config.padding_pixels + config.leading_pixels;
830 	else
831 		h = titlefnt.texth + config.padding_pixels;
832 
833 	h = config.padding_pixels + ((item->image) ? MAX(h, config.image_pixels) : h);
834 	item->h = MAX(h, queue->h);
835 
836 	/* compute notification width */
837 	if (config.shrink) {
838 		titlew = drawtext(&titlefnt, NULL, NULL, 0, 0, item->title);
839 		bodyw = (item->body) ? drawtext(&bodyfnt, NULL, NULL, 0, 0, item->body) : 0;
840 		w = MAX(titlew, bodyw);
841 		if (item->image) {
842 			w += config.image_pixels + config.padding_pixels * 2;
843 		} else {
844 			w += config.padding_pixels * 2;
845 		}
846 		item->w = MIN(w, queue->w);
847 	} else {
848 		item->w = queue->w;
849 	}
850 
851 	/* call functions that set the item */
852 	createwindow(item);
853 	resettime(item);
854 	drawitem(item);
855 
856 	/* a new item was added to the queue, so the queue changed */
857 	queue->change = 1;
858 }
859 
860 /* delete item */
861 static void
delitem(struct Queue * queue,struct Item * item)862 delitem(struct Queue *queue, struct Item *item)
863 {
864 	free(item->title);
865 	if (item->body)
866 		free(item->body);
867 	XFreePixmap(dpy, item->pixmap);
868 	XDestroyWindow(dpy, item->win);
869 	if (item->prev)
870 		item->prev->next = item->next;
871 	else
872 		queue->head = item->next;
873 	if (item->next)
874 		item->next->prev = item->prev;
875 	else
876 		queue->tail = item->prev;
877 	free(item);
878 	queue->change = 1;
879 }
880 
881 /* print item's command to stdout */
882 static void
cmditem(struct Item * item)883 cmditem(struct Item *item)
884 {
885 	printf("%s\n", item->cmd);
886 }
887 
888 /* check the type of option given to a notification item */
889 static enum ItemOption
optiontype(const char * s)890 optiontype(const char *s)
891 {
892 	if (strncmp(s, "IMG:", 4) == 0)
893 		return IMG;
894 	if (strncmp(s, "BG:", 3) == 0)
895 		return BG;
896 	if (strncmp(s, "FG:", 3) == 0)
897 		return FG;
898 	if (strncmp(s, "BRD:", 4) == 0)
899 		return BRD;
900 	if (strncmp(s, "TAG:", 4) == 0)
901 		return TAG;
902 	if (strncmp(s, "CMD:", 4) == 0)
903 		return CMD;
904 	if (strncmp(s, "SEC:", 4) == 0)
905 		return SEC;
906 	return UNKNOWN;
907 }
908 
909 /* parse notification line */
910 static struct Itemspec *
parseline(char * s)911 parseline(char *s)
912 {
913 	enum ItemOption option;
914 	struct Itemspec *itemspec;
915 	const char *t;
916 	int n;
917 
918 	if ((itemspec = malloc(sizeof *itemspec)) == NULL)
919 		err(1, "malloc");
920 
921 	/* get the title */
922 	itemspec->title = strtok(s, "\t\n");
923 
924 	/* get the filename */
925 	itemspec->file = NULL;
926 	itemspec->foreground = NULL;
927 	itemspec->background = NULL;
928 	itemspec->border = NULL;
929 	itemspec->tag = NULL;
930 	itemspec->cmd = NULL;
931 	itemspec->sec = config.sec;
932 	while (itemspec->title && (option = optiontype(itemspec->title)) != UNKNOWN) {
933 		switch (option) {
934 		case IMG:
935 			itemspec->file = itemspec->title + 4;
936 			itemspec->title = strtok(NULL, "\t\n");
937 			break;
938 		case BG:
939 			itemspec->background = itemspec->title + 3;
940 			itemspec->title = strtok(NULL, "\t\n");
941 			break;
942 		case FG:
943 			itemspec->foreground = itemspec->title + 3;
944 			itemspec->title = strtok(NULL, "\t\n");
945 			break;
946 		case BRD:
947 			itemspec->border = itemspec->title + 4;
948 			itemspec->title = strtok(NULL, "\t\n");
949 			break;
950 		case TAG:
951 			itemspec->tag = itemspec->title + 4;
952 			itemspec->title = strtok(NULL, "\t\n");
953 			break;
954 		case CMD:
955 			itemspec->cmd = itemspec->title + 4;
956 			itemspec->title = strtok(NULL, "\t\n");
957 			break;
958 		case SEC:
959 			t = itemspec->title + 4;
960 			if (!getnum(&t, &n))
961 				itemspec->sec = n;
962 			itemspec->title = strtok(NULL, "\t\n");
963 			break;
964 		default:
965 			break;
966 		}
967 	}
968 
969 	/* get the body */
970 	itemspec->body = strtok(NULL, "\n");
971 	if (itemspec->body)
972 		while (*itemspec->body == '\t')
973 			itemspec->body++;
974 
975 	if (!itemspec->title)
976 		return NULL;
977 
978 	return itemspec;
979 }
980 
981 /* read x events */
982 static void
readevent(struct Queue * queue)983 readevent(struct Queue *queue)
984 {
985 	struct Item *item;
986 	XEvent ev;
987 
988 	while (XPending(dpy) && !XNextEvent(dpy, &ev)) {
989 		switch (ev.type) {
990 		case Expose:
991 			if (ev.xexpose.count == 0 && (item = getitem(queue, ev.xexpose.window)) != NULL)
992 				copypixmap(item);
993 			break;
994 		case ButtonPress:
995 			if ((item = getitem(queue, ev.xbutton.window)) == NULL)
996 				break;
997 			if ((ev.xbutton.button == config.actionbutton) && item->cmd)
998 				cmditem(item);
999 			delitem(queue, item);
1000 			break;
1001 		case MotionNotify:
1002 			if ((item = getitem(queue, ev.xmotion.window)) != NULL)
1003 				resettime(item);
1004 			break;
1005 		case ConfigureNotify:   /* monitor arrangement changed */
1006 			if (ev.xproperty.window == root) {
1007 				initmonitor();
1008 				queue->change = 1;
1009 			}
1010 			break;
1011 		}
1012 	}
1013 }
1014 
1015 /* check whether items have passed the time */
1016 static void
timeitems(struct Queue * queue)1017 timeitems(struct Queue *queue)
1018 {
1019 	struct Item *item;
1020 	struct Item *tmp;
1021 
1022 	item = queue->head;
1023 	while (item) {
1024 		tmp = item;
1025 		item = item->next;
1026 		if (tmp->sec && time(NULL) - tmp->time > tmp->sec) {
1027 			delitem(queue, tmp);
1028 		}
1029 	}
1030 }
1031 
1032 /* a notification was deleted or added, reorder the queue of notifications */
1033 static void
moveitems(struct Queue * queue)1034 moveitems(struct Queue *queue)
1035 {
1036 	struct Item *item;
1037 	int x, y;
1038 	int h = 0;
1039 
1040 	for (item = queue->head; item; item = item->next) {
1041 		x = queue->x + mon.x;
1042 		y = queue->y + mon.y;
1043 		switch (queue->gravity) {
1044 		case NorthWestGravity:
1045 			break;
1046 		case NorthGravity:
1047 			x += (mon.w - item->w) / 2 - config.border_pixels;
1048 			break;
1049 		case NorthEastGravity:
1050 			x += mon.w - item->w - config.border_pixels * 2;
1051 			break;
1052 		case WestGravity:
1053 			y += (mon.h - item->h) / 2 - config.border_pixels;
1054 			break;
1055 		case CenterGravity:
1056 			x += (mon.w - item->w) / 2 - config.border_pixels;
1057 			y += (mon.h - item->h) / 2 - config.border_pixels;
1058 			break;
1059 		case EastGravity:
1060 			x += mon.w - item->w - config.border_pixels * 2;
1061 			y += (mon.h - item->h) / 2 - config.border_pixels;
1062 			break;
1063 		case SouthWestGravity:
1064 			y += mon.h - item->h - config.border_pixels * 2;
1065 			break;
1066 		case SouthGravity:
1067 			x += (mon.w - item->w) / 2 - config.border_pixels;
1068 			y += mon.h - item->h - config.border_pixels * 2;
1069 			break;
1070 		case SouthEastGravity:
1071 			x += mon.w - item->w - config.border_pixels * 2;
1072 			y += mon.h - item->h - config.border_pixels * 2;
1073 			break;
1074 		}
1075 
1076 		if (queue->direction == DownWards)
1077 			y += h;
1078 		else
1079 			y -= h;
1080 		h += item->h + config.gap_pixels + config.border_pixels * 2;
1081 
1082 		XMoveWindow(dpy, item->win, x, y);
1083 		XMapWindow(dpy, item->win);
1084 		copypixmap(item);
1085 	}
1086 
1087 	queue->change = 0;
1088 }
1089 
1090 /* destroy all notification items of the given tag, or all items if tag is NULL */
1091 static void
cleanitems(struct Queue * queue,const char * tag)1092 cleanitems(struct Queue *queue, const char *tag)
1093 {
1094 	struct Item *item;
1095 	struct Item *tmp;
1096 
1097 	item = queue->head;
1098 	while (item) {
1099 		tmp = item;
1100 		item = item->next;
1101 		if (tag == NULL || (tmp->tag && strcmp(tmp->tag, tag) == 0)) {
1102 			delitem(queue, tmp);
1103 		}
1104 	}
1105 }
1106 
1107 /* clean up dc elements */
1108 static void
cleandc(void)1109 cleandc(void)
1110 {
1111 	XftColorFree(dpy, visual, colormap, &dc.background);
1112 	XftColorFree(dpy, visual, colormap, &dc.foreground);
1113 	XftColorFree(dpy, visual, colormap, &dc.border);
1114 
1115 	XFreeColormap(dpy, colormap);
1116 
1117 	XFreeGC(dpy, dc.gc);
1118 }
1119 
1120 /* xnotify: show notifications from stdin */
1121 int
main(int argc,char * argv[])1122 main(int argc, char *argv[])
1123 {
1124 	struct Itemspec *itemspec;
1125 	struct Queue *queue;    /* it contains the queue of notifications and their geometry */
1126 	struct pollfd pfd[2];   /* [2] for stdin and xfd, see poll(2) */
1127 	char buf[BUFSIZ];       /* buffer for stdin */
1128 	int timeout = -1;       /* maximum interval for poll(2) to complete */
1129 	int flags;              /* status flags for stdin */
1130 	int done = 0;           /* set to 1 when stdin reaches EOF */
1131 
1132 	/* open connection to server and set X variables */
1133 	if ((dpy = XOpenDisplay(NULL)) == NULL)
1134 		errx(1, "could not open display");
1135 	screen = DefaultScreen(dpy);
1136 	root = RootWindow(dpy, screen);
1137 	visual = DefaultVisual(dpy, screen);
1138 	depth = DefaultDepth(dpy, screen);
1139 	colormap = DefaultColormap(dpy, screen);
1140 	xfd = XConnectionNumber(dpy);
1141 	XrmInitialize();
1142 	if ((xrm = XResourceManagerString(dpy)) != NULL)
1143 		xdb = XrmGetStringDatabase(xrm);
1144 
1145 	/* get configuration */
1146 	getresources();
1147 	getoptions(argc, argv);
1148 
1149 	/* imlib2 stuff */
1150 	imlib_set_cache_size(2048 * 1024);
1151 	imlib_context_set_dither(1);
1152 	imlib_context_set_display(dpy);
1153 	imlib_context_set_visual(visual);
1154 	imlib_context_set_colormap(colormap);
1155 
1156 	/* init stuff */
1157 	initsignal();
1158 	initmonitor();
1159 	initdc();
1160 	initatoms();
1161 	initstructurenotify();
1162 
1163 	/* set up queue of notifications */
1164 	queue = setqueue();
1165 
1166 	/* Make stdin nonblocking */
1167 	if ((flags = fcntl(STDIN_FILENO, F_GETFL)) == -1)
1168 		err(1, "could not get status flags for stdin");
1169 	if (fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK) == -1)
1170 		err(1, "could not set status flags for stdin");
1171 
1172 	/* prepare the structure for poll(2) */
1173 	pfd[0].fd = STDIN_FILENO;
1174 	pfd[1].fd = xfd;
1175 	pfd[0].events = pfd[1].events = POLLIN;
1176 
1177 	/* run main loop */
1178 	for (;;) {
1179 		if (poll(pfd, 2, timeout) > 0) {
1180 			if (pfd[0].revents & POLLHUP) {
1181 				pfd[0].fd = -1;
1182 				done = 1;
1183 			}
1184 			if (pfd[0].revents & POLLIN) {
1185 				if (fgets(buf, sizeof buf, stdin) == NULL)
1186 					break;
1187 				if ((itemspec = parseline(buf)) != NULL) {
1188 					if (oflag) {
1189 						cleanitems(queue, NULL);
1190 					} else if (itemspec->tag) {
1191 						cleanitems(queue, itemspec->tag);
1192 					}
1193 					additem(queue, itemspec);
1194 				}
1195 			}
1196 			if (pfd[1].revents & POLLIN) {
1197 				readevent(queue);
1198 			}
1199 		}
1200 		if (usrflag) {
1201 			if (usrflag > 1 && queue->head)
1202 				cmditem(queue->head);
1203 			cleanitems(queue, NULL);
1204 			usrflag = 0;
1205 		}
1206 		timeitems(queue);
1207 		if (queue->change)
1208 			moveitems(queue);
1209 		timeout = (queue->head) ? 1000 : -1;
1210 		XFlush(dpy);
1211 
1212 		if (done && !queue->head)
1213 			break;
1214 	}
1215 
1216 	/* clean up stuff */
1217 	cleanitems(queue, NULL);
1218 	cleandc();
1219 	free(queue);
1220 
1221 	/* close connection to server */
1222 	XrmDestroyDatabase(xdb);
1223 	XCloseDisplay(dpy);
1224 
1225 	return 0;
1226 }
1227