sent/sent.c
Markus Teich dca314e7a9 always redraw images
Previously when covering the sent window with an image slide selected and
exposing it again, the image was lost. This change always redraws the image and
also removes the now redundant bit flag in the state enum.
2016-09-26 12:26:11 +02:00

685 lines
15 KiB
C

/* See LICENSE file for copyright and license details. */
#include <sys/types.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <regex.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <X11/keysym.h>
#include <X11/XKBlib.h>
#include <X11/Xatom.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xft/Xft.h>
#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();
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 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(&regex, filters[i].regex,
REG_NOSUB | REG_EXTENDED | REG_ICASE)) {
fprintf(stderr, "sent: Invalid regex '%s'\n", filters[i].regex);
continue;
}
if (!regexec(&regex, filename, 0, NULL, 0)) {
bin = filters[i].bin;
regfree(&regex);
break;
}
regfree(&regex);
}
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 = calloc(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 */
if (!(s->img->buf = malloc(3 * s->img->bufwidth * s->img->bufheight)))
die("sent: Unable to allocate buffer for image:");
/* scratch buffer to read row by row */
rowlen = s->img->bufwidth * 2 * strlen("RGBA");
row = malloc(rowlen);
if (!row)
die("sent: Unable to allocate buffer for image row:");
/* 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");
if (!(img->ximg->data = malloc(img->ximg->bytes_per_line * height)))
die("sent: Unable to allocate data section for XImage");
if (!XInitImage(img->ximg))
die("sent: Unable to initiate XImage");
ffscale(img);
img->state |= SCALED;
}
void
ffscale(Image *img)
{
unsigned int x, y;
unsigned int width = img->ximg->width;
unsigned int height = img->ximg->height;
char* newBuf = img->ximg->data;
unsigned char* ibuf;
unsigned int jdy = img->ximg->bytes_per_line / 4 - width;
unsigned int dx = (img->bufwidth << 10) / width;
for (y = 0; y < height; y++) {
unsigned int bufx = img->bufwidth / width;
ibuf = &img->buf[y * img->bufheight / height * img->bufwidth * 3];
for (x = 0; x < width; x++) {
*newBuf++ = (ibuf[(bufx >> 10)*3+2]);
*newBuf++ = (ibuf[(bufx >> 10)*3+1]);
*newBuf++ = (ibuf[(bufx >> 10)*3+0]);
newBuf++;
bufx += dx;
}
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()
{
unsigned int i, j;
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);
}
free(slides);
slides = NULL;
}
}
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;
}
}
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;
ffload(&slides[idx]);
xdraw();
if (slidecount > idx + 1)
ffload(&slides[idx + 1]);
if (0 < idx)
ffload(&slides[idx - 1]);
}
}
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;
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();
ffload(&slides[0]);
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++) {
if (!(fstrs[j] = malloc(MAXFONTSTRLEN)))
die("sent: Unable to allocate fontstring:");
}
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(argv[0], "r")))
die("sent: Unable to open '%s' for reading:", argv[0]);
load(fp);
fclose(fp);
if (!slidecount)
usage();
xinit();
run();
cleanup();
return 0;
}