birde

Daemon-less notifications without D-Bus. Minimal and lightweight.
Log | Files | Refs | README | LICENSE

birde.c (6936B)


      1 #include <X11/Xlib.h>
      2 #include <X11/Xft/Xft.h>
      3 #include <X11/extensions/Xrandr.h>
      4 #include <X11/Xresource.h>
      5 #include <stdio.h>
      6 #include <stdlib.h>
      7 #include <signal.h>
      8 #include <unistd.h>
      9 #include <string.h>
     10 #include <stdarg.h>
     11 #include <fcntl.h>
     12 #include <sys/ipc.h>
     13 #include <sys/shm.h>
     14 
     15 #include "config.h"
     16 
     17 #define EXIT_ACTION 0
     18 #define EXIT_FAIL 1
     19 #define EXIT_DISMISS 2
     20 
     21 #define XRES_STR(name)                                        \
     22 	if (XrmGetResource(db, "birde." #name, "*", &type, &val)) \
     23 	name = val.addr
     24 #define XRES_INT(name)                                        \
     25 	if (XrmGetResource(db, "birde." #name, "*", &type, &val)) \
     26 	name = strtoul(val.addr, 0, 10)
     27 
     28 Display *display;
     29 Window window;
     30 int exit_code = EXIT_DISMISS;
     31 
     32 static void die(const char *format, ...)
     33 {
     34 	va_list ap;
     35 	va_start(ap, format);
     36 	vfprintf(stderr, format, ap);
     37 	fprintf(stderr, "\n");
     38 	va_end(ap);
     39 	exit(EXIT_FAIL);
     40 }
     41 
     42 int get_max_len(char *string, XftFont *font, int max_text_width)
     43 {
     44 	int eol = strlen(string);
     45 	XGlyphInfo info;
     46 	XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
     47 
     48 	if (info.width > max_text_width)
     49 	{
     50 		eol = max_text_width / font->max_advance_width;
     51 		info.width = 0;
     52 
     53 		while (info.width < max_text_width)
     54 		{
     55 			eol++;
     56 			XftTextExtentsUtf8(display, font, (FcChar8 *)string, eol, &info);
     57 		}
     58 
     59 		eol--;
     60 	}
     61 
     62 	for (int i = 0; i < eol; i++)
     63 		if (string[i] == '\n')
     64 		{
     65 			string[i] = ' ';
     66 			return ++i;
     67 		}
     68 
     69 	if (info.width <= max_text_width)
     70 		return eol;
     71 
     72 	int temp = eol;
     73 
     74 	while (string[eol] != ' ' && eol)
     75 		--eol;
     76 
     77 	if (eol == 0)
     78 		return temp;
     79 	else
     80 		return ++eol;
     81 }
     82 
     83 void expire(int sig)
     84 {
     85 	XEvent event;
     86 	event.type = ButtonPress;
     87 	event.xbutton.button = (sig == SIGUSR2) ? (ACTION_BUTTON) : (DISMISS_BUTTON);
     88 	XSendEvent(display, window, 0, 0, &event);
     89 	XFlush(display);
     90 }
     91 
     92 void read_y_offset(unsigned int **offset, int *id) {
     93     int shm_id = shmget(8432, sizeof(unsigned int), IPC_CREAT | 0660);
     94     if (shm_id == -1) die("shmget failed");
     95 
     96     *offset = (unsigned int *)shmat(shm_id, 0, 0);
     97     if (*offset == (unsigned int *)-1) die("shmat failed\n");
     98     *id = shm_id;
     99 }
    100 
    101 void free_y_offset(int id) {
    102     shmctl(id, IPC_RMID, NULL);
    103 }
    104 
    105 int main(int argc, char *argv[])
    106 {
    107 	if (argc == 1)
    108 		die("Usage: %s body", argv[0]);
    109 
    110 	struct sigaction act_expire, act_ignore;
    111 
    112 	act_expire.sa_handler = expire;
    113 	act_expire.sa_flags = SA_RESTART;
    114 	sigemptyset(&act_expire.sa_mask);
    115 
    116 	act_ignore.sa_handler = SIG_IGN;
    117 	act_ignore.sa_flags = 0;
    118 	sigemptyset(&act_ignore.sa_mask);
    119 
    120 	sigaction(SIGALRM, &act_expire, 0);
    121 	sigaction(SIGTERM, &act_expire, 0);
    122 	sigaction(SIGINT, &act_expire, 0);
    123 
    124 	sigaction(SIGUSR1, &act_ignore, 0);
    125 	sigaction(SIGUSR2, &act_ignore, 0);
    126 
    127 	if (!(display = XOpenDisplay(0)))
    128 		die("Cannot open display");
    129 
    130 	XrmInitialize();
    131 
    132 	char *res_man = XResourceManagerString(display);
    133 	XrmDatabase db = XrmGetStringDatabase(res_man);
    134 
    135 	char *type;
    136 	XrmValue val;
    137 
    138 	XRES_STR(background_color);
    139 	XRES_STR(border_color);
    140 	XRES_STR(font_color);
    141 	XRES_STR(font_pattern);
    142 
    143 	XRES_INT(line_spacing);
    144 	XRES_INT(padding);
    145 	XRES_INT(width);
    146 	XRES_INT(border_size);
    147 	XRES_INT(pos_x);
    148 	XRES_INT(pos_y);
    149 	XRES_INT(corner);
    150 	XRES_INT(duration);
    151 
    152 	int screen = DefaultScreen(display);
    153 	Visual *visual = DefaultVisual(display, screen);
    154 	Colormap colormap = DefaultColormap(display, screen);
    155 
    156 	int screen_x = 0;
    157 	int screen_y = 0;
    158 	int screen_width = DisplayWidth(display, screen);
    159 	int screen_height = DisplayHeight(display, screen);
    160 	if(use_primary_monitor) {
    161 		int nMonitors;
    162 		XRRMonitorInfo* info = XRRGetMonitors(display, RootWindow(display, screen), 1, &nMonitors);
    163 		for(int i = 0; i < nMonitors; i++) {
    164 			if(info[i].primary) {
    165 				screen_x = info[i].x;
    166 				screen_y = info[i].y;
    167 				screen_width = info[i].width;
    168 				screen_height = info[i].height;
    169 			}
    170 		}
    171 	}
    172 
    173 	XSetWindowAttributes attributes;
    174 	attributes.override_redirect = True;
    175 	XftColor color;
    176 	XftColorAllocName(display, visual, colormap, background_color, &color);
    177 	attributes.background_pixel = color.pixel;
    178 	XftColorAllocName(display, visual, colormap, border_color, &color);
    179 	attributes.border_pixel = color.pixel;
    180 
    181 	int num_of_lines = 0;
    182 	int max_text_width = width - 2 * padding;
    183 	int lines_size = 5;
    184 	char **lines = malloc(lines_size * sizeof(char *));
    185 	if (!lines)
    186 		die("malloc failed");
    187 
    188 	XftFont *font = XftFontOpenName(display, screen, font_pattern);
    189 
    190 	for (int i = 1; i < argc; i++)
    191 	{
    192 		for (unsigned int eol = get_max_len(argv[i], font, max_text_width); eol; argv[i] += eol, num_of_lines++, eol = get_max_len(argv[i], font, max_text_width))
    193 		{
    194 			if (lines_size <= num_of_lines)
    195 			{
    196 				lines = realloc(lines, (lines_size += 5) * sizeof(char *));
    197 				if (!lines)
    198 					die("realloc failed");
    199 			}
    200 
    201 			lines[num_of_lines] = malloc((eol + 1) * sizeof(char));
    202 			if (!lines[num_of_lines])
    203 				die("malloc failed");
    204 
    205 			strncpy(lines[num_of_lines], argv[i], eol);
    206 			lines[num_of_lines][eol] = '\0';
    207 		}
    208 	}
    209 
    210   int y_offset_id;
    211   unsigned int *y_offset;
    212   read_y_offset(&y_offset, &y_offset_id);
    213 
    214 	unsigned int text_height = font->ascent - font->descent;
    215 	unsigned int height = (num_of_lines - 1) * line_spacing + num_of_lines * text_height + 2 * padding;
    216 	unsigned int x = pos_x;
    217   unsigned int y = pos_y + *y_offset;
    218 
    219   unsigned int used_y_offset = (*y_offset) += height + padding;
    220 
    221 	if (corner == TOP_RIGHT || corner == BOTTOM_RIGHT)
    222 		x = screen_x + screen_width - width - border_size * 2 - x;
    223 
    224 	if (corner == BOTTOM_LEFT || corner == BOTTOM_RIGHT)
    225 		y = screen_y + screen_height - height - border_size * 2 - y;
    226 
    227 	window = XCreateWindow(display, RootWindow(display, screen), x, y, width, height, border_size, DefaultDepth(display, screen),
    228 						   CopyFromParent, visual, CWOverrideRedirect | CWBackPixel | CWBorderPixel, &attributes);
    229 
    230 	XftDraw *draw = XftDrawCreate(display, window, visual, colormap);
    231 	XftColorAllocName(display, visual, colormap, font_color, &color);
    232 
    233 	XSelectInput(display, window, ExposureMask | ButtonPress);
    234 	XMapWindow(display, window);
    235 
    236 	sigaction(SIGUSR1, &act_expire, 0);
    237 	sigaction(SIGUSR2, &act_expire, 0);
    238 
    239 	if (duration != 0)
    240 		alarm(duration);
    241 
    242 	for (;;)
    243 	{
    244 		XEvent event;
    245 		XNextEvent(display, &event);
    246 
    247 		if (event.type == Expose)
    248 		{
    249 			XClearWindow(display, window);
    250 			for (int i = 0; i < num_of_lines; i++)
    251 				XftDrawStringUtf8(draw, &color, font, padding, line_spacing * i + text_height * (i + 1) + padding,
    252 								  (FcChar8 *)lines[i], strlen(lines[i]));
    253 		}
    254 		else if (event.type == ButtonPress)
    255 		{
    256 			if (event.xbutton.button == DISMISS_BUTTON)
    257 				break;
    258 			else if (event.xbutton.button == ACTION_BUTTON)
    259 			{
    260 				exit_code = EXIT_ACTION;
    261 				break;
    262 			}
    263 		}
    264 	}
    265 
    266 
    267 	for (int i = 0; i < num_of_lines; i++)
    268 		free(lines[i]);
    269 
    270     if (used_y_offset == *y_offset) free_y_offset(y_offset_id);
    271 	free(lines);
    272 	XftDrawDestroy(draw);
    273 	XftColorFree(display, visual, colormap, &color);
    274 	XftFontClose(display, font);
    275 	XrmDestroyDatabase(db);
    276 	XCloseDisplay(display);
    277 
    278 	return exit_code;
    279 }