From 8b5710ff44ebabef1acae9783da8286d5366c74c Mon Sep 17 00:00:00 2001 From: Markus Teich Date: Mon, 23 Jun 2014 00:39:21 +0200 Subject: [PATCH] add stuff --- .gitignore | 7 + LICENSE | 4 +- Makefile | 56 ++++++ arg.h | 63 +++++++ config.def.h | 28 +++ config.mk | 25 +++ sent.c | 468 +++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 649 insertions(+), 2 deletions(-) create mode 100644 Makefile create mode 100644 arg.h create mode 100644 config.def.h create mode 100644 config.mk create mode 100644 sent.c diff --git a/.gitignore b/.gitignore index 4d40434..4c303b0 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,10 @@ *.i*86 *.x86_64 *.hex +/sent + +# vim +*.swp +*~ + +config.h diff --git a/LICENSE b/LICENSE index bdbdccd..35f3184 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014 schachmat +Copyright (c) 2014 markus.teich@stusta.mhn.de Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..0e9ea36 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +# sent - plain text presentation tool +# See LICENSE file for copyright and license details. + +include config.mk + +SRC = sent.c +OBJ = ${SRC:.c=.o} + +all: options sent cscope + +options: + @echo sent build options: + @echo "CFLAGS = ${CFLAGS}" + @echo "LDFLAGS = ${LDFLAGS}" + @echo "CC = ${CC}" + +config.h: + cp config.def.h config.h + +.c.o: + @echo CC $< + @${CC} -c ${CFLAGS} $< + +${OBJ}: config.h config.mk + +sent: ${OBJ} + @echo CC -o $@ + @${CC} -o $@ ${OBJ} ${LDFLAGS} + +cscope: ${SRC} config.h + @echo cScope + @cscope -R -b + +clean: + @echo cleaning + @rm -f sent ${OBJ} sent-${VERSION}.tar.gz + +dist: clean + @echo creating dist tarball + @mkdir -p sent-${VERSION} + @cp -R LICENSE Makefile config.mk config.def.h ${SRC} sent-${VERSION} + @tar -cf sent-${VERSION}.tar sent-${VERSION} + @gzip sent-${VERSION}.tar + @rm -rf sent-${VERSION} + +install: all + @echo installing executable file to ${DESTDIR}${PREFIX}/bin + @mkdir -p ${DESTDIR}${PREFIX}/bin + @cp -f sent ${DESTDIR}${PREFIX}/bin + @chmod 755 ${DESTDIR}${PREFIX}/bin/sent + +uninstall: + @echo removing executable file from ${DESTDIR}${PREFIX}/bin + @rm -f ${DESTDIR}${PREFIX}/bin/sent + +.PHONY: all options clean dist install uninstall cscope diff --git a/arg.h b/arg.h new file mode 100644 index 0000000..4df77a7 --- /dev/null +++ b/arg.h @@ -0,0 +1,63 @@ +/* + * Copy me if you can. + * by 20h + */ + +#ifndef ARG_H__ +#define ARG_H__ + +extern char *argv0; + +/* use main(int argc, char *argv[]) */ +#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ + argv[0] && argv[0][1]\ + && argv[0][0] == '-';\ + argc--, argv++) {\ + char argc_;\ + char **argv_;\ + int brk_;\ + if (argv[0][1] == '-' && argv[0][2] == '\0') {\ + argv++;\ + argc--;\ + break;\ + }\ + for (brk_ = 0, argv[0]++, argv_ = argv;\ + argv[0][0] && !brk_;\ + argv[0]++) {\ + if (argv_ != argv)\ + break;\ + argc_ = argv[0][0];\ + switch (argc_) + +/* Handles obsolete -NUM syntax */ +#define ARGNUM case '0':\ + case '1':\ + case '2':\ + case '3':\ + case '4':\ + case '5':\ + case '6':\ + case '7':\ + case '8':\ + case '9' + +#define ARGEND }\ + } + +#define ARGC() argc_ + +#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base))) + +#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\ + ((x), abort(), (char *)0) :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\ + (char *)0 :\ + (brk_ = 1, (argv[0][1] != '\0')?\ + (&argv[0][1]) :\ + (argc--, argv++, argv[0]))) + +#endif diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..ea2119f --- /dev/null +++ b/config.def.h @@ -0,0 +1,28 @@ +/* See LICENSE file for copyright and license details. */ + +static char font[] = "-*-dejavu sans condensed-bold-r-*-*-0-0-*-*-*-0-*-*"; +#define NUMFONTS 30 +#define FONTSZ(x) ((int)(100.0 * powf(1.1288, (x)))) /* x in [0, NUMFONTS-1] */ + +/* how much screen estate is to be used at max for the content */ +static float usablewidth = 0.75; +static float usableheight = 0.75; + +static Mousekey mshortcuts[] = { + /* button function argument */ + { Button1, advance, {.i = +1} }, + { Button2, advance, {.i = -1} }, +}; + +static Shortcut shortcuts[] = { + /* keysym function argument */ + { XK_q, quit, {0} }, + { XK_Right, advance, {.i = +1} }, + { XK_Left, advance, {.i = -1} }, + { XK_Return, advance, {.i = +1} }, + { XK_BackSpace, advance, {.i = -1} }, + { XK_Down, advance, {.i = +5} }, + { XK_Up, advance, {.i = -5} }, + { XK_Next, advance, {.i = +10} }, + { XK_Prior, advance, {.i = -10} }, +}; diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..2eb4d25 --- /dev/null +++ b/config.mk @@ -0,0 +1,25 @@ +# sent version +VERSION = 0.1 + +# Customize below to fit your system + +# paths +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +X11INC = /usr/X11R6/include +X11LIB = /usr/X11R6/lib + +# includes and libs +INCS = -I. -I/usr/include -I${X11INC} +LIBS = -L/usr/lib -lc -lm -L${X11LIB} -lX11 + +# flags +CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE -D_XOPEN_SOURCE=600 +CFLAGS += -g -std=c99 -pedantic -Wall ${INCS} ${CPPFLAGS} +LDFLAGS += -g ${LIBS} +#CFLAGS += -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} +#LDFLAGS += ${LIBS} + +# compiler and linker +CC ?= cc diff --git a/sent.c b/sent.c new file mode 100644 index 0000000..2e4c189 --- /dev/null +++ b/sent.c @@ -0,0 +1,468 @@ +/* See LICENSE for licence details. */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "arg.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) + +typedef struct { + char* text; +} Slide; + +/* Purely graphic info */ +typedef struct { + Display *dpy; + Window win; + Atom wmdeletewin, netwmname; + Visual *vis; + XSetWindowAttributes attrs; + int scr; + int w, h; +} XWindow; + +/* Drawing Context linked list*/ +struct DC{ + XFontStruct *font; + GC gc; + struct DC *next; +}; + +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; + +/* function definitions used in config.h */ +static void advance(const Arg *); +static void quit(const Arg *); + +/* config.h for applying patches and the configuration. */ +#include "config.h" + +static Bool xfontisscalable(char *name); +static XFontStruct *xloadqueryscalablefont(char *name, int size); +static struct DC *getfontsize(char *str, size_t len, int *width, int *height); +static void cleanup(struct DC *cur); +static void eprintf(const char *, ...); +static void load(FILE *fp); +static void advance(const Arg *arg); +static void quit(const Arg *arg); +static void run(); +static void usage(); +static void xdraw(); +static void xhints(); +static void xinit(); +static void xloadfonts(char *); + +static void bpress(XEvent *); +static void cmessage(XEvent *); +static void expose(XEvent *); +static void kpress(XEvent *); +static void resize(XEvent *); + +/* Globals */ +static Slide *slides = NULL; +static int idx = 0; +static int slidecount = 0; +static XWindow xw; +static struct DC dc; +static int running = 1; +static char *opt_font = NULL; + +static void (*handler[LASTEvent])(XEvent *) = { + [ButtonPress] = bpress, + [ClientMessage] = cmessage, + [ConfigureNotify] = resize, + [Expose] = expose, + [KeyPress] = kpress, +}; + + +Bool xfontisscalable(char *name) +{ + int i, field; + + if (!name || name[0] != '-') + return False; + + for (i = field = 0; name[i] != '\0'; i++) { + if (name[i] == '-') { + field++; + if ((field == 7) || (field == 8) || (field == 12)) + if ((name[i+1] != '0') || (name[i+2] != '-')) + return False; + } + } + return field == 14; +} + +XFontStruct *xloadqueryscalablefont(char *name, int size) +{ + int i, j, field; + char newname[500]; + int resx, resy; + + if (!name || name[0] != '-') + return NULL; + /* calculate our screen resolution in dots per inch. 25.4mm = 1 inch */ + resx = DisplayWidth(xw.dpy, xw.scr)/(DisplayWidthMM(xw.dpy, xw.scr)/25.4); + resy = DisplayHeight(xw.dpy, xw.scr)/(DisplayHeightMM(xw.dpy, xw.scr)/25.4); + /* copy the font name, changing the scalable fields as we do so */ + for (i = j = field = 0; name[i] != '\0' && field <= 14; i++) { + newname[j++] = name[i]; + if (name[i] == '-') { + field++; + switch (field) { + case 7: /* pixel size */ + case 12: /* average width */ + /* change from "-0-" to "-*-" */ + newname[j] = '*'; + j++; + if (name[i+1] != '\0') i++; + break; + case 8: /* point size */ + /* change from "-0-" to "--" */ + sprintf(&newname[j], "%d", size); + while (newname[j] != '\0') j++; + if (name[i+1] != '\0') i++; + break; + case 9: /* x-resolution */ + case 10: /* y-resolution */ + /* change from an unspecified resolution to resx or resy */ + sprintf(&newname[j], "%d", (field == 9) ? resx : resy); + while (newname[j] != '\0') j++; + while ((name[i+1] != '-') && (name[i+1] != '\0')) i++; + break; + } + } + } + newname[j] = '\0'; + return (field != 14) ? NULL : XLoadQueryFont(xw.dpy, newname); +} + +struct DC *getfontsize(char *str, size_t len, int *width, int *height) +{ + XCharStruct info; + int unused; + struct DC *pre = &dc; + struct DC *cur = &dc; + + do { + XTextExtents(cur->font, str, len, &unused, &unused, &unused, &info); + if (info.width > usablewidth * xw.w + || info.ascent + info.descent > usableheight * xw.h) + break; + pre = cur; + } while ((cur = cur->next)); + + XTextExtents(pre->font, "o", 1, &unused, &unused, &unused, &info); + *height = info.ascent; + *width = XTextWidth(pre->font, str, len); + return pre; +} + +void cleanup(struct DC *cur) +{ + XFreeFont(xw.dpy, cur->font); + XFreeGC(xw.dpy, cur->gc); + + if (cur->next) { + cleanup(cur->next); + cur->next = NULL; + } + + if (cur != &dc) { + free(cur); + return; + } + + XDestroyWindow(xw.dpy, xw.win); + XSync(xw.dpy, False); + XCloseDisplay(xw.dpy); + if (slides) { + free(slides); + slides = NULL; + } +} + +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); + } + exit(EXIT_FAILURE); +} + +void load(FILE *fp) +{ + static size_t size = 0; + char buf[BUFSIZ], *p; + size_t i; + + /* read each line from stdin and add it to the item list */ + for (i = slidecount; fgets(buf, sizeof(buf), fp); i++) { + if ((i+1) * sizeof(*slides) >= size) + if (!(slides = realloc(slides, (size += BUFSIZ)))) + eprintf("cannot realloc %u bytes:", size); + if ((p = strchr(buf, '\n'))) + *p = '\0'; + if (!(slides[i].text = strdup(buf))) + eprintf("cannot strdup %u bytes:", strlen(buf)+1); + } + if (slides) + slides[i].text = NULL; + slidecount = i; +} + +void advance(const Arg *arg) +{ + int new_idx = idx + arg->i; + LIMIT(new_idx, 0, slidecount-1); + if (new_idx != idx) { + idx = new_idx; + xdraw(); + } +} + +void quit(const Arg *arg) +{ + running = 0; +} + +void run() +{ + XEvent ev; + + /* Waiting for window mapping */ + while (1) { + XNextEvent(xw.dpy, &ev); + if (ev.type == ConfigureNotify) { + xw.w = ev.xconfigure.width; + xw.h = 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() +{ + eprintf("sent " VERSION " (c) 2014 markus.teich@stusta.mhn.de\n" \ + "usage: sent [-f font] FILE1 [FILE2 ...]", argv0); +} + +void xdraw() +{ + int line_len = strlen(slides[idx].text); + int height; + int width; + struct DC *dc = getfontsize(slides[idx].text, line_len, &width, &height); + + XClearWindow(xw.dpy, xw.win); + XDrawString(xw.dpy, xw.win, dc->gc, (xw.w - width)/2, (xw.h + height)/2, + slides[idx].text, line_len); +} + +void xhints() +{ + XClassHint class = {.res_name = "sent", .res_class = "presenter"}; + XWMHints wm = {.flags = InputHint, .input = True}; + XSizeHints *sizeh = NULL; + + if (!(sizeh = XAllocSizeHints())) + eprintf("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))) + eprintf("Can't open display."); + xw.scr = XDefaultScreen(xw.dpy); + xw.vis = XDefaultVisual(xw.dpy, xw.scr); + xw.w = DisplayWidth(xw.dpy, xw.scr); + xw.h = DisplayHeight(xw.dpy, xw.scr); + + xw.attrs.background_pixel = WhitePixel(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, + CWBackPixel | 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); + + xloadfonts(opt_font ? opt_font : font); + + 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(char *fontstr) +{ + int count = 0; + int i = 0; + XFontStruct *fnt; + XGCValues gcvalues; + struct DC *cur = &dc; + char **fstr = XListFonts(xw.dpy, fontstr, 42, &count); + + while (count-- && !xfontisscalable(fstr[count])) + ; /* nothing, just get first scalable font result */ + + if (count < 0) + eprintf("sent: could not find a scalable font matching %s", fontstr); + + memset(&gcvalues, 0, sizeof(gcvalues)); + + do { + if (!(fnt = xloadqueryscalablefont(fstr[count], FONTSZ(i)))) { + i++; + continue; + } + + cur->gc = XCreateGC(xw.dpy, XRootWindow(xw.dpy, xw.scr), 0, &gcvalues); + cur->font = fnt; + XSetFont(xw.dpy, cur->gc, fnt->fid); + XSetForeground(xw.dpy, cur->gc, BlackPixel(xw.dpy, xw.scr)); + cur->next = (++i < NUMFONTS) ? malloc(sizeof(struct DC)) : NULL; + cur = cur->next; + } while (cur && i < NUMFONTS); + + if (cur == &dc) + eprintf("sent: could not load fonts."); + + XFreeFontNames(fstr); +} + +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 resize(XEvent *e) +{ + xw.w = e->xconfigure.width; + xw.h = e->xconfigure.height; + xdraw(); +} + +int main(int argc, char *argv[]) +{ + int i; + FILE *fp = NULL; + + ARGBEGIN { + case 'f': + opt_font = EARGF(usage()); + break; + 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 file %s for reading:", argv[i]); + } + } + + if (!slides || !slides[0].text) + usage(); + + xinit(); + run(); + + cleanup(&dc); + return EXIT_SUCCESS; +}