sxalert

Simple notification system for X
git clone git://mcdim.xyz/sxalert.git
Log | Files | Refs | README | LICENSE

sxalert.c (6563B)


      1 /* Simple X Alert v0.1
      2  * Simple notification program for X
      3  * GNU GPL v3.0+
      4  *
      5  * sxalert.c - main program
      6  */
      7 
      8 #include <stdlib.h>
      9 #include <stdio.h>
     10 #include <unistd.h>
     11 #include <string.h>
     12 #include <stdbool.h>
     13 #include <getopt.h>
     14 #include <time.h>
     15 #include <poll.h>
     16 #include <X11/Xft/Xft.h>
     17 #include <X11/extensions/Xrandr.h>
     18 
     19 #include "config.h"
     20 
     21 #define VERSION "v0.2"
     22 
     23 static inline void
     24 die(const char *msg, int code)
     25 {
     26 	fprintf(stderr, "%s\n", msg);
     27 	exit(code);
     28 }
     29 
     30 static inline void
     31 help(char *bin)
     32 {
     33 	printf("sxalert %s - Alert utility for X\n\n"
     34 	       "	-t text hex color (eg FFFFFF)\n"
     35 	       "	-g background hex color\n"
     36 	       "	-r border hex color\n"
     37 	       "	-b border width in pixels\n"
     38 	       "	-d duration in milliseconds\n"
     39 	       "	-s read from stdin\n\n"
     40 	       "	-v print version & exit\n"
     41 	       "	-h print help panel & exit\n\n"
     42 	       "Usage: %s [arguments] \"text\" \"text\" \"text\"\n",
     43 	       VERSION, bin);
     44 	exit(0);
     45 }
     46 
     47 static int
     48 hex2int(char *str)
     49 {
     50 	int num;
     51 	sscanf(str, "%x", &num);
     52 	return num;
     53 }
     54 
     55 int
     56 get_max(int arr[], int len)
     57 {
     58 	int max = -1;
     59 	for (int i=0; i<len; i++) {
     60 		if (arr[i] > max)
     61 			max = arr[i];
     62 	}
     63 
     64 	return max;
     65 }
     66 
     67 static int
     68 get_width(Display *dpy, XftFont *font, struct alert *a)
     69 {
     70 	XGlyphInfo ext;
     71 	int width = 0;
     72 	if (a->lines_len > 0) {
     73 		int width_lines[a->lines_len];
     74 		int j = 0;
     75 		for (int i = 0; i < a->lines_len; i++) {
     76 			XftTextExtentsUtf8(dpy, font,
     77 				           (XftChar8*)a->lines[i],
     78 					   strlen(a->lines[i]),
     79 					   &ext);
     80     			width_lines[i] = ext.xOff;
     81 			j++;
     82 		}
     83 
     84 		int text_width = get_max(width_lines, a->lines_len);
     85 		width = (a->text_x_padding * 2) + text_width;
     86 	}
     87 
     88 	if (width < a->min_width)
     89 		return a->min_width;
     90 	else if (width > a->max_width)
     91 		return a->max_width;
     92 	return width;
     93 }
     94 
     95 static int
     96 find_window(struct xwin *windows, int count, Window id)
     97 {
     98 	for (int i = 0; i < count; ++i) {
     99 		if (windows[i].win == id)
    100 			return i;
    101 	}
    102 
    103 	return -1;
    104 }
    105 
    106 static void
    107 write_text(Display *dpy, struct xwin *windows, int count,
    108            XftColor color, XftFont *font, struct alert *a)
    109 {
    110 	while (1) {
    111 		struct pollfd pfd = {
    112 		        .fd = ConnectionNumber(dpy),
    113 		        .events = POLLIN,
    114 		};
    115 		
    116 		int pending = XPending(dpy) > 0 ||
    117 			      poll(&pfd, 1, a->duration) > 0;
    118 
    119 		if (!pending)
    120 		        break;
    121 		
    122 		XEvent ev;
    123 		XNextEvent(dpy, &ev);
    124 	
    125 		int i = find_window(windows, count, ev.xany.window);
    126 		
    127 		if (i < 0)
    128 		        continue;
    129 		
    130 		if (ev.type == Expose) {
    131 			int spacing = a->text_height * 2;
    132 			
    133 			for (int line = 0; line < a->lines_len; ++line) {
    134 				XftDrawStringUtf8(windows[i].draw, &color,
    135 					font, a->text_x_padding, spacing,
    136 				        (XftChar8 *)a->lines[line],
    137 				        (int)strlen(a->lines[line])
    138 				);
    139 				
    140 				spacing += a->text_height * 2;
    141 			}
    142 		} else if (ev.type == ButtonPress) {
    143 			switch (ev.xbutton.button) {
    144 			case Button1: /* left click */
    145 				exit(0);
    146 				break;
    147 			case Button2: /* middle click */
    148 				exit(1);
    149 				break;
    150 			case Button3: /* right click */
    151 				exit(2);
    152 				break;
    153 			}
    154 		}
    155 	}
    156 }
    157 
    158 
    159 static void
    160 init_render(Display *dpy, struct xrender *r, struct alert *a)
    161 {
    162 	char text_color_pnd[8];
    163 	snprintf(text_color_pnd, 8, "#%s", a->text_color);
    164 
    165 	r->scr = DefaultScreen(dpy);
    166 	r->visual = DefaultVisual(dpy, r->scr);
    167 	r->cmap = DefaultColormap(dpy, r->scr);
    168 
    169 	r->font = XftFontOpenName(dpy, r->scr, a->font);
    170 	if (!r->font)
    171 		die("Cannot load font\n", -1);
    172 	if (!XftColorAllocName(dpy, r->visual, r->cmap,
    173 			       text_color_pnd, &(r->color)))
    174 		die("Cannot allocate Xft color\n", -1);
    175 }
    176 
    177 static void
    178 init_window(Display *dpy, struct xrender *r, struct xwin *w,
    179 	    struct alert *a, XRRMonitorInfo * monitor)
    180 {
    181 	w->width = get_width(dpy, r->font, a);
    182 	w->height = a->lines_len * (a->text_height * 2)
    183 		    + a->text_height;
    184 
    185         Screen *screen = ScreenOfDisplay(dpy, 0);
    186 	w->x = monitor->x + monitor->width - w->width - a->x_offset;
    187 	w->y = monitor->y + a->y_offset;
    188 
    189 
    190 	w->win = XCreateSimpleWindow(dpy, RootWindow(dpy, r->scr), w->x,
    191 					 a->y_offset, w->width, w->height,
    192 					 a->border_width,
    193 					 hex2int(a->border_color),
    194 					 hex2int(a->bg_color));
    195 	XSetWindowAttributes attr = {
    196         	.override_redirect = True
    197 	};
    198 	XChangeWindowAttributes(dpy, w->win, CWOverrideRedirect, &attr);
    199 	XSelectInput(dpy, w->win, ExposureMask | KeyPressMask);
    200 	XMapWindow(dpy, w->win);
    201 
    202 	w->draw = XftDrawCreate(dpy, w->win, r->visual, r->cmap);
    203 
    204 	XSelectInput(dpy, w->win, ExposureMask | ButtonPressMask);
    205 }
    206 
    207 static void
    208 draw(struct alert a)
    209 {
    210 	Display *dpy = XOpenDisplay(NULL);
    211 	if (!dpy)
    212 		die("Cannot open X11 display\n", -1);
    213 
    214 	struct xrender r;
    215 	init_render(dpy, &r, &a);
    216 
    217 	struct mon m;
    218 	m.monitor_count = 1;
    219 	m.monitors = XRRGetMonitors(dpy, RootWindow(dpy, r.scr),
    220 				    True, &m.monitor_count);
    221 	
    222 	struct xwin *w = calloc(m.monitor_count, sizeof(struct xwin));
    223 	if (!w)
    224 		die("Cannot allocate window array\n", -1);
    225 
    226 	for (int i = 0; i < m.monitor_count; i++) {
    227 		init_window(dpy, &r, &w[i], &a, &m.monitors[i]);
    228 
    229 	}
    230 
    231 	write_text(dpy, w, m.monitor_count, r.color, r.font, &a);
    232 }
    233 
    234 void
    235 read_stdin(struct alert *a, FILE *f)
    236 {
    237 	int buffer = 264;
    238 	char *p = malloc(buffer);
    239 
    240 	int i = 0;
    241 	while (fgets(p, buffer, f) != NULL) {
    242 		for (int j=0; j<264; j++) {
    243 			if (p[j] == '\n')
    244 				p[j] = '\0';
    245 		}
    246 
    247 		a->lines = realloc(a->lines, (i + 1)*sizeof(*a->lines));
    248 		if (a->lines == NULL) {
    249 			die("Cannot allocate memory\n", -1);
    250 		}
    251 		a->lines[i] = calloc(buffer, 1);
    252 		strncpy(a->lines[i], p, buffer);
    253 		i++;
    254 	}
    255 	a->lines_len = i;
    256 	free(p);
    257 }
    258 
    259 
    260 int
    261 main(int argc, char **argv)
    262 {
    263 	int c = 3;
    264 	extern char *optarg;
    265 
    266 	struct alert a = default_alert;
    267 
    268 	int s = 0;
    269 	while ((c = getopt(argc, argv, "d:b:t:g:r:svh")) != -1 ) {
    270 		switch (c) {
    271 		case 'v':
    272 			die(VERSION, EXIT_SUCCESS);
    273 		case 'h':
    274 			help(argv[0]);
    275 		case 'd':
    276 			a.duration=atoi(optarg);
    277 			if (a.duration == 0)
    278 				a.duration = -1;
    279 			break;
    280 		case 'b':
    281 			a.border_width=atoi(optarg);
    282 			break;
    283 		case 't':
    284 			strncpy(a.text_color, optarg, 7);
    285 			break;
    286 		case 'g':
    287 			strncpy(a.bg_color, optarg, 7);
    288 			break;
    289 		case 'r':
    290 			strncpy(a.border_color, optarg, 7);
    291 			break;
    292 		case 's':
    293 			s = 1;
    294 			break;
    295 		}
    296 	}
    297 
    298 	int cmd_lines_len = argc - optind;
    299 	char **lines = argv + optind;
    300 
    301 	if (!s) {
    302 		a.lines = lines;
    303 		a.lines_len = cmd_lines_len;
    304 		draw(a);
    305 		return 4;
    306 	}
    307 
    308 	FILE *f = tmpfile();
    309 	int cc;
    310 	
    311 	while ((cc = getchar()) != EOF)
    312 	    fputc(cc, f);
    313 	
    314 	for (int i=0; i<cmd_lines_len; i++)
    315 		fprintf(f, "%s\n", lines[i]);
    316 		//fputs(lines[i], f);
    317 	
    318 	a.lines = NULL;
    319 	a.lines_len = 0;
    320 	rewind(f);
    321 	read_stdin(&a, f);
    322 
    323 	draw(a);
    324 
    325 	return 4;
    326 }