sent/sent.c
Markus Teich 4a828c2d6c prevent flickering when changing slides
When the window background is left at WhitePixel (from initialization) and the
user has set a dark background color in config.h, the window shortly displays
the white default background when clearing after advancing to another slide.
This was very disturbing and is fixed now by setting the default window
background color to the bgcol chosen in config.h.
2015-11-17 01:04:04 +01:00

696 lines
16 KiB
C

/* See LICENSE for licence details. */
#include <errno.h>
#include <math.h>
#include <png.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.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 "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,
LOADED = 1,
SCALED = 2,
DRAWN = 4
} imgstate;
typedef struct {
unsigned char *buf;
unsigned int bufwidth, bufheight;
imgstate state;
XImage *ximg;
FILE *f;
png_structp png_ptr;
png_infop info_ptr;
int numpasses;
} Image;
typedef struct {
unsigned int linecount;
char **lines;
Image *img;
} 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 Image *pngopen(char *filename);
static void pngfree(Image *img);
static int pngread(Image *img);
static int pngprepare(Image *img);
static void pngscale(Image *img);
static void pngdraw(Image *img);
static void getfontsize(Slide *s, unsigned int *width, unsigned int *height);
static void cleanup();
static void eprintf(const char *, ...);
static void die(const char *, ...);
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 Scm *sc;
static Fnt *fonts[NUMFONTSCALES];
static int running = 1;
static void (*handler[LASTEvent])(XEvent *) = {
[ButtonPress] = bpress,
[ClientMessage] = cmessage,
[ConfigureNotify] = configure,
[Expose] = expose,
[KeyPress] = kpress,
};
Image *pngopen(char *filename)
{
FILE *f;
unsigned char buf[8];
Image *img;
if (!(f = fopen(filename, "rb"))) {
eprintf("could not open file %s:", filename);
return NULL;
}
if (fread(buf, 1, 8, f) != 8 || png_sig_cmp(buf, 1, 8))
return NULL;
img = malloc(sizeof(Image));
memset(img, 0, sizeof(Image));
if (!(img->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
NULL, NULL))) {
free(img);
return NULL;
}
if (!(img->info_ptr = png_create_info_struct(img->png_ptr))
|| setjmp(png_jmpbuf(img->png_ptr))) {
pngfree(img);
return NULL;
}
img->f = f;
rewind(f);
png_init_io(img->png_ptr, f);
png_read_info(img->png_ptr, img->info_ptr);
img->bufwidth = png_get_image_width(img->png_ptr, img->info_ptr);
img->bufheight = png_get_image_height(img->png_ptr, img->info_ptr);
return img;
}
void pngfree(Image *img)
{
png_destroy_read_struct(&img->png_ptr, img->info_ptr ? &img->info_ptr : NULL, NULL);
free(img->buf);
if (img->ximg)
XDestroyImage(img->ximg);
free(img);
}
int pngread(Image *img)
{
unsigned int y;
png_bytepp row_pointers;
if (!img)
return 0;
if (img->state & LOADED)
return 2;
if (img->buf)
free(img->buf);
if (!(img->buf = malloc(3 * img->bufwidth * img->bufheight)))
return 0;
if (setjmp(png_jmpbuf(img->png_ptr))) {
png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL);
return 0;
}
{
int color_type = png_get_color_type(img->png_ptr, img->info_ptr);
int bit_depth = png_get_bit_depth(img->png_ptr, img->info_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_expand(img->png_ptr);
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand(img->png_ptr);
if (png_get_valid(img->png_ptr, img->info_ptr, PNG_INFO_tRNS))
png_set_expand(img->png_ptr);
if (bit_depth == 16)
png_set_strip_16(img->png_ptr);
if (color_type == PNG_COLOR_TYPE_GRAY
|| color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(img->png_ptr);
png_color_16 my_background = {.red = 0xff, .green = 0xff, .blue = 0xff};
png_color_16p image_background;
if (png_get_bKGD(img->png_ptr, img->info_ptr, &image_background))
png_set_background(img->png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);
else
png_set_background(img->png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 2, 1.0);
if (png_get_interlace_type(img->png_ptr, img->info_ptr) == PNG_INTERLACE_ADAM7)
img->numpasses = png_set_interlace_handling(img->png_ptr);
else
img->numpasses = 1;
png_read_update_info(img->png_ptr, img->info_ptr);
}
row_pointers = (png_bytepp)malloc(img->bufheight * sizeof(png_bytep));
for (y = 0; y < img->bufheight; y++)
row_pointers[y] = img->buf + y * img->bufwidth * 3;
png_read_image(img->png_ptr, row_pointers);
free(row_pointers);
png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL);
fclose(img->f);
img->state |= LOADED;
return 1;
}
int pngprepare(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) {
eprintf("display depths <24 not supported.");
return 0;
}
if (!(img->ximg = XCreateImage(xw.dpy, CopyFromParent, depth, ZPixmap, 0,
NULL, width, height, 32, 0))) {
eprintf("could not create XImage");
return 0;
}
if (!(img->ximg->data = malloc(img->ximg->bytes_per_line * height))) {
eprintf("could not alloc data section for XImage");
XDestroyImage(img->ximg);
img->ximg = NULL;
return 0;
}
if (!XInitImage(img->ximg)) {
eprintf("could not init XImage");
free(img->ximg->data);
XDestroyImage(img->ximg);
img->ximg = NULL;
return 0;
}
pngscale(img);
img->state |= SCALED;
return 1;
}
void pngscale(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 pngdraw(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);
img->state |= DRAWN;
}
void getfontsize(Slide *s, unsigned int *width, unsigned int *height)
{
int i, j;
unsigned int curw, imax;
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]);
if (curw >= *width)
imax = i;
while (j > 0 && curw > xw.uw) {
drw_setfontset(d, fonts[--j]);
curw = drw_fontset_getwidth(d, s->lines[i]);
}
if (imax == i)
*width = curw;
}
*height = fonts[j]->h * lfac;
*width += fonts[j]->h;
}
void cleanup()
{
unsigned int i, j;
for (i = 0; i < NUMFONTSCALES; i++)
drw_fontset_free(fonts[i]);
drw_scm_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)
pngfree(slides[i].img);
}
free(slides);
slides = NULL;
}
}
void die(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
}
exit(1);
}
void eprintf(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
}
}
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) {
if ((slidecount+1) * sizeof(*slides) >= size)
if (!(slides = realloc(slides, (size += BUFSIZ))))
die("cannot realloc %u bytes:", size);
/* eat consecutive empty lines */
while ((p = fgets(buf, sizeof(buf), fp)))
if (strcmp(buf, "\n") != 0 && buf[0] != '#')
break;
if (!p)
break;
/* 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("cannot realloc %u bytes:", maxlines * sizeof(s->lines[0]));
}
blen = strlen(buf);
if (!(s->lines[s->linecount] = strdup(buf)))
die("cannot strdup:");
if (s->lines[s->linecount][blen-1] == '\n')
s->lines[s->linecount][blen-1] = '\0';
/* only make image slide if first line of a slide starts with @ */
if (s->linecount == 0 && s->lines[0][0] == '@') {
memmove(s->lines[0], &s->lines[0][1], blen);
s->img = pngopen(s->lines[0]);
}
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 &= ~(DRAWN | SCALED);
idx = new_idx;
xdraw();
if (slidecount > idx + 1 && slides[idx + 1].img && !pngread(slides[idx + 1].img))
die("could not read image %s", slides[idx + 1].lines[0]);
if (0 < idx && slides[idx - 1].img && !pngread(slides[idx - 1].img))
die("could not read image %s", slides[idx - 1].lines[0]);
}
}
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 usage()
{
die("sent " VERSION " (c) 2014-2015 markus.teich@stusta.mhn.de\n" \
"usage: sent FILE1 [FILE2 ...]", argv0);
}
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,
slides[idx].lines[i],
0);
drw_map(d, xw.win, 0, 0, xw.w, xw.h);
} else if (!(im->state & LOADED) && !pngread(im)) {
eprintf("could not read image %s", slides[idx].lines[0]);
} else if (!(im->state & SCALED) && !pngprepare(im)) {
eprintf("could not prepare image %s for drawing", slides[idx].lines[0]);
} else if (!(im->state & DRAWN)) {
pngdraw(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: Could not alloc 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("Can't 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("Can't create drawing context.");
sc = drw_scm_create(d, fgcol, bgcol);
drw_setscheme(d, sc);
XSetWindowBackground(xw.dpy, xw.win, sc->bg.pix);
xloadfonts();
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("could not malloc fstrs");
}
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("font string too long");
}
fonts[i] = drw_fontset_create(d, (const char**)fstrs, LEN(fstrs));
}
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 &= ~(DRAWN | SCALED);
xdraw();
}
int main(int argc, char *argv[])
{
int i;
FILE *fp = NULL;
ARGBEGIN {
case 'v':
default:
usage();
} ARGEND;
for (i = 0; i < argc; i++) {
if ((fp = strcmp(argv[i], "-") ? fopen(argv[i], "r") : stdin)) {
load(fp);
fclose(fp);
} else {
eprintf("could not open %s for reading:", argv[i]);
}
}
if (!slides || !slides[0].lines)
usage();
xinit();
run();
cleanup();
return 0;
}