herbe

git clone git://mattcarlson.org/repos/herbe.git
Log | Files | Refs

herbe.c (8868B)


      1 #include <X11/Xft/Xft.h>
      2 #include <X11/Xlib.h>
      3 #include <errno.h>
      4 #include <fcntl.h>
      5 #include <fcntl.h>
      6 #include <mqueue.h>
      7 #include <signal.h>
      8 #include <stdarg.h>
      9 #include <X11/Xresource.h>
     10 #include <stdio.h>
     11 #include <stdlib.h>
     12 #include <string.h>
     13 #include <sys/file.h>
     14 #include <sys/ipc.h>
     15 #include <sys/shm.h>
     16 #include <unistd.h>
     17 
     18 #include "config.h"
     19 
     20 #define EXIT_ACTION 0
     21 #define EXIT_FAIL 1
     22 #define EXIT_DISMISS 2
     23 
     24 #define XRES_STR(name)                                        \
     25 	if (XrmGetResource(db, "herbe." #name, "*", &type, &val)) \
     26 	name = val.addr
     27 #define XRES_INT(name)                                        \
     28 	if (XrmGetResource(db, "herbe." #name, "*", &type, &val)) \
     29 	name = strtoul(val.addr, 0, 10)
     30 
     31 Display *display;
     32 XftFont *font;
     33 Window window;
     34 int num_of_lines;
     35 char **lines;
     36 int exit_code = EXIT_DISMISS;
     37 
     38 struct mq_object {
     39 	pid_t pid;
     40 	long timestamp;
     41 	char  buffer[1024];
     42 };
     43 long lastTimestamp;
     44 
     45 static void die(const char *format, ...)
     46 {
     47 	va_list ap;
     48 	va_start(ap, format);
     49 	vfprintf(stderr, format, ap);
     50 	fprintf(stderr, "\n");
     51 	va_end(ap);
     52 	exit(EXIT_FAIL);
     53 }
     54 
     55 void read_y_offset(unsigned int **offset, int *id) {
     56 	int shm_id = shmget(8432, sizeof(unsigned int), IPC_CREAT | 0660);
     57 	if (shm_id == -1) die("shmget failed");
     58 
     59 	*offset = (unsigned int *)shmat(shm_id, 0, 0);
     60 	if (*offset == (unsigned int *)-1) die("shmat failed\n");
     61 	*id = shm_id;
     62 }
     63 
     64 void free_y_offset(int id) {
     65 	shmctl(id, IPC_RMID, NULL);
     66 }
     67 
     68 int get_max_len(char *string, XftFont *font, int max_text_width)
     69 {
     70 	int eol = strlen(string);
     71 	XGlyphInfo info;
     72 	XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
     73 
     74 	if (info.width > max_text_width)
     75 	{
     76 		eol = max_text_width / font->max_advance_width;
     77 		info.width = 0;
     78 
     79 		while (info.width < max_text_width)
     80 		{
     81 			eol++;
     82 			XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
     83 		}
     84 
     85 		eol--;
     86 	}
     87 
     88 	for (int i = 0; i < eol; i++)
     89 		if (string[i] == '\n')
     90 		{
     91 			string[i] = ' ';
     92 			return ++i;
     93 		}
     94 
     95 	if (info.width <= max_text_width)
     96 		return eol;
     97 
     98 	int temp = eol;
     99 
    100 	while (string[eol] != ' ' && eol)
    101 		--eol;
    102 
    103 	if (eol == 0)
    104 		return temp;
    105 	else
    106 		return ++eol;
    107 }
    108 
    109 void freeLines() {
    110 	if(lines) {
    111 		for (int i = 0; i < num_of_lines; i++)
    112 			free(lines[i]);
    113 		free(lines);
    114 	}
    115 }
    116 
    117 void constructLines(char* strList[], int numberOfStrings) {
    118 	freeLines();
    119 	int max_text_width = width - 2 * padding;
    120 	num_of_lines = 0;
    121 	int lines_size = 5;
    122 	lines = malloc(lines_size * sizeof(char *));
    123 	if (!lines)
    124 		die("malloc failed");
    125 
    126 	for (int i = 0; i < numberOfStrings; i++)
    127 	{
    128 		for (unsigned int eol = get_max_len(strList[i], font, max_text_width); eol; strList[i] += eol, num_of_lines++, eol = get_max_len(strList[i], font, max_text_width))
    129 		{
    130 			if (lines_size <= num_of_lines)
    131 			{
    132 				lines = realloc(lines, (lines_size += 5) * sizeof(char *));
    133 				if (!lines)
    134 					die("realloc failed");
    135 			}
    136 			lines[num_of_lines] = malloc((eol + 1) * sizeof(char));
    137 			if (!lines[num_of_lines])
    138 				die("malloc failed");
    139 
    140 			strncpy(lines[num_of_lines], strList[i], eol);
    141 			lines[num_of_lines][eol] = '\0';
    142 		}
    143 	}
    144 }
    145 
    146 void reload(union sigval sv);
    147 void readAllEvents(mqd_t mqd) {
    148 
    149 	struct sigevent event = {.sigev_notify=SIGEV_THREAD, .sigev_signo=SIGHUP, .sigev_value.sival_int=mqd, .sigev_notify_function=reload};
    150 	if(mq_notify(mqd, &event) == -1) {
    151 		perror("mq_notify failed");
    152 		exit(1);
    153 	}
    154 	struct mq_object object;
    155 	while(1) {
    156 		int ret = mq_receive(mqd, (char*)&object, sizeof(object), NULL);
    157 		if(ret==-1) {
    158 			if(errno == EAGAIN)
    159 				return;
    160 			perror("mq_receive");
    161 			exit(1);
    162 		}
    163 		if(object.timestamp && lastTimestamp > object.timestamp)
    164 			return;
    165 		if(object.timestamp)
    166 			lastTimestamp = object.timestamp;
    167 		char *buffer = object.buffer;
    168 
    169 		constructLines(&buffer, 1);
    170 		kill(object.pid, SIGTERM);
    171 	}
    172 }
    173 void reload(union sigval sv) {
    174 	// we've already timed out
    175 	if(alarm(duration) == 0)
    176 		return;
    177 	readAllEvents(sv.sival_int);
    178 	XEvent event;
    179 	event.type = Expose;
    180 	XSendEvent(display, window, 0, 0, &event);
    181 	XFlush(display);
    182 }
    183 
    184 void expire(int sig)
    185 {
    186 	XEvent event;
    187 	event.type = ButtonPress;
    188 	event.xbutton.button = (sig == SIGUSR2) ? (ACTION_BUTTON) : (DISMISS_BUTTON);
    189 	XSendEvent(display, window, 0, 0, &event);
    190 	XFlush(display);
    191 }
    192 
    193 void exitSuccess() {
    194        exit(0);
    195 }
    196 
    197 int main(int argc, char *argv[])
    198 {
    199 	if (argc == 1)
    200 	{
    201 		die("Usage: %s body", argv[0]);
    202 	}
    203 
    204 	const char* id =getenv("HERBE_ID");
    205 	mqd_t mqd=-1;
    206 	if(id) {
    207 		struct mq_attr attr = { .mq_maxmsg = 10, .mq_msgsize = sizeof(struct mq_object) };
    208 		mqd = mq_open(id, O_RDWR|O_CREAT|O_NONBLOCK, 0722, &attr);
    209 		if(mqd==-1){
    210 			perror("mq_open");
    211 			die("mq_open");
    212 		}
    213 		while (1) {
    214 			if(flock(mqd, LOCK_EX|LOCK_NB) == 0) {
    215 				// if we get the lock, register for events
    216 				break;
    217 			}
    218 			if(errno != EWOULDBLOCK) {
    219 				perror("flock");
    220 				exit(1);
    221 			}
    222 			// someone else is listening for events
    223 			char* ts_str = getenv("NOTIFICATION_ID");
    224 			lastTimestamp = ts_str?atol(ts_str):0;
    225 			struct mq_object object = {getpid(), lastTimestamp, {0}};
    226 			char *buffer=object.buffer;
    227 			for(int i=1;i<argc;i++) {
    228 				strcat(buffer, argv[i]);
    229 				strcat(buffer, "\n");
    230 			}
    231 			signal(SIGTERM, exitSuccess);
    232 			if(mq_send(mqd, (char*)&object, sizeof(object), 1)==-1) {
    233 				perror("mq_send");
    234 				exit(1);
    235 			}
    236 			signal(SIGALRM, SIG_IGN);
    237 			alarm(1);
    238 			pause();
    239 		}
    240 	}
    241 
    242 	struct sigaction act_expire, act_ignore;
    243 
    244 	act_expire.sa_handler = expire;
    245 	act_expire.sa_flags = SA_RESTART;
    246 	sigemptyset(&act_expire.sa_mask);
    247 
    248 	act_ignore.sa_handler = SIG_IGN;
    249 	act_ignore.sa_flags = 0;
    250 	sigemptyset(&act_ignore.sa_mask);
    251 
    252 	sigaction(SIGALRM, &act_expire, 0);
    253 	sigaction(SIGTERM, &act_expire, 0);
    254 	sigaction(SIGINT, &act_expire, 0);
    255 
    256 	sigaction(SIGUSR1, &act_ignore, 0);
    257 	sigaction(SIGUSR2, &act_ignore, 0);
    258 
    259 	if (!(display = XOpenDisplay(0)))
    260 		die("Cannot open display");
    261 
    262 	XrmInitialize();
    263 
    264 	char *res_man = XResourceManagerString(display);
    265 	XrmDatabase db = XrmGetStringDatabase(res_man);
    266 
    267 	char *type;
    268 	XrmValue val;
    269 
    270 	XRES_STR(background_color);
    271 	XRES_STR(border_color);
    272 	XRES_STR(font_color);
    273 	XRES_STR(font_pattern);
    274 
    275 	XRES_INT(line_spacing);
    276 	XRES_INT(padding);
    277 	XRES_INT(width);
    278 	XRES_INT(border_size);
    279 	XRES_INT(pos_x);
    280 	XRES_INT(pos_y);
    281 	XRES_INT(corner);
    282 	XRES_INT(duration);
    283 
    284 	int screen = DefaultScreen(display);
    285 	Visual *visual = DefaultVisual(display, screen);
    286 	Colormap colormap = DefaultColormap(display, screen);
    287 
    288 	int screen_width = DisplayWidth(display, screen);
    289 	int screen_height = DisplayHeight(display, screen);
    290 
    291 	XSetWindowAttributes attributes;
    292 	attributes.override_redirect = True;
    293 	XftColor color;
    294 	XftColorAllocName(display, visual, colormap, background_color, &color);
    295 	attributes.background_pixel = color.pixel;
    296 	XftColorAllocName(display, visual, colormap, border_color, &color);
    297 	attributes.border_pixel = color.pixel;
    298 	font = XftFontOpenName(display, screen, font_pattern);
    299 
    300 	constructLines(argv+1, argc-1);
    301 
    302 	int y_offset_id;
    303 	unsigned int *y_offset;
    304 	read_y_offset(&y_offset, &y_offset_id);
    305 
    306 	unsigned int text_height = font->ascent - font->descent;
    307 	unsigned int height = (num_of_lines - 1) * line_spacing + num_of_lines * text_height + 2 * padding;
    308 	unsigned int x = pos_x;
    309 	unsigned int y = pos_y + *y_offset;
    310 	unsigned int used_y_offset = (*y_offset) += height + padding;
    311 
    312 	if (corner == TOP_RIGHT || corner == BOTTOM_RIGHT)
    313 		x = screen_width - width - border_size * 2 - x;
    314 	if (corner == BOTTOM_LEFT || corner == BOTTOM_RIGHT)
    315 		y = screen_height - height - border_size * 2 - y;
    316 
    317 	window = XCreateWindow(display, RootWindow(display, screen), x, y, width, height, border_size, DefaultDepth(display, screen),
    318 						   CopyFromParent, visual, CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attributes);
    319 
    320 	XftDraw *draw = XftDrawCreate(display, window, visual, colormap);
    321 	XftColorAllocName(display, visual, colormap, font_color, &color);
    322 
    323 	XSelectInput(display, window, ExposureMask | ButtonPress);
    324 	XMapWindow(display, window);
    325 
    326 	sigaction(SIGUSR1, &act_expire, 0);
    327 	sigaction(SIGUSR2, &act_expire, 0);
    328 
    329 	if (duration != 0)
    330 		alarm(duration);
    331 
    332 	if(id) {
    333 		readAllEvents(mqd);
    334 	}
    335 
    336 	for (;;)
    337 	{
    338 		XEvent event;
    339 		XNextEvent(display, &event);
    340 
    341 		if (event.type == Expose)
    342 		{
    343 			XClearWindow(display, window);
    344 			for (int i = 0; i < num_of_lines; i++)
    345 				XftDrawStringUtf8(draw, &color, font, padding, line_spacing * i + text_height * (i + 1) + padding,
    346 								  (FcChar8 *)lines[i], strlen(lines[i]));
    347 		}
    348 		else if (event.type == ButtonPress)
    349 		{
    350 			if (event.xbutton.button == DISMISS_BUTTON)
    351 				break;
    352 			else if (event.xbutton.button == ACTION_BUTTON)
    353 			{
    354 				exit_code = EXIT_ACTION;
    355 				break;
    356 			}
    357 		}
    358 	}
    359 
    360 	if (used_y_offset == *y_offset) free_y_offset(y_offset_id);
    361 	freeLines();
    362 
    363 	XftDrawDestroy(draw);
    364 	XftColorFree(display, visual, colormap, &color);
    365 	XftFontClose(display, font);
    366 	XrmDestroyDatabase(db);
    367 	XCloseDisplay(display);
    368 
    369 	if(id) {
    370 		mq_close(mqd);
    371 	}
    372 
    373 	return exit_code;
    374 }