sent.c - sent - simple plaintext presentation tool | |
git clone git://git.suckless.org/sent | |
Log | |
Files | |
Refs | |
README | |
LICENSE | |
--- | |
sent.c (15670B) | |
--- | |
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 | |
28 /* macros */ | |
29 #define LEN(a) (sizeof(a) / sizeof(a)[0]) | |
30 #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) | |
31 #define MAXFONTSTRLEN 128 | |
32 | |
33 typedef enum { | |
34 NONE = 0, | |
35 SCALED = 1, | |
36 } imgstate; | |
37 | |
38 typedef struct { | |
39 unsigned char *buf; | |
40 unsigned int bufwidth, bufheight; | |
41 imgstate state; | |
42 XImage *ximg; | |
43 int numpasses; | |
44 } Image; | |
45 | |
46 typedef struct { | |
47 char *regex; | |
48 char *bin; | |
49 } Filter; | |
50 | |
51 typedef struct { | |
52 unsigned int linecount; | |
53 char **lines; | |
54 Image *img; | |
55 char *embed; | |
56 } Slide; | |
57 | |
58 /* Purely graphic info */ | |
59 typedef struct { | |
60 Display *dpy; | |
61 Window win; | |
62 Atom wmdeletewin, netwmname; | |
63 Visual *vis; | |
64 XSetWindowAttributes attrs; | |
65 int scr; | |
66 int w, h; | |
67 int uw, uh; /* usable dimensions for drawing text and images */ | |
68 } XWindow; | |
69 | |
70 typedef union { | |
71 int i; | |
72 unsigned int ui; | |
73 float f; | |
74 const void *v; | |
75 } Arg; | |
76 | |
77 typedef struct { | |
78 unsigned int b; | |
79 void (*func)(const Arg *); | |
80 const Arg arg; | |
81 } Mousekey; | |
82 | |
83 typedef struct { | |
84 KeySym keysym; | |
85 void (*func)(const Arg *); | |
86 const Arg arg; | |
87 } Shortcut; | |
88 | |
89 static void fffree(Image *img); | |
90 static void ffload(Slide *s); | |
91 static void ffprepare(Image *img); | |
92 static void ffscale(Image *img); | |
93 static void ffdraw(Image *img); | |
94 | |
95 static void getfontsize(Slide *s, unsigned int *width, unsigned int *hei… | |
96 static void cleanup(int slidesonly); | |
97 static void reload(const Arg *arg); | |
98 static void load(FILE *fp); | |
99 static void advance(const Arg *arg); | |
100 static void quit(const Arg *arg); | |
101 static void resize(int width, int height); | |
102 static void run(void); | |
103 static void usage(void); | |
104 static void xdraw(void); | |
105 static void xhints(void); | |
106 static void xinit(void); | |
107 static void xloadfonts(void); | |
108 | |
109 static void bpress(XEvent *); | |
110 static void cmessage(XEvent *); | |
111 static void expose(XEvent *); | |
112 static void kpress(XEvent *); | |
113 static void configure(XEvent *); | |
114 | |
115 /* config.h for applying patches and the configuration. */ | |
116 #include "config.h" | |
117 | |
118 /* Globals */ | |
119 static const char *fname = NULL; | |
120 static Slide *slides = NULL; | |
121 static int idx = 0; | |
122 static int slidecount = 0; | |
123 static XWindow xw; | |
124 static Drw *d = NULL; | |
125 static Clr *sc; | |
126 static Fnt *fonts[NUMFONTSCALES]; | |
127 static int running = 1; | |
128 | |
129 static void (*handler[LASTEvent])(XEvent *) = { | |
130 [ButtonPress] = bpress, | |
131 [ClientMessage] = cmessage, | |
132 [ConfigureNotify] = configure, | |
133 [Expose] = expose, | |
134 [KeyPress] = kpress, | |
135 }; | |
136 | |
137 int | |
138 filter(int fd, const char *cmd) | |
139 { | |
140 int fds[2]; | |
141 | |
142 if (pipe(fds) < 0) | |
143 die("sent: Unable to create pipe:"); | |
144 | |
145 switch (fork()) { | |
146 case -1: | |
147 die("sent: Unable to fork:"); | |
148 case 0: | |
149 dup2(fd, 0); | |
150 dup2(fds[1], 1); | |
151 close(fds[0]); | |
152 close(fds[1]); | |
153 execlp("sh", "sh", "-c", cmd, (char *)0); | |
154 fprintf(stderr, "sent: execlp sh -c '%s': %s\n", cmd, st… | |
155 _exit(1); | |
156 } | |
157 close(fds[1]); | |
158 return fds[0]; | |
159 } | |
160 | |
161 void | |
162 fffree(Image *img) | |
163 { | |
164 free(img->buf); | |
165 if (img->ximg) | |
166 XDestroyImage(img->ximg); | |
167 free(img); | |
168 } | |
169 | |
170 void | |
171 ffload(Slide *s) | |
172 { | |
173 uint32_t y, x; | |
174 uint16_t *row; | |
175 uint8_t opac, fg_r, fg_g, fg_b, bg_r, bg_g, bg_b; | |
176 size_t rowlen, off, nbytes, i; | |
177 ssize_t count; | |
178 unsigned char hdr[16]; | |
179 char *bin = NULL; | |
180 char *filename; | |
181 regex_t regex; | |
182 int fdin, fdout; | |
183 | |
184 if (s->img || !(filename = s->embed) || !s->embed[0]) | |
185 return; /* already done */ | |
186 | |
187 for (i = 0; i < LEN(filters); i++) { | |
188 if (regcomp(®ex, filters[i].regex, | |
189 REG_NOSUB | REG_EXTENDED | REG_ICASE)) { | |
190 fprintf(stderr, "sent: Invalid regex '%s'\n", fi… | |
191 continue; | |
192 } | |
193 if (!regexec(®ex, filename, 0, NULL, 0)) { | |
194 bin = filters[i].bin; | |
195 regfree(®ex); | |
196 break; | |
197 } | |
198 regfree(®ex); | |
199 } | |
200 if (!bin) | |
201 die("sent: Unable to find matching filter for '%s'", fil… | |
202 | |
203 if ((fdin = open(filename, O_RDONLY)) < 0) | |
204 die("sent: Unable to open '%s':", filename); | |
205 | |
206 if ((fdout = filter(fdin, bin)) < 0) | |
207 die("sent: Unable to filter '%s':", filename); | |
208 close(fdin); | |
209 | |
210 if (read(fdout, hdr, 16) != 16) | |
211 die("sent: Unable to read filtered file '%s':", filename… | |
212 if (memcmp("farbfeld", hdr, 8)) | |
213 die("sent: Filtered file '%s' has no valid farbfeld head… | |
214 | |
215 s->img = ecalloc(1, sizeof(Image)); | |
216 s->img->bufwidth = ntohl(*(uint32_t *)&hdr[8]); | |
217 s->img->bufheight = ntohl(*(uint32_t *)&hdr[12]); | |
218 | |
219 free(s->img->buf); | |
220 /* internally the image is stored in 888 format */ | |
221 s->img->buf = ecalloc(s->img->bufwidth * s->img->bufheight, strl… | |
222 | |
223 /* scratch buffer to read row by row */ | |
224 rowlen = s->img->bufwidth * 2 * strlen("RGBA"); | |
225 row = ecalloc(1, rowlen); | |
226 | |
227 /* extract window background color channels for transparency */ | |
228 bg_r = (sc[ColBg].pixel >> 16) % 256; | |
229 bg_g = (sc[ColBg].pixel >> 8) % 256; | |
230 bg_b = (sc[ColBg].pixel >> 0) % 256; | |
231 | |
232 for (off = 0, y = 0; y < s->img->bufheight; y++) { | |
233 nbytes = 0; | |
234 while (nbytes < rowlen) { | |
235 count = read(fdout, (char *)row + nbytes, rowlen… | |
236 if (count < 0) | |
237 die("sent: Unable to read from pipe:"); | |
238 nbytes += count; | |
239 } | |
240 for (x = 0; x < rowlen / 2; x += 4) { | |
241 fg_r = ntohs(row[x + 0]) / 257; | |
242 fg_g = ntohs(row[x + 1]) / 257; | |
243 fg_b = ntohs(row[x + 2]) / 257; | |
244 opac = ntohs(row[x + 3]) / 257; | |
245 /* blend opaque part of image data with window b… | |
246 * emulate transparency */ | |
247 s->img->buf[off++] = (fg_r * opac + bg_r * (255 … | |
248 s->img->buf[off++] = (fg_g * opac + bg_g * (255 … | |
249 s->img->buf[off++] = (fg_b * opac + bg_b * (255 … | |
250 } | |
251 } | |
252 | |
253 free(row); | |
254 close(fdout); | |
255 } | |
256 | |
257 void | |
258 ffprepare(Image *img) | |
259 { | |
260 int depth = DefaultDepth(xw.dpy, xw.scr); | |
261 int width = xw.uw; | |
262 int height = xw.uh; | |
263 | |
264 if (xw.uw * img->bufheight > xw.uh * img->bufwidth) | |
265 width = img->bufwidth * xw.uh / img->bufheight; | |
266 else | |
267 height = img->bufheight * xw.uw / img->bufwidth; | |
268 | |
269 if (depth < 24) | |
270 die("sent: Display color depths < 24 not supported"); | |
271 | |
272 if (img->ximg) | |
273 XDestroyImage(img->ximg); | |
274 | |
275 if (!(img->ximg = XCreateImage(xw.dpy, CopyFromParent, depth, ZP… | |
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->bufw… | |
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 f… | |
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… | |
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, maxli… | |
444 die("sent: Unable to reallocate … | |
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 … | |
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->line… | |
459 s->linecount++; | |
460 } while ((p = fgets(buf, sizeof(buf), fp)) && strcmp(buf… | |
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(void) | |
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(void) | |
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 *… | |
537 width, | |
538 d->fonts->h, | |
539 0, | |
540 slides[idx].lines[i], | |
541 0); | |
542 drw_map(d, xw.win, 0, 0, xw.w, xw.h); | |
543 } else { | |
544 if (!(im->state & SCALED)) | |
545 ffprepare(im); | |
546 ffdraw(im); | |
547 } | |
548 } | |
549 | |
550 void | |
551 xhints(void) | |
552 { | |
553 XClassHint class = {.res_name = "sent", .res_class = "presenter"… | |
554 XWMHints wm = {.flags = InputHint, .input = True}; | |
555 XSizeHints *sizeh = NULL; | |
556 | |
557 if (!(sizeh = XAllocSizeHints())) | |
558 die("sent: Unable to allocate size hints"); | |
559 | |
560 sizeh->flags = PSize; | |
561 sizeh->height = xw.h; | |
562 sizeh->width = xw.w; | |
563 | |
564 XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm… | |
565 XFree(sizeh); | |
566 } | |
567 | |
568 void | |
569 xinit(void) | |
570 { | |
571 XTextProperty prop; | |
572 unsigned int i; | |
573 | |
574 if (!(xw.dpy = XOpenDisplay(NULL))) | |
575 die("sent: Unable to open display"); | |
576 xw.scr = XDefaultScreen(xw.dpy); | |
577 xw.vis = XDefaultVisual(xw.dpy, xw.scr); | |
578 resize(DisplayWidth(xw.dpy, xw.scr), DisplayHeight(xw.dpy, xw.sc… | |
579 | |
580 xw.attrs.bit_gravity = CenterGravity; | |
581 xw.attrs.event_mask = KeyPressMask | ExposureMask | StructureNot… | |
582 ButtonMotionMask | ButtonPressMask; | |
583 | |
584 xw.win = XCreateWindow(xw.dpy, XRootWindow(xw.dpy, xw.scr), 0, 0, | |
585 xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.s… | |
586 InputOutput, xw.vis, CWBitGravity | CWEve… | |
587 &xw.attrs); | |
588 | |
589 xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); | |
590 xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); | |
591 XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); | |
592 | |
593 if (!(d = drw_create(xw.dpy, xw.scr, xw.win, xw.w, xw.h))) | |
594 die("sent: Unable to create drawing context"); | |
595 sc = drw_scm_create(d, colors, 2); | |
596 drw_setscheme(d, sc); | |
597 XSetWindowBackground(xw.dpy, xw.win, sc[ColBg].pixel); | |
598 | |
599 xloadfonts(); | |
600 for (i = 0; i < slidecount; i++) | |
601 ffload(&slides[i]); | |
602 | |
603 XStringListToTextProperty(&argv0, 1, &prop); | |
604 XSetWMName(xw.dpy, xw.win, &prop); | |
605 XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); | |
606 XFree(prop.value); | |
607 XMapWindow(xw.dpy, xw.win); | |
608 xhints(); | |
609 XSync(xw.dpy, False); | |
610 } | |
611 | |
612 void | |
613 xloadfonts(void) | |
614 { | |
615 int i, j; | |
616 char *fstrs[LEN(fontfallbacks)]; | |
617 | |
618 for (j = 0; j < LEN(fontfallbacks); j++) { | |
619 fstrs[j] = ecalloc(1, MAXFONTSTRLEN); | |
620 } | |
621 | |
622 for (i = 0; i < NUMFONTSCALES; i++) { | |
623 for (j = 0; j < LEN(fontfallbacks); j++) { | |
624 if (MAXFONTSTRLEN < snprintf(fstrs[j], MAXFONTST… | |
625 die("sent: Font string too long"); | |
626 } | |
627 if (!(fonts[i] = drw_fontset_create(d, (const char**)fst… | |
628 die("sent: Unable to load any font for size %d",… | |
629 } | |
630 | |
631 for (j = 0; j < LEN(fontfallbacks); j++) | |
632 free(fstrs[j]); | |
633 } | |
634 | |
635 void | |
636 bpress(XEvent *e) | |
637 { | |
638 unsigned int i; | |
639 | |
640 for (i = 0; i < LEN(mshortcuts); i++) | |
641 if (e->xbutton.button == mshortcuts[i].b && mshortcuts[i… | |
642 mshortcuts[i].func(&(mshortcuts[i].arg)); | |
643 } | |
644 | |
645 void | |
646 cmessage(XEvent *e) | |
647 { | |
648 if (e->xclient.data.l[0] == xw.wmdeletewin) | |
649 running = 0; | |
650 } | |
651 | |
652 void | |
653 expose(XEvent *e) | |
654 { | |
655 if (0 == e->xexpose.count) | |
656 xdraw(); | |
657 } | |
658 | |
659 void | |
660 kpress(XEvent *e) | |
661 { | |
662 unsigned int i; | |
663 KeySym sym; | |
664 | |
665 sym = XkbKeycodeToKeysym(xw.dpy, (KeyCode)e->xkey.keycode, 0, 0); | |
666 for (i = 0; i < LEN(shortcuts); i++) | |
667 if (sym == shortcuts[i].keysym && shortcuts[i].func) | |
668 shortcuts[i].func(&(shortcuts[i].arg)); | |
669 } | |
670 | |
671 void | |
672 configure(XEvent *e) | |
673 { | |
674 resize(e->xconfigure.width, e->xconfigure.height); | |
675 if (slides[idx].img) | |
676 slides[idx].img->state &= ~SCALED; | |
677 xdraw(); | |
678 } | |
679 | |
680 void | |
681 usage(void) | |
682 { | |
683 die("usage: %s [file]", argv0); | |
684 } | |
685 | |
686 int | |
687 main(int argc, char *argv[]) | |
688 { | |
689 FILE *fp = NULL; | |
690 | |
691 ARGBEGIN { | |
692 case 'v': | |
693 fprintf(stderr, "sent-"VERSION"\n"); | |
694 return 0; | |
695 default: | |
696 usage(); | |
697 } ARGEND | |
698 | |
699 if (!argv[0] || !strcmp(argv[0], "-")) | |
700 fp = stdin; | |
701 else if (!(fp = fopen(fname = argv[0], "r"))) | |
702 die("sent: Unable to open '%s' for reading:", fname); | |
703 load(fp); | |
704 fclose(fp); | |
705 | |
706 xinit(); | |
707 run(); | |
708 | |
709 cleanup(0); | |
710 return 0; | |
711 } |