Introduction
Introduction Statistics Contact Development Disclaimer Help
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(&regex, 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(&regex, filename, 0, NULL, 0)) {
194 bin = filters[i].bin;
195 regfree(&regex);
196 break;
197 }
198 regfree(&regex);
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 }
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.