sent

my sent build
git clone https://git.afify.dev/sent.git
Log | Files | Refs | README | LICENSE

sent.c (16317B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <sys/types.h>
      3 #include <arpa/inet.h>
      4 
      5 #include <errno.h>
      6 #include <fcntl.h>
      7 #include <math.h>
      8 #include <regex.h>
      9 #include <stdarg.h>
     10 #include <stdio.h>
     11 #include <stdint.h>
     12 #include <stdlib.h>
     13 #include <string.h>
     14 #include <unistd.h>
     15 #include <X11/keysym.h>
     16 #include <X11/XKBlib.h>
     17 #include <X11/Xatom.h>
     18 #include <X11/Xlib.h>
     19 #include <X11/Xutil.h>
     20 #include <X11/Xft/Xft.h>
     21 
     22 #include "arg.h"
     23 #include "util.h"
     24 #include "drw.h"
     25 
     26 char *argv0;
     27 int use_inverted_colors = 0;
     28 
     29 /* macros */
     30 #define LEN(a)         (sizeof(a) / sizeof(a)[0])
     31 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
     32 #define MAXFONTSTRLEN  128
     33 
     34 typedef enum {
     35 	NONE = 0,
     36 	SCALED = 1,
     37 } imgstate;
     38 
     39 typedef struct {
     40 	unsigned char *buf;
     41 	unsigned int bufwidth, bufheight;
     42 	imgstate state;
     43 	XImage *ximg;
     44 	int numpasses;
     45 } Image;
     46 
     47 typedef struct {
     48 	char *regex;
     49 	char *bin;
     50 } Filter;
     51 
     52 typedef struct {
     53 	unsigned int linecount;
     54 	char **lines;
     55 	Image *img;
     56 	char *embed;
     57 } Slide;
     58 
     59 /* Purely graphic info */
     60 typedef struct {
     61 	Display *dpy;
     62 	Window win;
     63 	Atom wmdeletewin, netwmname;
     64 	Visual *vis;
     65 	XSetWindowAttributes attrs;
     66 	int scr;
     67 	int w, h;
     68 	int uw, uh; /* usable dimensions for drawing text and images */
     69 } XWindow;
     70 
     71 typedef union {
     72 	int i;
     73 	unsigned int ui;
     74 	float f;
     75 	const void *v;
     76 } Arg;
     77 
     78 typedef struct {
     79 	unsigned int b;
     80 	void (*func)(const Arg *);
     81 	const Arg arg;
     82 } Mousekey;
     83 
     84 typedef struct {
     85 	KeySym keysym;
     86 	void (*func)(const Arg *);
     87 	const Arg arg;
     88 } Shortcut;
     89 
     90 static void fffree(Image *img);
     91 static void ffload(Slide *s);
     92 static void ffprepare(Image *img);
     93 static void ffscale(Image *img);
     94 static void ffdraw(Image *img);
     95 
     96 static void getfontsize(Slide *s, unsigned int *width, unsigned int *height);
     97 static void cleanup(int slidesonly);
     98 static void reload(const Arg *arg);
     99 static void load(FILE *fp);
    100 static void advance(const Arg *arg);
    101 static void quit(const Arg *arg);
    102 static void resize(int width, int height);
    103 static void run();
    104 static void usage();
    105 static void xdraw();
    106 static void xhints();
    107 static void xinit();
    108 static void xloadfonts();
    109 static void togglescm();
    110 
    111 static void bpress(XEvent *);
    112 static void cmessage(XEvent *);
    113 static void expose(XEvent *);
    114 static void kpress(XEvent *);
    115 static void configure(XEvent *);
    116 
    117 /* config.h for applying patches and the configuration. */
    118 #include "config.h"
    119 
    120 /* Globals */
    121 static const char *fname = NULL;
    122 static Slide *slides = NULL;
    123 static int idx = 0;
    124 static int slidecount = 0;
    125 static XWindow xw;
    126 static Drw *d = NULL;
    127 static Clr *sc;
    128 static Fnt *fonts[NUMFONTSCALES];
    129 static int running = 1;
    130 
    131 static void (*handler[LASTEvent])(XEvent *) = {
    132 	[ButtonPress] = bpress,
    133 	[ClientMessage] = cmessage,
    134 	[ConfigureNotify] = configure,
    135 	[Expose] = expose,
    136 	[KeyPress] = kpress,
    137 };
    138 
    139 int
    140 filter(int fd, const char *cmd)
    141 {
    142 	int fds[2];
    143 
    144 	if (pipe(fds) < 0)
    145 		die("sent: Unable to create pipe:");
    146 
    147 	switch (fork()) {
    148 	case -1:
    149 		die("sent: Unable to fork:");
    150 	case 0:
    151 		dup2(fd, 0);
    152 		dup2(fds[1], 1);
    153 		close(fds[0]);
    154 		close(fds[1]);
    155 		execlp("sh", "sh", "-c", cmd, (char *)0);
    156 		fprintf(stderr, "sent: execlp sh -c '%s': %s\n", cmd, strerror(errno));
    157 		_exit(1);
    158 	}
    159 	close(fds[1]);
    160 	return fds[0];
    161 }
    162 
    163 void
    164 fffree(Image *img)
    165 {
    166 	free(img->buf);
    167 	if (img->ximg)
    168 		XDestroyImage(img->ximg);
    169 	free(img);
    170 }
    171 
    172 void
    173 ffload(Slide *s)
    174 {
    175 	uint32_t y, x;
    176 	uint16_t *row;
    177 	uint8_t opac, fg_r, fg_g, fg_b, bg_r, bg_g, bg_b;
    178 	size_t rowlen, off, nbytes, i;
    179 	ssize_t count;
    180 	unsigned char hdr[16];
    181 	char *bin = NULL;
    182 	char *filename;
    183 	regex_t regex;
    184 	int fdin, fdout;
    185 
    186 	if (s->img || !(filename = s->embed) || !s->embed[0])
    187 		return; /* already done */
    188 
    189 	for (i = 0; i < LEN(filters); i++) {
    190 		if (regcomp(&regex, filters[i].regex,
    191 		            REG_NOSUB | REG_EXTENDED | REG_ICASE)) {
    192 			fprintf(stderr, "sent: Invalid regex '%s'\n", filters[i].regex);
    193 			continue;
    194 		}
    195 		if (!regexec(&regex, filename, 0, NULL, 0)) {
    196 			bin = filters[i].bin;
    197 			regfree(&regex);
    198 			break;
    199 		}
    200 		regfree(&regex);
    201 	}
    202 	if (!bin)
    203 		die("sent: Unable to find matching filter for '%s'", filename);
    204 
    205 	if ((fdin = open(filename, O_RDONLY)) < 0)
    206 		die("sent: Unable to open '%s':", filename);
    207 
    208 	if ((fdout = filter(fdin, bin)) < 0)
    209 		die("sent: Unable to filter '%s':", filename);
    210 	close(fdin);
    211 
    212 	if (read(fdout, hdr, 16) != 16)
    213 		die("sent: Unable to read filtered file '%s':", filename);
    214 	if (memcmp("farbfeld", hdr, 8))
    215 		die("sent: Filtered file '%s' has no valid farbfeld header", filename);
    216 
    217 	s->img = ecalloc(1, sizeof(Image));
    218 	s->img->bufwidth = ntohl(*(uint32_t *)&hdr[8]);
    219 	s->img->bufheight = ntohl(*(uint32_t *)&hdr[12]);
    220 
    221 	if (s->img->buf)
    222 		free(s->img->buf);
    223 	/* internally the image is stored in 888 format */
    224 	s->img->buf = ecalloc(s->img->bufwidth * s->img->bufheight, strlen("888"));
    225 
    226 	/* scratch buffer to read row by row */
    227 	rowlen = s->img->bufwidth * 2 * strlen("RGBA");
    228 	row = ecalloc(1, rowlen);
    229 
    230 	/* extract window background color channels for transparency */
    231 	bg_r = (sc[ColBg].pixel >> 16) % 256;
    232 	bg_g = (sc[ColBg].pixel >>  8) % 256;
    233 	bg_b = (sc[ColBg].pixel >>  0) % 256;
    234 
    235 	for (off = 0, y = 0; y < s->img->bufheight; y++) {
    236 		nbytes = 0;
    237 		while (nbytes < rowlen) {
    238 			count = read(fdout, (char *)row + nbytes, rowlen - nbytes);
    239 			if (count < 0)
    240 				die("sent: Unable to read from pipe:");
    241 			nbytes += count;
    242 		}
    243 		for (x = 0; x < rowlen / 2; x += 4) {
    244 			fg_r = ntohs(row[x + 0]) / 257;
    245 			fg_g = ntohs(row[x + 1]) / 257;
    246 			fg_b = ntohs(row[x + 2]) / 257;
    247 			opac = ntohs(row[x + 3]) / 257;
    248 			/* blend opaque part of image data with window background color to
    249 			 * emulate transparency */
    250 			s->img->buf[off++] = (fg_r * opac + bg_r * (255 - opac)) / 255;
    251 			s->img->buf[off++] = (fg_g * opac + bg_g * (255 - opac)) / 255;
    252 			s->img->buf[off++] = (fg_b * opac + bg_b * (255 - opac)) / 255;
    253 		}
    254 	}
    255 
    256 	free(row);
    257 	close(fdout);
    258 }
    259 
    260 void
    261 ffprepare(Image *img)
    262 {
    263 	int depth = DefaultDepth(xw.dpy, xw.scr);
    264 	int width = xw.uw;
    265 	int height = xw.uh;
    266 
    267 	if (xw.uw * img->bufheight > xw.uh * img->bufwidth)
    268 		width = img->bufwidth * xw.uh / img->bufheight;
    269 	else
    270 		height = img->bufheight * xw.uw / img->bufwidth;
    271 
    272 	if (depth < 24)
    273 		die("sent: Display color depths < 24 not supported");
    274 
    275 	if (!(img->ximg = XCreateImage(xw.dpy, CopyFromParent, depth, ZPixmap, 0,
    276 	                               NULL, width, height, 32, 0)))
    277 		die("sent: Unable to create XImage");
    278 
    279 	img->ximg->data = ecalloc(height, img->ximg->bytes_per_line);
    280 	if (!XInitImage(img->ximg))
    281 		die("sent: Unable to initiate XImage");
    282 
    283 	ffscale(img);
    284 	img->state |= SCALED;
    285 }
    286 
    287 void
    288 ffscale(Image *img)
    289 {
    290 	unsigned int x, y;
    291 	unsigned int width = img->ximg->width;
    292 	unsigned int height = img->ximg->height;
    293 	char* newBuf = img->ximg->data;
    294 	unsigned char* ibuf;
    295 	unsigned int jdy = img->ximg->bytes_per_line / 4 - width;
    296 	unsigned int dx = (img->bufwidth << 10) / width;
    297 
    298 	for (y = 0; y < height; y++) {
    299 		unsigned int bufx = img->bufwidth / width;
    300 		ibuf = &img->buf[y * img->bufheight / height * img->bufwidth * 3];
    301 
    302 		for (x = 0; x < width; x++) {
    303 			*newBuf++ = (ibuf[(bufx >> 10)*3+2]);
    304 			*newBuf++ = (ibuf[(bufx >> 10)*3+1]);
    305 			*newBuf++ = (ibuf[(bufx >> 10)*3+0]);
    306 			newBuf++;
    307 			bufx += dx;
    308 		}
    309 		newBuf += jdy;
    310 	}
    311 }
    312 
    313 void
    314 ffdraw(Image *img)
    315 {
    316 	int xoffset = (xw.w - img->ximg->width) / 2;
    317 	int yoffset = (xw.h - img->ximg->height) / 2;
    318 	XPutImage(xw.dpy, xw.win, d->gc, img->ximg, 0, 0,
    319 	          xoffset, yoffset, img->ximg->width, img->ximg->height);
    320 	XFlush(xw.dpy);
    321 }
    322 
    323 void
    324 getfontsize(Slide *s, unsigned int *width, unsigned int *height)
    325 {
    326 	int i, j;
    327 	unsigned int curw, newmax;
    328 	float lfac = linespacing * (s->linecount - 1) + 1;
    329 
    330 	/* fit height */
    331 	for (j = NUMFONTSCALES - 1; j >= 0; j--)
    332 		if (fonts[j]->h * lfac <= xw.uh)
    333 			break;
    334 	LIMIT(j, 0, NUMFONTSCALES - 1);
    335 	drw_setfontset(d, fonts[j]);
    336 
    337 	/* fit width */
    338 	*width = 0;
    339 	for (i = 0; i < s->linecount; i++) {
    340 		curw = drw_fontset_getwidth(d, s->lines[i]);
    341 		newmax = (curw >= *width);
    342 		while (j > 0 && curw > xw.uw) {
    343 			drw_setfontset(d, fonts[--j]);
    344 			curw = drw_fontset_getwidth(d, s->lines[i]);
    345 		}
    346 		if (newmax)
    347 			*width = curw;
    348 	}
    349 	*height = fonts[j]->h * lfac;
    350 }
    351 
    352 void
    353 cleanup(int slidesonly)
    354 {
    355 	unsigned int i, j;
    356 
    357 	if (!slidesonly) {
    358 		for (i = 0; i < NUMFONTSCALES; i++)
    359 			drw_fontset_free(fonts[i]);
    360 		free(sc);
    361 		drw_free(d);
    362 
    363 		XDestroyWindow(xw.dpy, xw.win);
    364 		XSync(xw.dpy, False);
    365 		XCloseDisplay(xw.dpy);
    366 	}
    367 
    368 	if (slides) {
    369 		for (i = 0; i < slidecount; i++) {
    370 			for (j = 0; j < slides[i].linecount; j++)
    371 				free(slides[i].lines[j]);
    372 			free(slides[i].lines);
    373 			if (slides[i].img)
    374 				fffree(slides[i].img);
    375 		}
    376 		if (!slidesonly) {
    377 			free(slides);
    378 			slides = NULL;
    379 		}
    380 	}
    381 }
    382 
    383 void
    384 reload(const Arg *arg)
    385 {
    386 	FILE *fp = NULL;
    387 	unsigned int i;
    388 
    389 	if (!fname) {
    390 		fprintf(stderr, "sent: Cannot reload from stdin. Use a file!\n");
    391 		return;
    392 	}
    393 
    394 	cleanup(1);
    395 	slidecount = 0;
    396 
    397 	if (!(fp = fopen(fname, "r")))
    398 		die("sent: Unable to open '%s' for reading:", fname);
    399 	load(fp);
    400 	fclose(fp);
    401 
    402 	LIMIT(idx, 0, slidecount-1);
    403 	for (i = 0; i < slidecount; i++)
    404 		ffload(&slides[i]);
    405 	xdraw();
    406 }
    407 
    408 void
    409 load(FILE *fp)
    410 {
    411 	static size_t size = 0;
    412 	size_t blen, maxlines;
    413 	char buf[BUFSIZ], *p;
    414 	Slide *s;
    415 
    416 	/* read each line from fp and add it to the item list */
    417 	while (1) {
    418 		/* eat consecutive empty lines */
    419 		while ((p = fgets(buf, sizeof(buf), fp)))
    420 			if (strcmp(buf, "\n") != 0 && buf[0] != '#')
    421 				break;
    422 		if (!p)
    423 			break;
    424 
    425 		if ((slidecount+1) * sizeof(*slides) >= size)
    426 			if (!(slides = realloc(slides, (size += BUFSIZ))))
    427 				die("sent: Unable to reallocate %u bytes:", size);
    428 
    429 		/* read one slide */
    430 		maxlines = 0;
    431 		memset((s = &slides[slidecount]), 0, sizeof(Slide));
    432 		do {
    433 			/* if there's a leading null, we can't do blen-1 */
    434 			if (buf[0] == '\0')
    435 				continue;
    436 
    437 			if (buf[0] == '#')
    438 				continue;
    439 
    440 			/* grow lines array */
    441 			if (s->linecount >= maxlines) {
    442 				maxlines = 2 * s->linecount + 1;
    443 				if (!(s->lines = realloc(s->lines, maxlines * sizeof(s->lines[0]))))
    444 					die("sent: Unable to reallocate %u bytes:", maxlines * sizeof(s->lines[0]));
    445 			}
    446 
    447 			blen = strlen(buf);
    448 			if (!(s->lines[s->linecount] = strdup(buf)))
    449 				die("sent: Unable to strdup:");
    450 			if (s->lines[s->linecount][blen-1] == '\n')
    451 				s->lines[s->linecount][blen-1] = '\0';
    452 
    453 			/* mark as image slide if first line of a slide starts with @ */
    454 			if (s->linecount == 0 && s->lines[0][0] == '@')
    455 				s->embed = &s->lines[0][1];
    456 
    457 			if (s->lines[s->linecount][0] == '\\')
    458 				memmove(s->lines[s->linecount], &s->lines[s->linecount][1], blen);
    459 			s->linecount++;
    460 		} while ((p = fgets(buf, sizeof(buf), fp)) && strcmp(buf, "\n") != 0);
    461 
    462 		slidecount++;
    463 		if (!p)
    464 			break;
    465 	}
    466 
    467 	if (!slidecount)
    468 		die("sent: No slides in file");
    469 }
    470 
    471 void
    472 advance(const Arg *arg)
    473 {
    474 	int new_idx = idx + arg->i;
    475 	LIMIT(new_idx, 0, slidecount-1);
    476 	if (new_idx != idx) {
    477 		if (slides[idx].img)
    478 			slides[idx].img->state &= ~SCALED;
    479 		idx = new_idx;
    480 		xdraw();
    481 	}
    482 }
    483 
    484 void
    485 quit(const Arg *arg)
    486 {
    487 	running = 0;
    488 }
    489 
    490 void
    491 resize(int width, int height)
    492 {
    493 	xw.w = width;
    494 	xw.h = height;
    495 	xw.uw = usablewidth * width;
    496 	xw.uh = usableheight * height;
    497 	drw_resize(d, width, height);
    498 }
    499 
    500 void
    501 run()
    502 {
    503 	XEvent ev;
    504 
    505 	/* Waiting for window mapping */
    506 	while (1) {
    507 		XNextEvent(xw.dpy, &ev);
    508 		if (ev.type == ConfigureNotify) {
    509 			resize(ev.xconfigure.width, ev.xconfigure.height);
    510 		} else if (ev.type == MapNotify) {
    511 			break;
    512 		}
    513 	}
    514 
    515 	while (running) {
    516 		XNextEvent(xw.dpy, &ev);
    517 		if (handler[ev.type])
    518 			(handler[ev.type])(&ev);
    519 	}
    520 }
    521 
    522 void
    523 xdraw()
    524 {
    525 	unsigned int height, width, i;
    526 	Image *im = slides[idx].img;
    527 
    528 	getfontsize(&slides[idx], &width, &height);
    529 	XClearWindow(xw.dpy, xw.win);
    530 
    531 	if (!im) {
    532 		drw_rect(d, 0, 0, xw.w, xw.h, 1, 1);
    533 		for (i = 0; i < slides[idx].linecount; i++)
    534 			drw_text(d,
    535 			         (xw.w - width) / 2,
    536 			         (xw.h - height) / 2 + i * linespacing * d->fonts->h,
    537 			         width,
    538 			         d->fonts->h,
    539 			         0,
    540 			         slides[idx].lines[i],
    541 			         0);
    542 		if (idx != 0 && progressheight != 0) {
    543 			drw_rect(d,
    544 			         0, xw.h - progressheight,
    545 			         (xw.w * idx)/(slidecount - 1), progressheight,
    546 			         1, 0);
    547 		}
    548 		drw_map(d, xw.win, 0, 0, xw.w, xw.h);
    549 	} else {
    550 		if (!(im->state & SCALED))
    551 			ffprepare(im);
    552 		ffdraw(im);
    553 	}
    554 }
    555 
    556 void
    557 xhints()
    558 {
    559 	XClassHint class = {.res_name = "sent", .res_class = "presenter"};
    560 	XWMHints wm = {.flags = InputHint, .input = True};
    561 	XSizeHints *sizeh = NULL;
    562 
    563 	if (!(sizeh = XAllocSizeHints()))
    564 		die("sent: Unable to allocate size hints");
    565 
    566 	sizeh->flags = PSize;
    567 	sizeh->height = xw.h;
    568 	sizeh->width = xw.w;
    569 
    570 	XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, &class);
    571 	XFree(sizeh);
    572 }
    573 
    574 void
    575 xinit()
    576 {
    577 	XTextProperty prop;
    578 	unsigned int i;
    579 
    580 	if (!(xw.dpy = XOpenDisplay(NULL)))
    581 		die("sent: Unable to open display");
    582 	xw.scr = XDefaultScreen(xw.dpy);
    583 	xw.vis = XDefaultVisual(xw.dpy, xw.scr);
    584 	resize(DisplayWidth(xw.dpy, xw.scr), DisplayHeight(xw.dpy, xw.scr));
    585 
    586 	xw.attrs.bit_gravity = CenterGravity;
    587 	xw.attrs.event_mask = KeyPressMask | ExposureMask | StructureNotifyMask |
    588 	                      ButtonMotionMask | ButtonPressMask;
    589 
    590 	xw.win = XCreateWindow(xw.dpy, XRootWindow(xw.dpy, xw.scr), 0, 0,
    591 	                       xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr),
    592 	                       InputOutput, xw.vis, CWBitGravity | CWEventMask,
    593 	                       &xw.attrs);
    594 
    595 	xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
    596 	xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
    597 	XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
    598 
    599 	if (!(d = drw_create(xw.dpy, xw.scr, xw.win, xw.w, xw.h)))
    600 		die("sent: Unable to create drawing context");
    601 	//sc = drw_scm_create(d, colors, 2);
    602 	if (use_inverted_colors) {
    603 		sc = drw_scm_create(d, inverted_colors, 2);
    604 	} else {
    605 		sc = drw_scm_create(d, colors, 2);
    606 	}
    607 	drw_setscheme(d, sc);
    608 	XSetWindowBackground(xw.dpy, xw.win, sc[ColBg].pixel);
    609 
    610 	xloadfonts();
    611 	for (i = 0; i < slidecount; i++)
    612 		ffload(&slides[i]);
    613 
    614 	XStringListToTextProperty(&argv0, 1, &prop);
    615 	XSetWMName(xw.dpy, xw.win, &prop);
    616 	XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
    617 	XFree(prop.value);
    618 	XMapWindow(xw.dpy, xw.win);
    619 	xhints();
    620 	XSync(xw.dpy, False);
    621 }
    622 
    623 void
    624 togglescm()
    625 {
    626 	if (use_inverted_colors) {
    627 		free(sc);
    628 		sc = drw_scm_create(d, colors, 2);
    629 		use_inverted_colors = 0;
    630 	} else {
    631 		sc = drw_scm_create(d, inverted_colors, 2);
    632 		use_inverted_colors = 1;
    633 	}
    634 	drw_setscheme(d, sc);
    635 	XSetWindowBackground(xw.dpy, xw.win, sc[ColBg].pixel);
    636 	xdraw();
    637 }
    638 
    639 void
    640 xloadfonts()
    641 {
    642 	int i, j;
    643 	char *fstrs[LEN(fontfallbacks)];
    644 
    645 	for (j = 0; j < LEN(fontfallbacks); j++) {
    646 		fstrs[j] = ecalloc(1, MAXFONTSTRLEN);
    647 	}
    648 
    649 	for (i = 0; i < NUMFONTSCALES; i++) {
    650 		for (j = 0; j < LEN(fontfallbacks); j++) {
    651 			if (MAXFONTSTRLEN < snprintf(fstrs[j], MAXFONTSTRLEN, "%s:size=%d", fontfallbacks[j], FONTSZ(i)))
    652 				die("sent: Font string too long");
    653 		}
    654 		if (!(fonts[i] = drw_fontset_create(d, (const char**)fstrs, LEN(fstrs))))
    655 			die("sent: Unable to load any font for size %d", FONTSZ(i));
    656 	}
    657 
    658 	for (j = 0; j < LEN(fontfallbacks); j++)
    659 		if (fstrs[j])
    660 			free(fstrs[j]);
    661 }
    662 
    663 void
    664 bpress(XEvent *e)
    665 {
    666 	unsigned int i;
    667 
    668 	for (i = 0; i < LEN(mshortcuts); i++)
    669 		if (e->xbutton.button == mshortcuts[i].b && mshortcuts[i].func)
    670 			mshortcuts[i].func(&(mshortcuts[i].arg));
    671 }
    672 
    673 void
    674 cmessage(XEvent *e)
    675 {
    676 	if (e->xclient.data.l[0] == xw.wmdeletewin)
    677 		running = 0;
    678 }
    679 
    680 void
    681 expose(XEvent *e)
    682 {
    683 	if (0 == e->xexpose.count)
    684 		xdraw();
    685 }
    686 
    687 void
    688 kpress(XEvent *e)
    689 {
    690 	unsigned int i;
    691 	KeySym sym;
    692 
    693 	sym = XkbKeycodeToKeysym(xw.dpy, (KeyCode)e->xkey.keycode, 0, 0);
    694 	for (i = 0; i < LEN(shortcuts); i++)
    695 		if (sym == shortcuts[i].keysym && shortcuts[i].func)
    696 			shortcuts[i].func(&(shortcuts[i].arg));
    697 }
    698 
    699 void
    700 configure(XEvent *e)
    701 {
    702 	resize(e->xconfigure.width, e->xconfigure.height);
    703 	if (slides[idx].img)
    704 		slides[idx].img->state &= ~SCALED;
    705 	xdraw();
    706 }
    707 
    708 void
    709 usage()
    710 {
    711 	die("usage: %s [file]", argv0);
    712 }
    713 
    714 int
    715 main(int argc, char *argv[])
    716 {
    717 	FILE *fp = NULL;
    718 
    719 	ARGBEGIN {
    720 	case 'v':
    721 		fprintf(stderr, "sent-"VERSION"\n");
    722 		return 0;
    723 	case 'i':
    724 		use_inverted_colors = 1;
    725 		break;
    726 	default:
    727 		usage();
    728 	} ARGEND
    729 
    730 	if (!argv[0] || !strcmp(argv[0], "-"))
    731 		fp = stdin;
    732 	else if (!(fp = fopen(fname = argv[0], "r")))
    733 		die("sent: Unable to open '%s' for reading:", fname);
    734 	load(fp);
    735 	fclose(fp);
    736 
    737 	xinit();
    738 	run();
    739 
    740 	cleanup(0);
    741 	return 0;
    742 }