t* My version of sent
       
   URI git clone git://git.codevoid.de/sent-sdk.git
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tsent.c (17491B)
       ---
            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 *height);
           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();
          103 static void usage();
          104 static void xdraw();
          105 static void xhints();
          106 static void xinit();
          107 static void xloadfonts();
          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, strerror(errno));
          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", filters[i].regex);
          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'", filename);
          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 header", filename);
          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         if (s->img->buf)
          220                 free(s->img->buf);
          221         /* internally the image is stored in 888 format */
          222         s->img->buf = ecalloc(s->img->bufwidth * s->img->bufheight, strlen("888"));
          223 
          224         /* scratch buffer to read row by row */
          225         rowlen = s->img->bufwidth * 2 * strlen("RGBA");
          226         row = ecalloc(1, rowlen);
          227 
          228         /* extract window background color channels for transparency */
          229         bg_r = (sc[ColBg].pixel >> 16) % 256;
          230         bg_g = (sc[ColBg].pixel >>  8) % 256;
          231         bg_b = (sc[ColBg].pixel >>  0) % 256;
          232 
          233         for (off = 0, y = 0; y < s->img->bufheight; y++) {
          234                 nbytes = 0;
          235                 while (nbytes < rowlen) {
          236                         count = read(fdout, (char *)row + nbytes, rowlen - nbytes);
          237                         if (count < 0)
          238                                 die("sent: Unable to read from pipe:");
          239                         nbytes += count;
          240                 }
          241                 for (x = 0; x < rowlen / 2; x += 4) {
          242                         fg_r = ntohs(row[x + 0]) / 257;
          243                         fg_g = ntohs(row[x + 1]) / 257;
          244                         fg_b = ntohs(row[x + 2]) / 257;
          245                         opac = ntohs(row[x + 3]) / 257;
          246                         /* blend opaque part of image data with window background color to
          247                          * emulate transparency */
          248                         s->img->buf[off++] = (fg_r * opac + bg_r * (255 - opac)) / 255;
          249                         s->img->buf[off++] = (fg_g * opac + bg_g * (255 - opac)) / 255;
          250                         s->img->buf[off++] = (fg_b * opac + bg_b * (255 - opac)) / 255;
          251                 }
          252         }
          253 
          254         free(row);
          255         close(fdout);
          256 }
          257 
          258 void
          259 ffprepare(Image *img)
          260 {
          261         int depth = DefaultDepth(xw.dpy, xw.scr);
          262         int width = xw.uw;
          263         int height = xw.uh;
          264 
          265         if (xw.uw * img->bufheight > xw.uh * img->bufwidth)
          266                 width = img->bufwidth * xw.uh / img->bufheight;
          267         else
          268                 height = img->bufheight * xw.uw / img->bufwidth;
          269 
          270         if (depth < 24)
          271                 die("sent: Display color depths < 24 not supported");
          272 
          273         if (!(img->ximg = XCreateImage(xw.dpy, CopyFromParent, depth, ZPixmap, 0,
          274                                        NULL, width, height, 32, 0)))
          275                 die("sent: Unable to create XImage");
          276 
          277         img->ximg->data = ecalloc(height, img->ximg->bytes_per_line);
          278         if (!XInitImage(img->ximg))
          279                 die("sent: Unable to initiate XImage");
          280 
          281         ffscale(img);
          282         img->state |= SCALED;
          283 }
          284 
          285 static unsigned char double_to_uchar_clamp255(double dbl)
          286 {
          287         dbl = round(dbl);
          288 
          289         return
          290                 (dbl < 0.0)   ? 0 :
          291                 (dbl > 255.0) ? 255 : (unsigned char)dbl;
          292 }
          293 
          294 static int int_clamp(int integer, int lower, int upper)
          295 {
          296         if (integer < lower)
          297                 return lower;
          298         else if (integer >= upper)
          299                 return upper - 1;
          300         else
          301                 return integer;
          302 }
          303 
          304 void
          305 ffscale(Image *img)
          306 {
          307         const unsigned width = img->ximg->width;
          308         const unsigned height = img->ximg->height;
          309         unsigned char* newBuf = (unsigned char*)img->ximg->data;
          310         const unsigned jdy = img->ximg->bytes_per_line / 4 - width;
          311 
          312         const double x_scale = ((double)img->bufwidth/(double)width);
          313         const double y_scale = ((double)img->bufheight/(double)height);
          314 
          315         for (unsigned y = 0; y < height; ++y) {
          316                 const double old_y = (double)y * y_scale;
          317                 const double y_factor = ceil(old_y) - old_y;
          318                 const int old_y_int_0 = int_clamp((int)floor(old_y), 0, img->bufheight);
          319                 const int old_y_int_1 = int_clamp((int)ceil(old_y), 0, img->bufheight);
          320 
          321                 for (unsigned x = 0; x < width; ++x) {
          322                         const double old_x = (double)x * x_scale;
          323                         const double x_factor = ceil(old_x) - old_x;
          324                         const int old_x_int_0 = int_clamp((int)floor(old_x), 0, img->bufwidth);
          325                         const int old_x_int_1 = int_clamp((int)ceil(old_x), 0, img->bufwidth);
          326 
          327                         const unsigned c00_pos = 3*((old_x_int_0) + ((old_y_int_0)*img->bufwidth));
          328                         const unsigned c01_pos = 3*((old_x_int_0) + ((old_y_int_1)*img->bufwidth));
          329                         const unsigned c10_pos = 3*((old_x_int_1) + ((old_y_int_0)*img->bufwidth));
          330                         const unsigned c11_pos = 3*((old_x_int_1) + ((old_y_int_1)*img->bufwidth));
          331 
          332                         for (int i = 2; i >= 0 ; --i) {
          333                                 const unsigned char c00 = img->buf[c00_pos + i];
          334                                 const unsigned char c01 = img->buf[c01_pos + i];
          335                                 const unsigned char c10 = img->buf[c10_pos + i];
          336                                 const unsigned char c11 = img->buf[c11_pos + i];
          337 
          338                                 const double x_result_0 = (double)c00*x_factor + (double)c10*(1.0 - x_factor);
          339                                 const double x_result_1 = (double)c01*x_factor + (double)c11*(1.0 - x_factor);
          340                                 const double result = x_result_0*y_factor + x_result_1*(1.0 - y_factor);
          341 
          342                                 *newBuf++ = double_to_uchar_clamp255(result);
          343                         }
          344                         newBuf++;
          345                 }
          346                 newBuf += jdy;
          347         }
          348 }
          349 
          350 void
          351 ffdraw(Image *img)
          352 {
          353         int xoffset = (xw.w - img->ximg->width) / 2;
          354         int yoffset = (xw.h - img->ximg->height) / 2;
          355         XPutImage(xw.dpy, xw.win, d->gc, img->ximg, 0, 0,
          356                   xoffset, yoffset, img->ximg->width, img->ximg->height);
          357         XFlush(xw.dpy);
          358 }
          359 
          360 void
          361 getfontsize(Slide *s, unsigned int *width, unsigned int *height)
          362 {
          363         int i, j;
          364         unsigned int curw, newmax;
          365         float lfac = linespacing * (s->linecount - 1) + 1;
          366 
          367         /* fit height */
          368         for (j = NUMFONTSCALES - 1; j >= 0; j--)
          369                 if (fonts[j]->h * lfac <= xw.uh)
          370                         break;
          371         LIMIT(j, 0, NUMFONTSCALES - 1);
          372         drw_setfontset(d, fonts[j]);
          373 
          374         /* fit width */
          375         *width = 0;
          376         for (i = 0; i < s->linecount; i++) {
          377                 curw = drw_fontset_getwidth(d, s->lines[i]);
          378                 newmax = (curw >= *width);
          379                 while (j > 0 && curw > xw.uw) {
          380                         drw_setfontset(d, fonts[--j]);
          381                         curw = drw_fontset_getwidth(d, s->lines[i]);
          382                 }
          383                 if (newmax)
          384                         *width = curw;
          385         }
          386         *height = fonts[j]->h * lfac;
          387 }
          388 
          389 void
          390 cleanup(int slidesonly)
          391 {
          392         unsigned int i, j;
          393 
          394         if (!slidesonly) {
          395                 for (i = 0; i < NUMFONTSCALES; i++)
          396                         drw_fontset_free(fonts[i]);
          397                 free(sc);
          398                 drw_free(d);
          399 
          400                 XDestroyWindow(xw.dpy, xw.win);
          401                 XSync(xw.dpy, False);
          402                 XCloseDisplay(xw.dpy);
          403         }
          404 
          405         if (slides) {
          406                 for (i = 0; i < slidecount; i++) {
          407                         for (j = 0; j < slides[i].linecount; j++)
          408                                 free(slides[i].lines[j]);
          409                         free(slides[i].lines);
          410                         if (slides[i].img)
          411                                 fffree(slides[i].img);
          412                 }
          413                 if (!slidesonly) {
          414                         free(slides);
          415                         slides = NULL;
          416                 }
          417         }
          418 }
          419 
          420 void
          421 reload(const Arg *arg)
          422 {
          423         FILE *fp = NULL;
          424         unsigned int i;
          425 
          426         if (!fname) {
          427                 fprintf(stderr, "sent: Cannot reload from stdin. Use a file!\n");
          428                 return;
          429         }
          430 
          431         cleanup(1);
          432         slidecount = 0;
          433 
          434         if (!(fp = fopen(fname, "r")))
          435                 die("sent: Unable to open '%s' for reading:", fname);
          436         load(fp);
          437         fclose(fp);
          438 
          439         LIMIT(idx, 0, slidecount-1);
          440         for (i = 0; i < slidecount; i++)
          441                 ffload(&slides[i]);
          442         xdraw();
          443 }
          444 
          445 void
          446 load(FILE *fp)
          447 {
          448         static size_t size = 0;
          449         size_t blen, maxlines;
          450         char buf[BUFSIZ], *p;
          451         Slide *s;
          452 
          453         /* read each line from fp and add it to the item list */
          454         while (1) {
          455                 /* eat consecutive empty lines */
          456                 while ((p = fgets(buf, sizeof(buf), fp)))
          457                         if (strcmp(buf, "\n") != 0 && buf[0] != '#')
          458                                 break;
          459                 if (!p)
          460                         break;
          461 
          462                 if ((slidecount+1) * sizeof(*slides) >= size)
          463                         if (!(slides = realloc(slides, (size += BUFSIZ))))
          464                                 die("sent: Unable to reallocate %u bytes:", size);
          465 
          466                 /* read one slide */
          467                 maxlines = 0;
          468                 memset((s = &slides[slidecount]), 0, sizeof(Slide));
          469                 do {
          470                         /* if there's a leading null, we can't do blen-1 */
          471                         if (buf[0] == '\0')
          472                                 continue;
          473 
          474                         if (buf[0] == '#')
          475                                 continue;
          476 
          477                         /* grow lines array */
          478                         if (s->linecount >= maxlines) {
          479                                 maxlines = 2 * s->linecount + 1;
          480                                 if (!(s->lines = realloc(s->lines, maxlines * sizeof(s->lines[0]))))
          481                                         die("sent: Unable to reallocate %u bytes:", maxlines * sizeof(s->lines[0]));
          482                         }
          483 
          484                         blen = strlen(buf);
          485                         if (!(s->lines[s->linecount] = strdup(buf)))
          486                                 die("sent: Unable to strdup:");
          487                         if (s->lines[s->linecount][blen-1] == '\n')
          488                                 s->lines[s->linecount][blen-1] = '\0';
          489 
          490                         /* mark as image slide if first line of a slide starts with @ */
          491                         if (s->linecount == 0 && s->lines[0][0] == '@')
          492                                 s->embed = &s->lines[0][1];
          493 
          494                         if (s->lines[s->linecount][0] == '\\')
          495                                 memmove(s->lines[s->linecount], &s->lines[s->linecount][1], blen);
          496                         s->linecount++;
          497                 } while ((p = fgets(buf, sizeof(buf), fp)) && strcmp(buf, "\n") != 0);
          498 
          499                 slidecount++;
          500                 if (!p)
          501                         break;
          502         }
          503 
          504         if (!slidecount)
          505                 die("sent: No slides in file");
          506 }
          507 
          508 void
          509 advance(const Arg *arg)
          510 {
          511         int new_idx = idx + arg->i;
          512         LIMIT(new_idx, 0, slidecount-1);
          513         if (new_idx != idx) {
          514                 if (slides[idx].img)
          515                         slides[idx].img->state &= ~SCALED;
          516                 idx = new_idx;
          517                 xdraw();
          518         }
          519 }
          520 
          521 void
          522 quit(const Arg *arg)
          523 {
          524         running = 0;
          525 }
          526 
          527 void
          528 resize(int width, int height)
          529 {
          530         xw.w = width;
          531         xw.h = height;
          532         xw.uw = usablewidth * width;
          533         xw.uh = usableheight * height;
          534         drw_resize(d, width, height);
          535 }
          536 
          537 void
          538 run()
          539 {
          540         XEvent ev;
          541 
          542         /* Waiting for window mapping */
          543         while (1) {
          544                 XNextEvent(xw.dpy, &ev);
          545                 if (ev.type == ConfigureNotify) {
          546                         resize(ev.xconfigure.width, ev.xconfigure.height);
          547                 } else if (ev.type == MapNotify) {
          548                         break;
          549                 }
          550         }
          551 
          552         while (running) {
          553                 XNextEvent(xw.dpy, &ev);
          554                 if (handler[ev.type])
          555                         (handler[ev.type])(&ev);
          556         }
          557 }
          558 
          559 void
          560 xdraw()
          561 {
          562         unsigned int height, width, i;
          563         Image *im = slides[idx].img;
          564 
          565         getfontsize(&slides[idx], &width, &height);
          566         XClearWindow(xw.dpy, xw.win);
          567 
          568         if (!im) {
          569                 drw_rect(d, 0, 0, xw.w, xw.h, 1, 1);
          570                 for (i = 0; i < slides[idx].linecount; i++)
          571                         drw_text(d,
          572                                  (xw.w - width) / 2,
          573                                  (xw.h - height) / 2 + i * linespacing * d->fonts->h,
          574                                  width,
          575                                  d->fonts->h,
          576                                  0,
          577                                  slides[idx].lines[i],
          578                                  0);
          579                 if (idx != 0 && progressheight != 0) {
          580                         drw_rect(d,
          581                                  0, xw.h - progressheight,
          582                                  (xw.w * idx)/(slidecount - 1), progressheight,
          583                                  1, 0);
          584                 }
          585                 drw_map(d, xw.win, 0, 0, xw.w, xw.h);
          586         } else {
          587                 if (!(im->state & SCALED))
          588                         ffprepare(im);
          589                 ffdraw(im);
          590         }
          591 }
          592 
          593 void
          594 xhints()
          595 {
          596         XClassHint class = {.res_name = "sent", .res_class = "presenter"};
          597         XWMHints wm = {.flags = InputHint, .input = True};
          598         XSizeHints *sizeh = NULL;
          599 
          600         if (!(sizeh = XAllocSizeHints()))
          601                 die("sent: Unable to allocate size hints");
          602 
          603         sizeh->flags = PSize;
          604         sizeh->height = xw.h;
          605         sizeh->width = xw.w;
          606 
          607         XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, &class);
          608         XFree(sizeh);
          609 }
          610 
          611 void
          612 xinit()
          613 {
          614         XTextProperty prop;
          615         unsigned int i;
          616 
          617         if (!(xw.dpy = XOpenDisplay(NULL)))
          618                 die("sent: Unable to open display");
          619         xw.scr = XDefaultScreen(xw.dpy);
          620         xw.vis = XDefaultVisual(xw.dpy, xw.scr);
          621         resize(DisplayWidth(xw.dpy, xw.scr), DisplayHeight(xw.dpy, xw.scr));
          622 
          623         xw.attrs.bit_gravity = CenterGravity;
          624         xw.attrs.event_mask = KeyPressMask | ExposureMask | StructureNotifyMask |
          625                               ButtonMotionMask | ButtonPressMask;
          626 
          627         xw.win = XCreateWindow(xw.dpy, XRootWindow(xw.dpy, xw.scr), 0, 0,
          628                                xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr),
          629                                InputOutput, xw.vis, CWBitGravity | CWEventMask,
          630                                &xw.attrs);
          631 
          632         xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
          633         xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
          634         XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
          635 
          636         if (!(d = drw_create(xw.dpy, xw.scr, xw.win, xw.w, xw.h)))
          637                 die("sent: Unable to create drawing context");
          638         sc = drw_scm_create(d, colors, 2);
          639         drw_setscheme(d, sc);
          640         XSetWindowBackground(xw.dpy, xw.win, sc[ColBg].pixel);
          641 
          642         xloadfonts();
          643         for (i = 0; i < slidecount; i++)
          644                 ffload(&slides[i]);
          645 
          646         XStringListToTextProperty(&argv0, 1, &prop);
          647         XSetWMName(xw.dpy, xw.win, &prop);
          648         XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname);
          649         XFree(prop.value);
          650         XMapWindow(xw.dpy, xw.win);
          651         xhints();
          652         XSync(xw.dpy, False);
          653 }
          654 
          655 void
          656 xloadfonts()
          657 {
          658         int i, j;
          659         char *fstrs[LEN(fontfallbacks)];
          660 
          661         for (j = 0; j < LEN(fontfallbacks); j++) {
          662                 fstrs[j] = ecalloc(1, MAXFONTSTRLEN);
          663         }
          664 
          665         for (i = 0; i < NUMFONTSCALES; i++) {
          666                 for (j = 0; j < LEN(fontfallbacks); j++) {
          667                         if (MAXFONTSTRLEN < snprintf(fstrs[j], MAXFONTSTRLEN, "%s:size=%d", fontfallbacks[j], FONTSZ(i)))
          668                                 die("sent: Font string too long");
          669                 }
          670                 if (!(fonts[i] = drw_fontset_create(d, (const char**)fstrs, LEN(fstrs))))
          671                         die("sent: Unable to load any font for size %d", FONTSZ(i));
          672         }
          673 
          674         for (j = 0; j < LEN(fontfallbacks); j++)
          675                 if (fstrs[j])
          676                         free(fstrs[j]);
          677 }
          678 
          679 void
          680 bpress(XEvent *e)
          681 {
          682         unsigned int i;
          683 
          684         for (i = 0; i < LEN(mshortcuts); i++)
          685                 if (e->xbutton.button == mshortcuts[i].b && mshortcuts[i].func)
          686                         mshortcuts[i].func(&(mshortcuts[i].arg));
          687 }
          688 
          689 void
          690 cmessage(XEvent *e)
          691 {
          692         if (e->xclient.data.l[0] == xw.wmdeletewin)
          693                 running = 0;
          694 }
          695 
          696 void
          697 expose(XEvent *e)
          698 {
          699         if (0 == e->xexpose.count)
          700                 xdraw();
          701 }
          702 
          703 void
          704 kpress(XEvent *e)
          705 {
          706         unsigned int i;
          707         KeySym sym;
          708 
          709         sym = XkbKeycodeToKeysym(xw.dpy, (KeyCode)e->xkey.keycode, 0, 0);
          710         for (i = 0; i < LEN(shortcuts); i++)
          711                 if (sym == shortcuts[i].keysym && shortcuts[i].func)
          712                         shortcuts[i].func(&(shortcuts[i].arg));
          713 }
          714 
          715 void
          716 configure(XEvent *e)
          717 {
          718         resize(e->xconfigure.width, e->xconfigure.height);
          719         if (slides[idx].img)
          720                 slides[idx].img->state &= ~SCALED;
          721         xdraw();
          722 }
          723 
          724 void
          725 usage()
          726 {
          727         die("usage: %s [-c fgcolor] [-b bgcolor] [-f font] [file]", argv0);
          728 }
          729 
          730 int
          731 main(int argc, char *argv[])
          732 {
          733         FILE *fp = NULL;
          734 
          735         ARGBEGIN {
          736         case 'v':
          737                 fprintf(stderr, "sent-"VERSION"\n");
          738                 return 0;
          739         case 'f':
          740                 fontfallbacks[0] = EARGF(usage());
          741                 break;
          742         case 'c':
          743                 colors[0] = EARGF(usage());
          744                 break;
          745         case 'b':
          746                 colors[1] = EARGF(usage());
          747                 break;
          748         default:
          749                 usage();
          750         } ARGEND
          751 
          752         if (!argv[0] || !strcmp(argv[0], "-"))
          753                 fp = stdin;
          754         else if (!(fp = fopen(fname = argv[0], "r")))
          755                 die("sent: Unable to open '%s' for reading:", fname);
          756         load(fp);
          757         fclose(fp);
          758 
          759         xinit();
          760         run();
          761 
          762         cleanup(0);
          763         return 0;
          764 }