URI: 
       sent.c - sent - simple plaintext presentation tool
  HTML git clone git://git.suckless.org/sent
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR 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 *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(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, 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         free(s->img->buf);
          220         /* internally the image is stored in 888 format */
          221         s->img->buf = ecalloc(s->img->bufwidth * s->img->bufheight, strlen("888"));
          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 - nbytes);
          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 background color to
          246                          * emulate transparency */
          247                         s->img->buf[off++] = (fg_r * opac + bg_r * (255 - opac)) / 255;
          248                         s->img->buf[off++] = (fg_g * opac + bg_g * (255 - opac)) / 255;
          249                         s->img->buf[off++] = (fg_b * opac + bg_b * (255 - opac)) / 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, 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(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 * d->fonts->h,
          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, &class);
          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.scr));
          579 
          580         xw.attrs.bit_gravity = CenterGravity;
          581         xw.attrs.event_mask = KeyPressMask | ExposureMask | StructureNotifyMask |
          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.scr),
          586                                InputOutput, xw.vis, CWBitGravity | CWEventMask,
          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], MAXFONTSTRLEN, "%s:size=%d", fontfallbacks[j], FONTSZ(i)))
          625                                 die("sent: Font string too long");
          626                 }
          627                 if (!(fonts[i] = drw_fontset_create(d, (const char**)fstrs, LEN(fstrs))))
          628                         die("sent: Unable to load any font for size %d", FONTSZ(i));
          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].func)
          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 }