/* 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; }