pax_global_header00006660000000000000000000000064131530753060014515gustar00rootroot0000000000000052 comment=4736a6118d095db033843258248896649675faf2 .gitignore000066400000000000000000000003221315307530600130460ustar00rootroot00000000000000# Object files *.o *.ko *.obj *.elf # Libraries *.lib *.a # Shared objects (inc. Windows DLLs) *.dll *.so *.so.* *.dylib # Executables *.exe *.out *.app *.i*86 *.x86_64 *.hex /sent # vim *.swp *~ config.h LICENSE000066400000000000000000000024111315307530600120640ustar00rootroot00000000000000ISC-License (c) 2014-2016 Markus Teich Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. (c) 2015 Jonas Jelten (c) 2015 Szabolcs Nagy (c) 2015 Tony Lainson (c) 2015 Jan Christoph Ebersbach (c) 2015 Ivan Tham (c) 2015 Quentin Rameau (c) 2015 Alexis (c) 2015 Dimitris Papastamos (c) 2015 Grant Mathews (c) 2015 David Phillips (c) 2016 Laslo Hunhold (c) 2016 Hiltjo Posthuma Makefile000066400000000000000000000026001315307530600125170ustar00rootroot00000000000000# sent - plain text presentation tool # See LICENSE file for copyright and license details. include config.mk SRC = sent.c drw.c util.c OBJ = ${SRC:.c=.o} all: options sent 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 || echo cScope not installed 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 @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 @mkdir -p ${DESTDIR}${MANPREFIX}/man1 @cp sent.1 ${DESTDIR}${MANPREFIX}/man1/sent.1 @chmod 644 ${DESTDIR}${MANPREFIX}/man1/sent.1 uninstall: @echo removing executable file from ${DESTDIR}${PREFIX}/bin @rm -f ${DESTDIR}${PREFIX}/bin/sent .PHONY: all options clean dist install uninstall cscope README.md000066400000000000000000000025741315307530600123500ustar00rootroot00000000000000sent is a simple plaintext presentation tool. sent does not need latex, libreoffice or any other fancy file format, it uses plaintext files to describe the slides and can include images via farbfeld. Every paragraph represents a slide in the presentation. The presentation is displayed in a simple X11 window. The content of each slide is automatically scaled to fit the window and centered so you also don't have to worry about alignment. Instead you can really concentrate on the content. Dependencies You need Xlib and Xft to build sent and the farbfeld[0] tools installed to use images in your presentations. Demo To get a little demo, just type make && ./sent example You can navigate with the arrow keys and quit with `q`. Usage sent [FILE] If FILE is omitted or equals `-`, stdin will be read. Produce image slides by prepending a `@` in front of the filename as a single paragraph. Lines starting with `#` will be ignored. A `\` at the beginning of the line escapes `@` and `#`. A presentation file could look like this: sent @nyan.png depends on - Xlib - Xft - farbfeld sent FILENAME one slide per paragraph # This is a comment and will not be part of the presentation \# This and the next line start with backslashes \@FILE.png thanks / questions? Development sent is developed at http://tools.suckless.org/sent 0: http://tools.suckless.org/farbfeld/ arg.h000066400000000000000000000046131315307530600120070ustar00rootroot00000000000000/* * ISC-License * * Copyright 2017 Laslo Hunhold * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef ARG_H #define ARG_H extern char *argv0; /* int main(int argc, char *argv[]) */ #define ARGBEGIN for (argv0 = *argv, *argv ? (argc--, argv++) : ((void *)0); \ *argv && (*argv)[0] == '-' && (*argv)[1]; argc--, argv++) { \ int i_, argused_; \ if ((*argv)[1] == '-' && !(*argv)[2]) { \ argc--, argv++; \ break; \ } \ for (i_ = 1, argused_ = 0; (*argv)[i_]; i_++) { \ switch((*argv)[i_]) #define ARGEND if (argused_) { \ if ((*argv)[i_ + 1]) { \ break; \ } else { \ argc--, argv++; \ break; \ } \ } \ } \ } #define ARGC() ((*argv)[i_]) #define ARGF_(x) (((*argv)[i_ + 1]) ? (argused_ = 1, &((*argv)[i_ + 1])) : \ (*(argv + 1)) ? (argused_ = 1, *(argv + 1)) : (x)) #define EARGF(x) ARGF_(((x), exit(1), (char *)0)) #define ARGF() ARGF_((char *)0) #endif config.def.h000066400000000000000000000034731315307530600132430ustar00rootroot00000000000000/* See LICENSE file for copyright and license details. */ static char *fontfallbacks[] = { "dejavu sans", "roboto", "ubuntu", }; #define NUMFONTSCALES 42 #define FONTSZ(x) ((int)(10.0 * powf(1.1288, (x)))) /* x in [0, NUMFONTSCALES-1] */ static const char *colors[] = { "#000000", /* foreground color */ "#FFFFFF", /* background color */ }; static const float linespacing = 1.4; /* how much screen estate is to be used at max for the content */ static const float usablewidth = 0.75; static const float usableheight = 0.75; static Mousekey mshortcuts[] = { /* button function argument */ { Button1, advance, {.i = +1} }, { Button3, advance, {.i = -1} }, { Button4, advance, {.i = -1} }, { Button5, advance, {.i = +1} }, }; static Shortcut shortcuts[] = { /* keysym function argument */ { XK_Escape, quit, {0} }, { XK_q, quit, {0} }, { XK_Right, advance, {.i = +1} }, { XK_Left, advance, {.i = -1} }, { XK_Return, advance, {.i = +1} }, { XK_space, advance, {.i = +1} }, { XK_BackSpace, advance, {.i = -1} }, { XK_l, advance, {.i = +1} }, { XK_h, advance, {.i = -1} }, { XK_j, advance, {.i = +1} }, { XK_k, advance, {.i = -1} }, { XK_Down, advance, {.i = +1} }, { XK_Up, advance, {.i = -1} }, { XK_Next, advance, {.i = +1} }, { XK_Prior, advance, {.i = -1} }, { XK_n, advance, {.i = +1} }, { XK_p, advance, {.i = -1} }, { XK_r, reload, {0} }, }; static Filter filters[] = { { "\\.ff$", "cat" }, { "\\.ff.bz2$", "bunzip2" }, { "\\.[a-z0-9]+$", "2ff" }, }; config.mk000066400000000000000000000014631315307530600126630ustar00rootroot00000000000000# sent version VERSION = 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/usr/include/freetype2 -I${X11INC} LIBS = -L/usr/lib -lc -lm -L${X11LIB} -lXft -lfontconfig -lX11 # OpenBSD (uncomment) #INCS = -I. -I${X11INC} -I${X11INC}/freetype2 # FreeBSD (uncomment) #INCS = -I. -I/usr/local/include -I/usr/local/include/freetype2 -I${X11INC} #LIBS = -L/usr/local/lib -lc -lm -L${X11LIB} -lXft -lfontconfig -lX11 # flags CPPFLAGS = -DVERSION=\"${VERSION}\" -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 drw.c000066400000000000000000000232501315307530600120230ustar00rootroot00000000000000/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include "drw.h" #include "util.h" #define UTF_INVALID 0xFFFD #define UTF_SIZ 4 static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; static long utf8decodebyte(const char c, size_t *i) { for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) return (unsigned char)c & ~utfmask[*i]; return 0; } static size_t utf8validate(long *u, size_t i) { if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) *u = UTF_INVALID; for (i = 1; *u > utfmax[i]; ++i) ; return i; } static size_t utf8decode(const char *c, long *u, size_t clen) { size_t i, j, len, type; long udecoded; *u = UTF_INVALID; if (!clen) return 0; udecoded = utf8decodebyte(c[0], &len); if (!BETWEEN(len, 1, UTF_SIZ)) return 1; for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); if (type) return j; } if (j < len) return 0; *u = udecoded; utf8validate(u, len); return len; } Drw * drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) { Drw *drw = ecalloc(1, sizeof(Drw)); drw->dpy = dpy; drw->screen = screen; drw->root = root; drw->w = w; drw->h = h; drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); drw->gc = XCreateGC(dpy, root, 0, NULL); XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); return drw; } void drw_resize(Drw *drw, unsigned int w, unsigned int h) { if (!drw) return; drw->w = w; drw->h = h; if (drw->drawable) XFreePixmap(drw->dpy, drw->drawable); drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); } void drw_free(Drw *drw) { XFreePixmap(drw->dpy, drw->drawable); XFreeGC(drw->dpy, drw->gc); free(drw); } /* This function is an implementation detail. Library users should use * drw_fontset_create instead. */ static Fnt * xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) { Fnt *font; XftFont *xfont = NULL; FcPattern *pattern = NULL; if (fontname) { /* Using the pattern found at font->xfont->pattern does not yield the * same substitution results as using the pattern returned by * FcNameParse; using the latter results in the desired fallback * behaviour whereas the former just results in missing-character * rectangles being drawn, at least with some fonts. */ if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); return NULL; } if (!(pattern = FcNameParse((FcChar8 *) fontname))) { fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); XftFontClose(drw->dpy, xfont); return NULL; } } else if (fontpattern) { if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { fprintf(stderr, "error, cannot load font from pattern.\n"); return NULL; } } else { die("no font specified."); } font = ecalloc(1, sizeof(Fnt)); font->xfont = xfont; font->pattern = pattern; font->h = xfont->ascent + xfont->descent; font->dpy = drw->dpy; return font; } static void xfont_free(Fnt *font) { if (!font) return; if (font->pattern) FcPatternDestroy(font->pattern); XftFontClose(font->dpy, font->xfont); free(font); } Fnt* drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) { Fnt *cur, *ret = NULL; size_t i; if (!drw || !fonts) return NULL; for (i = 1; i <= fontcount; i++) { if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { cur->next = ret; ret = cur; } } return (drw->fonts = ret); } void drw_fontset_free(Fnt *font) { if (font) { drw_fontset_free(font->next); xfont_free(font); } } void drw_clr_create(Drw *drw, Clr *dest, const char *clrname) { if (!drw || !dest || !clrname) return; if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), DefaultColormap(drw->dpy, drw->screen), clrname, dest)) die("error, cannot allocate color '%s'", clrname); } /* Wrapper to create color schemes. The caller has to call free(3) on the * returned color scheme when done using it. */ Clr * drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) { size_t i; Clr *ret; /* need at least two colors for a scheme */ if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) return NULL; for (i = 0; i < clrcount; i++) drw_clr_create(drw, &ret[i], clrnames[i]); return ret; } void drw_setfontset(Drw *drw, Fnt *set) { if (drw) drw->fonts = set; } void drw_setscheme(Drw *drw, Clr *scm) { if (drw) drw->scheme = scm; } void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) { if (!drw || !drw->scheme) return; XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); if (filled) XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); else XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); } int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) { char buf[1024]; int ty; unsigned int ew; XftDraw *d = NULL; Fnt *usedfont, *curfont, *nextfont; size_t i, len; int utf8strlen, utf8charlen, render = x || y || w || h; long utf8codepoint = 0; const char *utf8str; FcCharSet *fccharset; FcPattern *fcpattern; FcPattern *match; XftResult result; int charexists = 0; if (!drw || (render && !drw->scheme) || !text || !drw->fonts) return 0; if (!render) { w = ~w; } else { XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); d = XftDrawCreate(drw->dpy, drw->drawable, DefaultVisual(drw->dpy, drw->screen), DefaultColormap(drw->dpy, drw->screen)); x += lpad; w -= lpad; } usedfont = drw->fonts; while (1) { utf8strlen = 0; utf8str = text; nextfont = NULL; while (*text) { utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); for (curfont = drw->fonts; curfont; curfont = curfont->next) { charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); if (charexists) { if (curfont == usedfont) { utf8strlen += utf8charlen; text += utf8charlen; } else { nextfont = curfont; } break; } } if (!charexists || nextfont) break; else charexists = 0; } if (utf8strlen) { drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); /* shorten text if necessary */ for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) drw_font_getexts(usedfont, utf8str, len, &ew, NULL); if (len) { memcpy(buf, utf8str, len); buf[len] = '\0'; if (len < utf8strlen) for (i = len; i && i > len - 3; buf[--i] = '.') ; /* NOP */ if (render) { ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], usedfont->xfont, x, ty, (XftChar8 *)buf, len); } x += ew; w -= ew; } } if (!*text) { break; } else if (nextfont) { charexists = 0; usedfont = nextfont; } else { /* Regardless of whether or not a fallback font is found, the * character must be drawn. */ charexists = 1; fccharset = FcCharSetCreate(); FcCharSetAddChar(fccharset, utf8codepoint); if (!drw->fonts->pattern) { /* Refer to the comment in xfont_create for more information. */ die("the first font in the cache must be loaded from a font string."); } fcpattern = FcPatternDuplicate(drw->fonts->pattern); FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); FcDefaultSubstitute(fcpattern); match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); FcCharSetDestroy(fccharset); FcPatternDestroy(fcpattern); if (match) { usedfont = xfont_create(drw, NULL, match); if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { for (curfont = drw->fonts; curfont->next; curfont = curfont->next) ; /* NOP */ curfont->next = usedfont; } else { xfont_free(usedfont); usedfont = drw->fonts; } } } } if (d) XftDrawDestroy(d); return x + (render ? w : 0); } void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) { if (!drw) return; XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); XSync(drw->dpy, False); } unsigned int drw_fontset_getwidth(Drw *drw, const char *text) { if (!drw || !drw->fonts || !text) return 0; return drw_text(drw, 0, 0, 0, 0, 0, text, 0); } void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) { XGlyphInfo ext; if (!font || !text) return; XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); if (w) *w = ext.xOff; if (h) *h = font->h; } Cur * drw_cur_create(Drw *drw, int shape) { Cur *cur; if (!drw || !(cur = ecalloc(1, sizeof(Cur)))) return NULL; cur->cursor = XCreateFontCursor(drw->dpy, shape); return cur; } void drw_cur_free(Drw *drw, Cur *cursor) { if (!cursor) return; XFreeCursor(drw->dpy, cursor->cursor); free(cursor); } drw.h000066400000000000000000000031731315307530600120320ustar00rootroot00000000000000/* See LICENSE file for copyright and license details. */ typedef struct { Cursor cursor; } Cur; typedef struct Fnt { Display *dpy; unsigned int h; XftFont *xfont; FcPattern *pattern; struct Fnt *next; } Fnt; enum { ColFg, ColBg }; /* Clr scheme index */ typedef XftColor Clr; typedef struct { unsigned int w, h; Display *dpy; int screen; Window root; Drawable drawable; GC gc; Clr *scheme; Fnt *fonts; } Drw; /* Drawable abstraction */ Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); void drw_resize(Drw *drw, unsigned int w, unsigned int h); void drw_free(Drw *drw); /* Fnt abstraction */ Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); void drw_fontset_free(Fnt* set); unsigned int drw_fontset_getwidth(Drw *drw, const char *text); void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); /* Colorscheme abstraction */ void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); /* Cursor abstraction */ Cur *drw_cur_create(Drw *drw, int shape); void drw_cur_free(Drw *drw, Cur *cursor); /* Drawing context manipulation */ void drw_setfontset(Drw *drw, Fnt *set); void drw_setscheme(Drw *drw, Clr *scm); /* Drawing functions */ void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); /* Map functions */ void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); example000066400000000000000000000024451315307530600124440ustar00rootroot00000000000000sent Origin: Takahashi Why? • PPTX sucks • LATEX sucks • PDF sucks also: terminal presentations don't support images… @nyan.png this text will not be displayed, since the @ at the start of the first line makes this paragraph an image slide. easy to use depends on ♽ Xlib ☢ Xft ☃ farbfeld ~1000 lines of code usage: $ sent FILE1 [FILE2 …] ▸ one slide per paragraph ▸ lines starting with # are ignored ▸ image slide: paragraph containing @FILENAME ▸ empty slide: just use a \ as a paragraph # This is a comment and will not be part of the presentation # multiple empty lines between paragraphs are also ignored # The following lines should produce # one empty slide \ \ \@this_line_actually_started_with_a_\.png \#This line as well ⇒ Prepend a backslash to kill behaviour of special characters Images are handled in the http://tools.suckless.org/farbfeld/ format internally. sent also supports transparent images. Try changing the background in config.h and rebuild. @transparent_test.ff 😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏 😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟 😠😡😢😣😥😦😧😨😩😪😫😭😮😯😰😱 😲😳😴😵😶😷😸😹😺😻😼😽😾😿🙀☠ thanks. questions? nyan.png000066400000000000000000000016051315307530600125360ustar00rootroot00000000000000PNG  IHDRC[gAMA a cHRMz&u0`:pQ<PLTE̙3bKGDH oFFs| vpAg^\_VIDATx0Q @lha/a3F Zz$YO4,]cZ+++++++++++XŬ[.7_+V{VVVVVVVVVVVVfkLeÇa?YYYYYYYYYYYYYc=UAVլcZuHi> Zo-YYYYYYYYYY_:-zI_#Z?2)++++++++++kt%Ri>:Rj,t(Md]Z;ZN9VVVVVVVVVVV-[ǨkqMeZ83X7dXɄsh(a!VVVVVVVVV~>Z8lȡf+k+++++++++klJ fi˖ceeeeeeeeeezw kњ|_9$֥++++++++++놭ˌp>+:d S]Pl=_gXYYYYYYYYYYi(=] ^3ceeeeeeeeeeeݔ5yϓXYYY{kf>oѐK:ִcWy5beeeeeeeeeee}M-^J%tEXtdate:create2014-06-29T23:14:20+02:00ήA%tEXtdate:modify2014-06-29T23:09:18+02:006IENDB`sent.1000066400000000000000000000034121315307530600121140ustar00rootroot00000000000000.Dd 2016-08-12 .Dt SENT 1 .Sh NAME .Nm sent .Nd simple plaintext presentation tool .Sh SYNOPSIS .Nm .Op Fl v .Op Ar file .Sh DESCRIPTION .Nm is a simple plain text presentation tool for X. sent does not need LaTeX, LibreOffice or any other fancy file format. Instead, sent reads plain text describing the slides. sent can also draw images. .Pp Every paragraph represents a slide in the presentation. Especially for presentations using the Takahashi method this is very nice and allows you to write the presentation for a quick lightning talk within a few minutes. .Sh OPTIONS .Bl -tag -width Ds .It Fl v Print version information to stdout and exit. .El .Sh USAGE .Bl -tag -width Ds .It Em Mouse commands .Bl -tag -width Ds .It Sy Button1 | Button5 Go to next slide, if existent. .It Sy Button3 | Button4 Go to previous slide, if existent. .El .It Em Keyboard commands .Bl -tag -width Ds .It Sy Escape | q Quit. .It Sy r Reload the slides. Only works on file input. .It Sy Right | Return | Space | l | j | Down | Next | n Go to next slide, if existent. .It Sy Left | Backspace | h | k | Up | Prior | p Go to previous slide, if existent. .El .El .Sh FORMAT The presentation file is made up of at least one paragraph, with an empty line separating two slides. Each input line is interpreted literally, except from control characters at the beginning of lines described as follows: .Bl -tag -width Ds .It Sy @ Create individual slide containing the image pointed to by the filename following the .Sy @ . .It Sy # Ignore this input line. .It Sy \e Create input line using the characters following the .Sy \e without interpreting them. .El .Sh CUSTOMIZATION .Nm can be customized by creating a custom config.h and (re)compiling the source code. This keeps it fast, secure and simple. .Sh SEE ALSO .Xr 2ff 1 sent.c000066400000000000000000000362111315307530600122010ustar00rootroot00000000000000/* 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; } 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(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; } } 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); if (!slidecount) usage(); xinit(); run(); cleanup(0); return 0; } transparent_test.ff000066400000000000000000000470601315307530600150050ustar00rootroot00000000000000farbfeld22util.c000066400000000000000000000010051315307530600121760ustar00rootroot00000000000000/* See LICENSE file for copyright and license details. */ #include #include #include #include #include "util.h" void * ecalloc(size_t nmemb, size_t size) { void *p; if (!(p = calloc(nmemb, size))) die("calloc:"); return p; } void die(const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if (fmt[0] && fmt[strlen(fmt)-1] == ':') { fputc(' ', stderr); perror(NULL); } else { fputc('\n', stderr); } exit(1); } util.h000066400000000000000000000004611315307530600122100ustar00rootroot00000000000000/* See LICENSE file for copyright and license details. */ #define MAX(A, B) ((A) > (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B)) #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) void die(const char *fmt, ...); void *ecalloc(size_t nmemb, size_t size);