From a4bc34d7bfc9c1b864edd50cb9ba43e4bdaf2e58 Mon Sep 17 00:00:00 2001 From: rafa_99 Date: Sun, 6 Oct 2019 14:47:06 +0000 Subject: Command Line Options Patch --- sent.1 | 11 + sent.c | 11 +- sent.c.orig | 745 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 766 insertions(+), 1 deletion(-) create mode 100644 sent.c.orig diff --git a/sent.1 b/sent.1 index fabc614..5d55bf4 100644 --- a/sent.1 +++ b/sent.1 @@ -5,6 +5,9 @@ .Nd simple plaintext presentation tool .Sh SYNOPSIS .Nm +.Op Fl f Ar font +.Op Fl c Ar fgcolor +.Op Fl b Ar bgcolor .Op Fl v .Op Ar file .Sh DESCRIPTION @@ -21,6 +24,14 @@ few minutes. .Bl -tag -width Ds .It Fl v Print version information to stdout and exit. +.It Fl f Ar font +Defines the +.Ar font +when sent is run. +.It Fl c Ar fgcolor +Defines the foreground color when sent is run. +.It Fl b Ar bgcolor +Defines the background color when sent is run. .El .Sh USAGE .Bl -tag -width Ds diff --git a/sent.c b/sent.c index bdc61be..a2d9030 100644 --- a/sent.c +++ b/sent.c @@ -714,7 +714,7 @@ configure(XEvent *e) void usage() { - die("usage: %s [file]", argv0); + die("usage: %s [-c fgcolor] [-b bgcolor] [-f font] [file]", argv0); } int @@ -726,6 +726,15 @@ main(int argc, char *argv[]) case 'v': fprintf(stderr, "sent-"VERSION"\n"); return 0; + case 'f': + fontfallbacks[0] = EARGF(usage()); + break; + case 'c': + colors[0] = EARGF(usage()); + break; + case 'b': + colors[1] = EARGF(usage()); + break; default: usage(); } ARGEND diff --git a/sent.c.orig b/sent.c.orig new file mode 100644 index 0000000..bdc61be --- /dev/null +++ b/sent.c.orig @@ -0,0 +1,745 @@ +/* See LICENSE file for copyright and license details. */ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arg.h" +#include "util.h" +#include "drw.h" + +char *argv0; + +/* macros */ +#define LEN(a) (sizeof(a) / sizeof(a)[0]) +#define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) +#define MAXFONTSTRLEN 128 + +typedef enum { + NONE = 0, + SCALED = 1, +} imgstate; + +typedef struct { + unsigned char *buf; + unsigned int bufwidth, bufheight; + imgstate state; + XImage *ximg; + int numpasses; +} Image; + +typedef struct { + char *regex; + char *bin; +} Filter; + +typedef struct { + unsigned int linecount; + char **lines; + Image *img; + char *embed; +} Slide; + +/* Purely graphic info */ +typedef struct { + Display *dpy; + Window win; + Atom wmdeletewin, netwmname; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int w, h; + int uw, uh; /* usable dimensions for drawing text and images */ +} XWindow; + +typedef union { + int i; + unsigned int ui; + float f; + const void *v; +} Arg; + +typedef struct { + unsigned int b; + void (*func)(const Arg *); + const Arg arg; +} Mousekey; + +typedef struct { + KeySym keysym; + void (*func)(const Arg *); + const Arg arg; +} Shortcut; + +static void fffree(Image *img); +static void ffload(Slide *s); +static void ffprepare(Image *img); +static void ffscale(Image *img); +static void ffdraw(Image *img); + +static void getfontsize(Slide *s, unsigned int *width, unsigned int *height); +static void cleanup(int slidesonly); +static void reload(const Arg *arg); +static void load(FILE *fp); +static void advance(const Arg *arg); +static void quit(const Arg *arg); +static void resize(int width, int height); +static void run(); +static void usage(); +static void xdraw(); +static void xhints(); +static void xinit(); +static void xloadfonts(); + +static void bpress(XEvent *); +static void cmessage(XEvent *); +static void expose(XEvent *); +static void kpress(XEvent *); +static void configure(XEvent *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +/* Globals */ +static const char *fname = NULL; +static Slide *slides = NULL; +static int idx = 0; +static int slidecount = 0; +static XWindow xw; +static Drw *d = NULL; +static Clr *sc; +static Fnt *fonts[NUMFONTSCALES]; +static int running = 1; + +static void (*handler[LASTEvent])(XEvent *) = { + [ButtonPress] = bpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = configure, + [Expose] = expose, + [KeyPress] = kpress, +}; + +int +filter(int fd, const char *cmd) +{ + int fds[2]; + + if (pipe(fds) < 0) + die("sent: Unable to create pipe:"); + + switch (fork()) { + case -1: + die("sent: Unable to fork:"); + case 0: + dup2(fd, 0); + dup2(fds[1], 1); + close(fds[0]); + close(fds[1]); + execlp("sh", "sh", "-c", cmd, (char *)0); + fprintf(stderr, "sent: execlp sh -c '%s': %s\n", cmd, strerror(errno)); + _exit(1); + } + close(fds[1]); + return fds[0]; +} + +void +fffree(Image *img) +{ + free(img->buf); + if (img->ximg) + XDestroyImage(img->ximg); + free(img); +} + +void +ffload(Slide *s) +{ + uint32_t y, x; + uint16_t *row; + uint8_t opac, fg_r, fg_g, fg_b, bg_r, bg_g, bg_b; + size_t rowlen, off, nbytes, i; + ssize_t count; + unsigned char hdr[16]; + char *bin = NULL; + char *filename; + regex_t regex; + int fdin, fdout; + + if (s->img || !(filename = s->embed) || !s->embed[0]) + return; /* already done */ + + for (i = 0; i < LEN(filters); i++) { + if (regcomp(®ex, filters[i].regex, + REG_NOSUB | REG_EXTENDED | REG_ICASE)) { + fprintf(stderr, "sent: Invalid regex '%s'\n", filters[i].regex); + continue; + } + if (!regexec(®ex, filename, 0, NULL, 0)) { + bin = filters[i].bin; + regfree(®ex); + break; + } + regfree(®ex); + } + if (!bin) + die("sent: Unable to find matching filter for '%s'", filename); + + if ((fdin = open(filename, O_RDONLY)) < 0) + die("sent: Unable to open '%s':", filename); + + if ((fdout = filter(fdin, bin)) < 0) + die("sent: Unable to filter '%s':", filename); + close(fdin); + + if (read(fdout, hdr, 16) != 16) + die("sent: Unable to read filtered file '%s':", filename); + if (memcmp("farbfeld", hdr, 8)) + die("sent: Filtered file '%s' has no valid farbfeld header", filename); + + s->img = ecalloc(1, sizeof(Image)); + s->img->bufwidth = ntohl(*(uint32_t *)&hdr[8]); + s->img->bufheight = ntohl(*(uint32_t *)&hdr[12]); + + if (s->img->buf) + free(s->img->buf); + /* internally the image is stored in 888 format */ + s->img->buf = ecalloc(s->img->bufwidth * s->img->bufheight, strlen("888")); + + /* scratch buffer to read row by row */ + rowlen = s->img->bufwidth * 2 * strlen("RGBA"); + row = ecalloc(1, rowlen); + + /* extract window background color channels for transparency */ + bg_r = (sc[ColBg].pixel >> 16) % 256; + bg_g = (sc[ColBg].pixel >> 8) % 256; + bg_b = (sc[ColBg].pixel >> 0) % 256; + + for (off = 0, y = 0; y < s->img->bufheight; y++) { + nbytes = 0; + while (nbytes < rowlen) { + count = read(fdout, (char *)row + nbytes, rowlen - nbytes); + if (count < 0) + die("sent: Unable to read from pipe:"); + nbytes += count; + } + for (x = 0; x < rowlen / 2; x += 4) { + fg_r = ntohs(row[x + 0]) / 257; + fg_g = ntohs(row[x + 1]) / 257; + fg_b = ntohs(row[x + 2]) / 257; + opac = ntohs(row[x + 3]) / 257; + /* blend opaque part of image data with window background color to + * emulate transparency */ + s->img->buf[off++] = (fg_r * opac + bg_r * (255 - opac)) / 255; + s->img->buf[off++] = (fg_g * opac + bg_g * (255 - opac)) / 255; + s->img->buf[off++] = (fg_b * opac + bg_b * (255 - opac)) / 255; + } + } + + free(row); + close(fdout); +} + +void +ffprepare(Image *img) +{ + int depth = DefaultDepth(xw.dpy, xw.scr); + int width = xw.uw; + int height = xw.uh; + + if (xw.uw * img->bufheight > xw.uh * img->bufwidth) + width = img->bufwidth * xw.uh / img->bufheight; + else + height = img->bufheight * xw.uw / img->bufwidth; + + if (depth < 24) + die("sent: Display color depths < 24 not supported"); + + if (!(img->ximg = XCreateImage(xw.dpy, CopyFromParent, depth, ZPixmap, 0, + NULL, width, height, 32, 0))) + die("sent: Unable to create XImage"); + + img->ximg->data = ecalloc(height, img->ximg->bytes_per_line); + if (!XInitImage(img->ximg)) + die("sent: Unable to initiate XImage"); + + ffscale(img); + img->state |= SCALED; +} + +static unsigned char double_to_uchar_clamp255(double dbl) +{ + dbl = round(dbl); + + return + (dbl < 0.0) ? 0 : + (dbl > 255.0) ? 255 : (unsigned char)dbl; +} + +static int int_clamp(int integer, int lower, int upper) +{ + if (integer < lower) + return lower; + else if (integer >= upper) + return upper - 1; + else + return integer; +} + +void +ffscale(Image *img) +{ + const unsigned width = img->ximg->width; + const unsigned height = img->ximg->height; + unsigned char* newBuf = (unsigned char*)img->ximg->data; + const unsigned jdy = img->ximg->bytes_per_line / 4 - width; + + const double x_scale = ((double)img->bufwidth/(double)width); + const double y_scale = ((double)img->bufheight/(double)height); + + for (unsigned y = 0; y < height; ++y) { + const double old_y = (double)y * y_scale; + const double y_factor = ceil(old_y) - old_y; + const int old_y_int_0 = int_clamp((int)floor(old_y), 0, img->bufheight); + const int old_y_int_1 = int_clamp((int)ceil(old_y), 0, img->bufheight); + + for (unsigned x = 0; x < width; ++x) { + const double old_x = (double)x * x_scale; + const double x_factor = ceil(old_x) - old_x; + const int old_x_int_0 = int_clamp((int)floor(old_x), 0, img->bufwidth); + const int old_x_int_1 = int_clamp((int)ceil(old_x), 0, img->bufwidth); + + const unsigned c00_pos = 3*((old_x_int_0) + ((old_y_int_0)*img->bufwidth)); + const unsigned c01_pos = 3*((old_x_int_0) + ((old_y_int_1)*img->bufwidth)); + const unsigned c10_pos = 3*((old_x_int_1) + ((old_y_int_0)*img->bufwidth)); + const unsigned c11_pos = 3*((old_x_int_1) + ((old_y_int_1)*img->bufwidth)); + + for (int i = 2; i >= 0 ; --i) { + const unsigned char c00 = img->buf[c00_pos + i]; + const unsigned char c01 = img->buf[c01_pos + i]; + const unsigned char c10 = img->buf[c10_pos + i]; + const unsigned char c11 = img->buf[c11_pos + i]; + + const double x_result_0 = (double)c00*x_factor + (double)c10*(1.0 - x_factor); + const double x_result_1 = (double)c01*x_factor + (double)c11*(1.0 - x_factor); + const double result = x_result_0*y_factor + x_result_1*(1.0 - y_factor); + + *newBuf++ = double_to_uchar_clamp255(result); + } + newBuf++; + } + newBuf += jdy; + } +} + +void +ffdraw(Image *img) +{ + int xoffset = (xw.w - img->ximg->width) / 2; + int yoffset = (xw.h - img->ximg->height) / 2; + XPutImage(xw.dpy, xw.win, d->gc, img->ximg, 0, 0, + xoffset, yoffset, img->ximg->width, img->ximg->height); + XFlush(xw.dpy); +} + +void +getfontsize(Slide *s, unsigned int *width, unsigned int *height) +{ + int i, j; + unsigned int curw, newmax; + float lfac = linespacing * (s->linecount - 1) + 1; + + /* fit height */ + for (j = NUMFONTSCALES - 1; j >= 0; j--) + if (fonts[j]->h * lfac <= xw.uh) + break; + LIMIT(j, 0, NUMFONTSCALES - 1); + drw_setfontset(d, fonts[j]); + + /* fit width */ + *width = 0; + for (i = 0; i < s->linecount; i++) { + curw = drw_fontset_getwidth(d, s->lines[i]); + newmax = (curw >= *width); + while (j > 0 && curw > xw.uw) { + drw_setfontset(d, fonts[--j]); + curw = drw_fontset_getwidth(d, s->lines[i]); + } + if (newmax) + *width = curw; + } + *height = fonts[j]->h * lfac; +} + +void +cleanup(int slidesonly) +{ + unsigned int i, j; + + if (!slidesonly) { + for (i = 0; i < NUMFONTSCALES; i++) + drw_fontset_free(fonts[i]); + free(sc); + drw_free(d); + + XDestroyWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + XCloseDisplay(xw.dpy); + } + + if (slides) { + for (i = 0; i < slidecount; i++) { + for (j = 0; j < slides[i].linecount; j++) + free(slides[i].lines[j]); + free(slides[i].lines); + if (slides[i].img) + fffree(slides[i].img); + } + if (!slidesonly) { + free(slides); + slides = NULL; + } + } +} + +void +reload(const Arg *arg) +{ + FILE *fp = NULL; + unsigned int i; + + if (!fname) { + fprintf(stderr, "sent: Cannot reload from stdin. Use a file!\n"); + return; + } + + cleanup(1); + slidecount = 0; + + if (!(fp = fopen(fname, "r"))) + die("sent: Unable to open '%s' for reading:", fname); + load(fp); + fclose(fp); + + LIMIT(idx, 0, slidecount-1); + for (i = 0; i < slidecount; i++) + ffload(&slides[i]); + xdraw(); +} + +void +load(FILE *fp) +{ + static size_t size = 0; + size_t blen, maxlines; + char buf[BUFSIZ], *p; + Slide *s; + + /* read each line from fp and add it to the item list */ + while (1) { + /* eat consecutive empty lines */ + while ((p = fgets(buf, sizeof(buf), fp))) + if (strcmp(buf, "\n") != 0 && buf[0] != '#') + break; + if (!p) + break; + + if ((slidecount+1) * sizeof(*slides) >= size) + if (!(slides = realloc(slides, (size += BUFSIZ)))) + die("sent: Unable to reallocate %u bytes:", size); + + /* read one slide */ + maxlines = 0; + memset((s = &slides[slidecount]), 0, sizeof(Slide)); + do { + if (buf[0] == '#') + continue; + + /* grow lines array */ + if (s->linecount >= maxlines) { + maxlines = 2 * s->linecount + 1; + if (!(s->lines = realloc(s->lines, maxlines * sizeof(s->lines[0])))) + die("sent: Unable to reallocate %u bytes:", maxlines * sizeof(s->lines[0])); + } + + blen = strlen(buf); + if (!(s->lines[s->linecount] = strdup(buf))) + die("sent: Unable to strdup:"); + if (s->lines[s->linecount][blen-1] == '\n') + s->lines[s->linecount][blen-1] = '\0'; + + /* mark as image slide if first line of a slide starts with @ */ + if (s->linecount == 0 && s->lines[0][0] == '@') + s->embed = &s->lines[0][1]; + + if (s->lines[s->linecount][0] == '\\') + memmove(s->lines[s->linecount], &s->lines[s->linecount][1], blen); + s->linecount++; + } while ((p = fgets(buf, sizeof(buf), fp)) && strcmp(buf, "\n") != 0); + + slidecount++; + if (!p) + break; + } + + if (!slidecount) + die("sent: No slides in file"); +} + +void +advance(const Arg *arg) +{ + int new_idx = idx + arg->i; + LIMIT(new_idx, 0, slidecount-1); + if (new_idx != idx) { + if (slides[idx].img) + slides[idx].img->state &= ~SCALED; + idx = new_idx; + xdraw(); + } +} + +void +quit(const Arg *arg) +{ + running = 0; +} + +void +resize(int width, int height) +{ + xw.w = width; + xw.h = height; + xw.uw = usablewidth * width; + xw.uh = usableheight * height; + drw_resize(d, width, height); +} + +void +run() +{ + XEvent ev; + + /* Waiting for window mapping */ + while (1) { + XNextEvent(xw.dpy, &ev); + if (ev.type == ConfigureNotify) { + resize(ev.xconfigure.width, ev.xconfigure.height); + } else if (ev.type == MapNotify) { + break; + } + } + + while (running) { + XNextEvent(xw.dpy, &ev); + if (handler[ev.type]) + (handler[ev.type])(&ev); + } +} + +void +xdraw() +{ + unsigned int height, width, i; + Image *im = slides[idx].img; + + getfontsize(&slides[idx], &width, &height); + XClearWindow(xw.dpy, xw.win); + + if (!im) { + drw_rect(d, 0, 0, xw.w, xw.h, 1, 1); + for (i = 0; i < slides[idx].linecount; i++) + drw_text(d, + (xw.w - width) / 2, + (xw.h - height) / 2 + i * linespacing * d->fonts->h, + width, + d->fonts->h, + 0, + slides[idx].lines[i], + 0); + drw_map(d, xw.win, 0, 0, xw.w, xw.h); + } else { + if (!(im->state & SCALED)) + ffprepare(im); + ffdraw(im); + } +} + +void +xhints() +{ + XClassHint class = {.res_name = "sent", .res_class = "presenter"}; + XWMHints wm = {.flags = InputHint, .input = True}; + XSizeHints *sizeh = NULL; + + if (!(sizeh = XAllocSizeHints())) + die("sent: Unable to allocate size hints"); + + sizeh->flags = PSize; + sizeh->height = xw.h; + sizeh->width = xw.w; + + XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, &class); + XFree(sizeh); +} + +void +xinit() +{ + XTextProperty prop; + unsigned int i; + + if (!(xw.dpy = XOpenDisplay(NULL))) + die("sent: Unable to open display"); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + resize(DisplayWidth(xw.dpy, xw.scr), DisplayHeight(xw.dpy, xw.scr)); + + xw.attrs.bit_gravity = CenterGravity; + xw.attrs.event_mask = KeyPressMask | ExposureMask | StructureNotifyMask | + ButtonMotionMask | ButtonPressMask; + + xw.win = XCreateWindow(xw.dpy, XRootWindow(xw.dpy, xw.scr), 0, 0, + xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), + InputOutput, xw.vis, CWBitGravity | CWEventMask, + &xw.attrs); + + xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); + xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); + XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); + + if (!(d = drw_create(xw.dpy, xw.scr, xw.win, xw.w, xw.h))) + die("sent: Unable to create drawing context"); + sc = drw_scm_create(d, colors, 2); + drw_setscheme(d, sc); + XSetWindowBackground(xw.dpy, xw.win, sc[ColBg].pixel); + + xloadfonts(); + for (i = 0; i < slidecount; i++) + ffload(&slides[i]); + + XStringListToTextProperty(&argv0, 1, &prop); + XSetWMName(xw.dpy, xw.win, &prop); + XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); + XFree(prop.value); + XMapWindow(xw.dpy, xw.win); + xhints(); + XSync(xw.dpy, False); +} + +void +xloadfonts() +{ + int i, j; + char *fstrs[LEN(fontfallbacks)]; + + for (j = 0; j < LEN(fontfallbacks); j++) { + fstrs[j] = ecalloc(1, MAXFONTSTRLEN); + } + + for (i = 0; i < NUMFONTSCALES; i++) { + for (j = 0; j < LEN(fontfallbacks); j++) { + if (MAXFONTSTRLEN < snprintf(fstrs[j], MAXFONTSTRLEN, "%s:size=%d", fontfallbacks[j], FONTSZ(i))) + die("sent: Font string too long"); + } + if (!(fonts[i] = drw_fontset_create(d, (const char**)fstrs, LEN(fstrs)))) + die("sent: Unable to load any font for size %d", FONTSZ(i)); + } + + for (j = 0; j < LEN(fontfallbacks); j++) + if (fstrs[j]) + free(fstrs[j]); +} + +void +bpress(XEvent *e) +{ + unsigned int i; + + for (i = 0; i < LEN(mshortcuts); i++) + if (e->xbutton.button == mshortcuts[i].b && mshortcuts[i].func) + mshortcuts[i].func(&(mshortcuts[i].arg)); +} + +void +cmessage(XEvent *e) +{ + if (e->xclient.data.l[0] == xw.wmdeletewin) + running = 0; +} + +void +expose(XEvent *e) +{ + if (0 == e->xexpose.count) + xdraw(); +} + +void +kpress(XEvent *e) +{ + unsigned int i; + KeySym sym; + + sym = XkbKeycodeToKeysym(xw.dpy, (KeyCode)e->xkey.keycode, 0, 0); + for (i = 0; i < LEN(shortcuts); i++) + if (sym == shortcuts[i].keysym && shortcuts[i].func) + shortcuts[i].func(&(shortcuts[i].arg)); +} + +void +configure(XEvent *e) +{ + resize(e->xconfigure.width, e->xconfigure.height); + if (slides[idx].img) + slides[idx].img->state &= ~SCALED; + xdraw(); +} + +void +usage() +{ + die("usage: %s [file]", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp = NULL; + + ARGBEGIN { + case 'v': + fprintf(stderr, "sent-"VERSION"\n"); + return 0; + default: + usage(); + } ARGEND + + if (!argv[0] || !strcmp(argv[0], "-")) + fp = stdin; + else if (!(fp = fopen(fname = argv[0], "r"))) + die("sent: Unable to open '%s' for reading:", fname); + load(fp); + fclose(fp); + + xinit(); + run(); + + cleanup(0); + return 0; +} -- cgit v1.2.3