├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── arg.h ├── config.def.h ├── config.mk ├── drw.c ├── drw.h ├── example ├── nyan.png ├── sent.1 ├── sent.c ├── transparent_test.ff ├── util.c └── util.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | *.obj 5 | *.elf 6 | 7 | # Libraries 8 | *.lib 9 | *.a 10 | 11 | # Shared objects (inc. Windows DLLs) 12 | *.dll 13 | *.so 14 | *.so.* 15 | *.dylib 16 | 17 | # Executables 18 | *.exe 19 | *.out 20 | *.app 21 | *.i*86 22 | *.x86_64 23 | *.hex 24 | /sent 25 | 26 | # vim 27 | *.swp 28 | *~ 29 | 30 | config.h 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC-License 2 | 3 | (c) 2014-2017 Markus Teich 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | 17 | (c) 2016,2017 Laslo Hunhold 18 | (c) 2016 ssd 19 | (c) 2016 Hiltjo Posthuma 20 | (c) 2015 David Phillips 21 | (c) 2015 Grant Mathews 22 | (c) 2015 Dimitris Papastamos 23 | (c) 2015 Alexis 24 | (c) 2015 Quentin Rameau 25 | (c) 2015 Ivan Tham 26 | (c) 2015 Jan Christoph Ebersbach 27 | (c) 2015 Tony Lainson 28 | (c) 2015 Szabolcs Nagy 29 | (c) 2015 Jonas Jelten 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # sent - plain text presentation tool 2 | # See LICENSE file for copyright and license details. 3 | 4 | include config.mk 5 | 6 | SRC = sent.c drw.c util.c 7 | OBJ = ${SRC:.c=.o} 8 | 9 | all: options sent 10 | 11 | options: 12 | @echo sent build options: 13 | @echo "CFLAGS = ${CFLAGS}" 14 | @echo "LDFLAGS = ${LDFLAGS}" 15 | @echo "CC = ${CC}" 16 | 17 | config.h: 18 | cp config.def.h config.h 19 | 20 | .c.o: 21 | @echo CC $< 22 | @${CC} -c ${CFLAGS} $< 23 | 24 | ${OBJ}: config.h config.mk 25 | 26 | sent: ${OBJ} 27 | @echo CC -o $@ 28 | @${CC} -o $@ ${OBJ} ${LDFLAGS} 29 | 30 | cscope: ${SRC} config.h 31 | @echo cScope 32 | @cscope -R -b || echo cScope not installed 33 | 34 | clean: 35 | @echo cleaning 36 | @rm -f sent ${OBJ} sent-${VERSION}.tar.gz 37 | 38 | dist: clean 39 | @echo creating dist tarball 40 | @mkdir -p sent-${VERSION} 41 | @cp -R LICENSE Makefile config.mk config.def.h ${SRC} sent-${VERSION} 42 | @tar -cf sent-${VERSION}.tar sent-${VERSION} 43 | @gzip sent-${VERSION}.tar 44 | @rm -rf sent-${VERSION} 45 | 46 | install: all 47 | @echo installing executable file to ${DESTDIR}${PREFIX}/bin 48 | @mkdir -p ${DESTDIR}${PREFIX}/bin 49 | @cp -f sent ${DESTDIR}${PREFIX}/bin 50 | @chmod 755 ${DESTDIR}${PREFIX}/bin/sent 51 | @echo installing manual page to ${DESTDIR}${MANPREFIX}/man1 52 | @mkdir -p ${DESTDIR}${MANPREFIX}/man1 53 | @cp sent.1 ${DESTDIR}${MANPREFIX}/man1/sent.1 54 | @chmod 644 ${DESTDIR}${MANPREFIX}/man1/sent.1 55 | 56 | uninstall: 57 | @echo removing executable file from ${DESTDIR}${PREFIX}/bin 58 | @rm -f ${DESTDIR}${PREFIX}/bin/sent 59 | 60 | .PHONY: all options clean dist install uninstall cscope 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | _sent_ is a simple plaintext presentation tool. 2 | 3 | _sent_ does not need Latex, LibreOffice or any other fancy file format. 4 | It uses plaintext files to describe the slides and can include images via farbfeld. 5 | Every paragraph represents a slide in the presentation. 6 | 7 | The presentation is displayed in a simple X11 window. 8 | The content of each slide is automatically scaled to fit the window and centered, 9 | so you also don't have to worry about alignment. 10 | Instead you can really concentrate on the content. 11 | 12 | ### Dependencies 13 | 14 | You need _Xlib_ and _Xft_ to build _sent_, 15 | and the [farbfeld][0] tools installed to use images in your presentations. 16 | 17 | ### Demo 18 | 19 | To get a little demo, just type 20 | 21 | ```bash 22 | make && ./sent example 23 | ``` 24 | 25 | You can navigate with the arrow keys and quit with `q`. 26 | 27 | 28 | ### Usage 29 | 30 | ```bash 31 | sent [FILE] 32 | sent -h 33 | sent -v 34 | ``` 35 | 36 | If `FILE` is omitted or equals `-`, `stdin` will be read. 37 | Produce image slides by prepending a `@` in front of the filename as a single paragraph. 38 | Lines starting with `#` will be ignored. 39 | A `\` at the beginning of the line escapes `@` and `#`. 40 | 41 | A presentation file could look like this: 42 | 43 | ```sent 44 | sent 45 | 46 | @nyan.png 47 | 48 | depends on 49 | - Xlib 50 | - Xft 51 | - farbfeld 52 | 53 | sent FILENAME 54 | one slide per paragraph 55 | # This is a comment and will not be part of the presentation 56 | \# This and the next line start with backslashes 57 | 58 | \@FILE.png 59 | 60 | thanks / questions? 61 | ``` 62 | 63 | ### Development 64 | 65 | sent is developed at 66 | 67 | 68 | [0]: http://tools.suckless.org/farbfeld/ 69 | -------------------------------------------------------------------------------- /arg.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ISC-License 3 | * 4 | * Copyright 2017 Laslo Hunhold 5 | * 6 | * Permission to use, copy, modify, and/or distribute this software for any 7 | * purpose with or without fee is hereby granted, provided that the above 8 | * copyright notice and this permission notice appear in all copies. 9 | * 10 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | */ 18 | #ifndef ARG_H 19 | #define ARG_H 20 | 21 | extern char *argv0; 22 | 23 | /* int main(int argc, char *argv[]) */ 24 | #define ARGBEGIN for (argv0 = *argv, *argv ? (argc--, argv++) : ((void *)0); \ 25 | *argv && (*argv)[0] == '-' && (*argv)[1]; argc--, argv++) { \ 26 | int i_, argused_; \ 27 | if ((*argv)[1] == '-' && !(*argv)[2]) { \ 28 | argc--, argv++; \ 29 | break; \ 30 | } \ 31 | for (i_ = 1, argused_ = 0; (*argv)[i_]; i_++) { \ 32 | switch((*argv)[i_]) 33 | #define ARGEND if (argused_) { \ 34 | if ((*argv)[i_ + 1]) { \ 35 | break; \ 36 | } else { \ 37 | argc--, argv++; \ 38 | break; \ 39 | } \ 40 | } \ 41 | } \ 42 | } 43 | #define ARGC() ((*argv)[i_]) 44 | #define ARGF_(x) (((*argv)[i_ + 1]) ? (argused_ = 1, &((*argv)[i_ + 1])) : \ 45 | (*(argv + 1)) ? (argused_ = 1, *(argv + 1)) : (x)) 46 | #define EARGF(x) ARGF_(((x), exit(1), (char *)0)) 47 | #define ARGF() ARGF_((char *)0) 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /config.def.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | static char *fontfallbacks[] = { 4 | "dejavu sans", 5 | "roboto", 6 | "ubuntu", 7 | }; 8 | #define NUMFONTSCALES 42 9 | #define FONTSZ(x) ((int)(10.0 * powf(1.1288, (x)))) /* x in [0, NUMFONTSCALES-1] */ 10 | 11 | static const char *colors[] = { 12 | "#000000", /* foreground color */ 13 | "#FFFFFF", /* background color */ 14 | }; 15 | 16 | static const float linespacing = 1.4; 17 | 18 | /* how much screen estate is to be used at max for the content */ 19 | static const float usablewidth = 0.75; 20 | static const float usableheight = 0.75; 21 | 22 | static Mousekey mshortcuts[] = { 23 | /* button function argument */ 24 | { Button1, advance, {.i = +1} }, 25 | { Button3, advance, {.i = -1} }, 26 | { Button4, advance, {.i = -1} }, 27 | { Button5, advance, {.i = +1} }, 28 | }; 29 | 30 | static Shortcut shortcuts[] = { 31 | /* keysym function argument */ 32 | { XK_Escape, quit, {0} }, 33 | { XK_q, quit, {0} }, 34 | { XK_Right, advance, {.i = +1} }, 35 | { XK_Left, advance, {.i = -1} }, 36 | { XK_Return, advance, {.i = +1} }, 37 | { XK_space, advance, {.i = +1} }, 38 | { XK_BackSpace, advance, {.i = -1} }, 39 | { XK_l, advance, {.i = +1} }, 40 | { XK_h, advance, {.i = -1} }, 41 | { XK_j, advance, {.i = +1} }, 42 | { XK_k, advance, {.i = -1} }, 43 | { XK_Down, advance, {.i = +1} }, 44 | { XK_Up, advance, {.i = -1} }, 45 | { XK_Next, advance, {.i = +1} }, 46 | { XK_Prior, advance, {.i = -1} }, 47 | { XK_n, advance, {.i = +1} }, 48 | { XK_p, advance, {.i = -1} }, 49 | { XK_r, reload, {0} }, 50 | }; 51 | 52 | static Filter filters[] = { 53 | { "\\.ff$", "cat" }, 54 | { "\\.ff.bz2$", "bunzip2" }, 55 | { "\\.[a-z0-9]+$", "2ff" }, 56 | }; 57 | -------------------------------------------------------------------------------- /config.mk: -------------------------------------------------------------------------------- 1 | # sent version 2 | VERSION = 1 3 | 4 | # Customize below to fit your system 5 | 6 | # paths 7 | PREFIX = /usr/local 8 | MANPREFIX = ${PREFIX}/share/man 9 | 10 | X11INC = /usr/X11R6/include 11 | X11LIB = /usr/X11R6/lib 12 | 13 | # includes and libs 14 | INCS = -I. -I/usr/include -I/usr/include/freetype2 -I${X11INC} 15 | LIBS = -L/usr/lib -lc -lm -L${X11LIB} -lXft -lfontconfig -lX11 16 | # OpenBSD (uncomment) 17 | #INCS = -I. -I${X11INC} -I${X11INC}/freetype2 18 | # FreeBSD (uncomment) 19 | #INCS = -I. -I/usr/local/include -I/usr/local/include/freetype2 -I${X11INC} 20 | #LIBS = -L/usr/local/lib -lc -lm -L${X11LIB} -lXft -lfontconfig -lX11 21 | 22 | # flags 23 | CPPFLAGS = -DVERSION=\"${VERSION}\" -D_XOPEN_SOURCE=600 24 | CFLAGS += -g -std=c99 -pedantic -Wall ${INCS} ${CPPFLAGS} 25 | LDFLAGS += -g ${LIBS} 26 | #CFLAGS += -std=c99 -pedantic -Wall -Os ${INCS} ${CPPFLAGS} 27 | #LDFLAGS += ${LIBS} 28 | 29 | # compiler and linker 30 | CC ?= cc 31 | -------------------------------------------------------------------------------- /drw.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "drw.h" 9 | #include "util.h" 10 | 11 | #define UTF_INVALID 0xFFFD 12 | #define UTF_SIZ 4 13 | 14 | static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 15 | static const unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8}; 16 | static const long utfmin[UTF_SIZ + 1] = { 0, 0, 0x80, 0x800, 0x10000}; 17 | static const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF}; 18 | 19 | static long 20 | utf8decodebyte(const char c, size_t *i) 21 | { 22 | for (*i = 0; *i < (UTF_SIZ + 1); ++(*i)) 23 | if (((unsigned char)c & utfmask[*i]) == utfbyte[*i]) 24 | return (unsigned char)c & ~utfmask[*i]; 25 | return 0; 26 | } 27 | 28 | static size_t 29 | utf8validate(long *u, size_t i) 30 | { 31 | if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) 32 | *u = UTF_INVALID; 33 | for (i = 1; *u > utfmax[i]; ++i) 34 | ; 35 | return i; 36 | } 37 | 38 | static size_t 39 | utf8decode(const char *c, long *u, size_t clen) 40 | { 41 | size_t i, j, len, type; 42 | long udecoded; 43 | 44 | *u = UTF_INVALID; 45 | if (!clen) 46 | return 0; 47 | udecoded = utf8decodebyte(c[0], &len); 48 | if (!BETWEEN(len, 1, UTF_SIZ)) 49 | return 1; 50 | for (i = 1, j = 1; i < clen && j < len; ++i, ++j) { 51 | udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); 52 | if (type) 53 | return j; 54 | } 55 | if (j < len) 56 | return 0; 57 | *u = udecoded; 58 | utf8validate(u, len); 59 | 60 | return len; 61 | } 62 | 63 | Drw * 64 | drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) 65 | { 66 | Drw *drw = ecalloc(1, sizeof(Drw)); 67 | 68 | drw->dpy = dpy; 69 | drw->screen = screen; 70 | drw->root = root; 71 | drw->w = w; 72 | drw->h = h; 73 | drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); 74 | drw->gc = XCreateGC(dpy, root, 0, NULL); 75 | XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); 76 | 77 | return drw; 78 | } 79 | 80 | void 81 | drw_resize(Drw *drw, unsigned int w, unsigned int h) 82 | { 83 | if (!drw) 84 | return; 85 | 86 | drw->w = w; 87 | drw->h = h; 88 | if (drw->drawable) 89 | XFreePixmap(drw->dpy, drw->drawable); 90 | drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); 91 | } 92 | 93 | void 94 | drw_free(Drw *drw) 95 | { 96 | XFreePixmap(drw->dpy, drw->drawable); 97 | XFreeGC(drw->dpy, drw->gc); 98 | free(drw); 99 | } 100 | 101 | /* This function is an implementation detail. Library users should use 102 | * drw_fontset_create instead. 103 | */ 104 | static Fnt * 105 | xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern) 106 | { 107 | Fnt *font; 108 | XftFont *xfont = NULL; 109 | FcPattern *pattern = NULL; 110 | 111 | if (fontname) { 112 | /* Using the pattern found at font->xfont->pattern does not yield the 113 | * same substitution results as using the pattern returned by 114 | * FcNameParse; using the latter results in the desired fallback 115 | * behaviour whereas the former just results in missing-character 116 | * rectangles being drawn, at least with some fonts. */ 117 | if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) { 118 | fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname); 119 | return NULL; 120 | } 121 | if (!(pattern = FcNameParse((FcChar8 *) fontname))) { 122 | fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname); 123 | XftFontClose(drw->dpy, xfont); 124 | return NULL; 125 | } 126 | } else if (fontpattern) { 127 | if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { 128 | fprintf(stderr, "error, cannot load font from pattern.\n"); 129 | return NULL; 130 | } 131 | } else { 132 | die("no font specified."); 133 | } 134 | 135 | font = ecalloc(1, sizeof(Fnt)); 136 | font->xfont = xfont; 137 | font->pattern = pattern; 138 | font->h = xfont->ascent + xfont->descent; 139 | font->dpy = drw->dpy; 140 | 141 | return font; 142 | } 143 | 144 | static void 145 | xfont_free(Fnt *font) 146 | { 147 | if (!font) 148 | return; 149 | if (font->pattern) 150 | FcPatternDestroy(font->pattern); 151 | XftFontClose(font->dpy, font->xfont); 152 | free(font); 153 | } 154 | 155 | Fnt* 156 | drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) 157 | { 158 | Fnt *cur, *ret = NULL; 159 | size_t i; 160 | 161 | if (!drw || !fonts) 162 | return NULL; 163 | 164 | for (i = 1; i <= fontcount; i++) { 165 | if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) { 166 | cur->next = ret; 167 | ret = cur; 168 | } 169 | } 170 | return (drw->fonts = ret); 171 | } 172 | 173 | void 174 | drw_fontset_free(Fnt *font) 175 | { 176 | if (font) { 177 | drw_fontset_free(font->next); 178 | xfont_free(font); 179 | } 180 | } 181 | 182 | void 183 | drw_clr_create(Drw *drw, Clr *dest, const char *clrname) 184 | { 185 | if (!drw || !dest || !clrname) 186 | return; 187 | 188 | if (!XftColorAllocName(drw->dpy, DefaultVisual(drw->dpy, drw->screen), 189 | DefaultColormap(drw->dpy, drw->screen), 190 | clrname, dest)) 191 | die("error, cannot allocate color '%s'", clrname); 192 | } 193 | 194 | /* Wrapper to create color schemes. The caller has to call free(3) on the 195 | * returned color scheme when done using it. */ 196 | Clr * 197 | drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount) 198 | { 199 | size_t i; 200 | Clr *ret; 201 | 202 | /* need at least two colors for a scheme */ 203 | if (!drw || !clrnames || clrcount < 2 || !(ret = ecalloc(clrcount, sizeof(XftColor)))) 204 | return NULL; 205 | 206 | for (i = 0; i < clrcount; i++) 207 | drw_clr_create(drw, &ret[i], clrnames[i]); 208 | return ret; 209 | } 210 | 211 | void 212 | drw_setfontset(Drw *drw, Fnt *set) 213 | { 214 | if (drw) 215 | drw->fonts = set; 216 | } 217 | 218 | void 219 | drw_setscheme(Drw *drw, Clr *scm) 220 | { 221 | if (drw) 222 | drw->scheme = scm; 223 | } 224 | 225 | void 226 | drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert) 227 | { 228 | if (!drw || !drw->scheme) 229 | return; 230 | XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel); 231 | if (filled) 232 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 233 | else 234 | XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1); 235 | } 236 | 237 | int 238 | drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert) 239 | { 240 | char buf[1024]; 241 | int ty; 242 | unsigned int ew; 243 | XftDraw *d = NULL; 244 | Fnt *usedfont, *curfont, *nextfont; 245 | size_t i, len; 246 | int utf8charlen, render = x || y || w || h; 247 | long utf8codepoint = 0; 248 | FcCharSet *fccharset; 249 | FcPattern *fcpattern; 250 | FcPattern *match; 251 | XftResult result; 252 | int charexists = 0; 253 | 254 | if (!drw || (render && !drw->scheme) || !text || !drw->fonts) 255 | return 0; 256 | 257 | if (!render) { 258 | w = ~w; 259 | } else { 260 | XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel); 261 | XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); 262 | d = XftDrawCreate(drw->dpy, drw->drawable, 263 | DefaultVisual(drw->dpy, drw->screen), 264 | DefaultColormap(drw->dpy, drw->screen)); 265 | x += lpad; 266 | w -= lpad; 267 | } 268 | 269 | usedfont = drw->fonts; 270 | while (1) { 271 | int utf8strlen = 0; 272 | const char* utf8str = text; 273 | nextfont = NULL; 274 | while (*text) { 275 | utf8charlen = utf8decode(text, &utf8codepoint, UTF_SIZ); 276 | for (curfont = drw->fonts; curfont; curfont = curfont->next) { 277 | charexists = charexists || XftCharExists(drw->dpy, curfont->xfont, utf8codepoint); 278 | if (charexists) { 279 | if (curfont == usedfont) { 280 | utf8strlen += utf8charlen; 281 | text += utf8charlen; 282 | } else { 283 | nextfont = curfont; 284 | } 285 | break; 286 | } 287 | } 288 | 289 | if (!charexists || nextfont) 290 | break; 291 | else 292 | charexists = 0; 293 | } 294 | 295 | if (utf8strlen) { 296 | drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); 297 | /* shorten text if necessary */ 298 | for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--) 299 | drw_font_getexts(usedfont, utf8str, len, &ew, NULL); 300 | 301 | if (len) { 302 | memcpy(buf, utf8str, len); 303 | buf[len] = '\0'; 304 | if (len < utf8strlen) 305 | for (i = len; i && i > len - 3; buf[--i] = '.') 306 | ; /* NOP */ 307 | 308 | if (render) { 309 | ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent; 310 | XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg], 311 | usedfont->xfont, x, ty, (XftChar8 *)buf, len); 312 | } 313 | x += ew; 314 | w -= ew; 315 | } 316 | } 317 | 318 | if (!*text) { 319 | break; 320 | } else if (nextfont) { 321 | charexists = 0; 322 | usedfont = nextfont; 323 | } else { 324 | /* Regardless of whether or not a fallback font is found, the 325 | * character must be drawn. */ 326 | charexists = 1; 327 | 328 | fccharset = FcCharSetCreate(); 329 | FcCharSetAddChar(fccharset, utf8codepoint); 330 | 331 | if (!drw->fonts->pattern) { 332 | /* Refer to the comment in xfont_create for more information. */ 333 | die("the first font in the cache must be loaded from a font string."); 334 | } 335 | 336 | fcpattern = FcPatternDuplicate(drw->fonts->pattern); 337 | FcPatternAddCharSet(fcpattern, FC_CHARSET, fccharset); 338 | FcPatternAddBool(fcpattern, FC_SCALABLE, FcTrue); 339 | 340 | FcConfigSubstitute(NULL, fcpattern, FcMatchPattern); 341 | FcDefaultSubstitute(fcpattern); 342 | match = XftFontMatch(drw->dpy, drw->screen, fcpattern, &result); 343 | 344 | FcCharSetDestroy(fccharset); 345 | FcPatternDestroy(fcpattern); 346 | 347 | if (match) { 348 | usedfont = xfont_create(drw, NULL, match); 349 | if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { 350 | for (curfont = drw->fonts; curfont->next; curfont = curfont->next) 351 | ; /* NOP */ 352 | curfont->next = usedfont; 353 | } else { 354 | xfont_free(usedfont); 355 | usedfont = drw->fonts; 356 | } 357 | } 358 | } 359 | } 360 | if (d) 361 | XftDrawDestroy(d); 362 | 363 | return x + (render ? w : 0); 364 | } 365 | 366 | void 367 | drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) 368 | { 369 | if (!drw) 370 | return; 371 | 372 | XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); 373 | XSync(drw->dpy, False); 374 | } 375 | 376 | unsigned int 377 | drw_fontset_getwidth(Drw *drw, const char *text) 378 | { 379 | if (!drw || !drw->fonts || !text) 380 | return 0; 381 | return drw_text(drw, 0, 0, 0, 0, 0, text, 0); 382 | } 383 | 384 | void 385 | drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) 386 | { 387 | XGlyphInfo ext; 388 | 389 | if (!font || !text) 390 | return; 391 | 392 | XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); 393 | if (w) 394 | *w = ext.xOff; 395 | if (h) 396 | *h = font->h; 397 | } 398 | -------------------------------------------------------------------------------- /drw.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | typedef struct { 4 | Cursor cursor; 5 | } Cur; 6 | 7 | typedef struct Fnt { 8 | Display *dpy; 9 | unsigned int h; 10 | XftFont *xfont; 11 | FcPattern *pattern; 12 | struct Fnt *next; 13 | } Fnt; 14 | 15 | enum { ColFg, ColBg }; /* Clr scheme index */ 16 | typedef XftColor Clr; 17 | 18 | typedef struct { 19 | unsigned int w, h; 20 | Display *dpy; 21 | int screen; 22 | Window root; 23 | Drawable drawable; 24 | GC gc; 25 | Clr *scheme; 26 | Fnt *fonts; 27 | } Drw; 28 | 29 | /* Drawable abstraction */ 30 | Drw *drw_create(Display *dpy, int screen, Window win, unsigned int w, unsigned int h); 31 | void drw_resize(Drw *drw, unsigned int w, unsigned int h); 32 | void drw_free(Drw *drw); 33 | 34 | /* Fnt abstraction */ 35 | Fnt *drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount); 36 | void drw_fontset_free(Fnt* set); 37 | unsigned int drw_fontset_getwidth(Drw *drw, const char *text); 38 | void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h); 39 | 40 | /* Colorscheme abstraction */ 41 | void drw_clr_create(Drw *drw, Clr *dest, const char *clrname); 42 | Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount); 43 | 44 | /* Drawing context manipulation */ 45 | void drw_setfontset(Drw *drw, Fnt *set); 46 | void drw_setscheme(Drw *drw, Clr *scm); 47 | 48 | /* Drawing functions */ 49 | void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); 50 | int drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, unsigned int lpad, const char *text, int invert); 51 | 52 | /* Map functions */ 53 | void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); 54 | -------------------------------------------------------------------------------- /example: -------------------------------------------------------------------------------- 1 | sent 2 | 3 | Origin: 4 | Takahashi 5 | 6 | Why? 7 | • PPTX sucks 8 | • LATEX sucks 9 | • PDF sucks 10 | 11 | also: 12 | terminal presentations 13 | don't support images… 14 | 15 | @nyan.png 16 | this text will not be displayed, since the @ at the start of the first line 17 | makes this paragraph an image slide. 18 | 19 | easy to use 20 | 21 | depends on 22 | ♽ Xlib 23 | ☢ Xft 24 | ☃ farbfeld 25 | 26 | ~1000 lines of code 27 | 28 | usage: 29 | $ sent FILE1 [FILE2 …] 30 | 31 | ▸ one slide per paragraph 32 | ▸ lines starting with # are ignored 33 | ▸ image slide: paragraph containing @FILENAME 34 | ▸ empty slide: just use a \ as a paragraph 35 | 36 | # This is a comment and will not be part of the presentation 37 | 38 | # multiple empty lines between paragraphs are also ignored 39 | 40 | 41 | # The following lines should produce 42 | # one empty slide 43 | 44 | 45 | 46 | \ 47 | \ 48 | 49 | \@this_line_actually_started_with_a_\.png 50 | \#This line as well 51 | ⇒ Prepend a backslash to kill behaviour of special characters 52 | 53 | Images are handled in the 54 | http://tools.suckless.org/farbfeld/ 55 | format internally. 56 | 57 | sent also supports transparent images. 58 | Try changing the background in config.h 59 | and rebuild. 60 | 61 | @transparent_test.ff 62 | 63 | 😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏 64 | 😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟 65 | 😠😡😢😣😥😦😧😨😩😪😫😭😮😯😰😱 66 | 😲😳😴😵😶😷😸😹😺😻😼😽😾😿🙀☠ 67 | 68 | thanks. 69 | questions? 70 | -------------------------------------------------------------------------------- /nyan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoijui/sent/2f3bd5e99a5dee89c73a8e21699842e7cbb225e8/nyan.png -------------------------------------------------------------------------------- /sent.1: -------------------------------------------------------------------------------- 1 | .Dd 2016-08-12 2 | .Dt SENT 1 3 | .Sh NAME 4 | .Nm sent 5 | .Nd simple plaintext presentation tool 6 | .Sh SYNOPSIS 7 | .Nm 8 | .Op Fl v 9 | .Op Ar file 10 | .Sh DESCRIPTION 11 | .Nm 12 | is a simple plain text presentation tool for X. sent does not need LaTeX, 13 | LibreOffice or any other fancy file format. Instead, sent reads plain text 14 | describing the slides. sent can also draw images. 15 | .Pp 16 | Every paragraph represents a slide in the presentation. Especially for 17 | presentations using the Takahashi method this is very nice and allows 18 | you to write the presentation for a quick lightning talk within a 19 | few minutes. 20 | .Sh OPTIONS 21 | .Bl -tag -width Ds 22 | .It Fl v 23 | Print version information to stdout and exit. 24 | .El 25 | .Sh USAGE 26 | .Bl -tag -width Ds 27 | .It Em Mouse commands 28 | .Bl -tag -width Ds 29 | .It Sy Button1 | Button5 30 | Go to next slide, if existent. 31 | .It Sy Button3 | Button4 32 | Go to previous slide, if existent. 33 | .El 34 | .It Em Keyboard commands 35 | .Bl -tag -width Ds 36 | .It Sy Escape | q 37 | Quit. 38 | .It Sy r 39 | Reload the slides. Only works on file input. 40 | .It Sy Right | Return | Space | l | j | Down | Next | n 41 | Go to next slide, if existent. 42 | .It Sy Left | Backspace | h | k | Up | Prior | p 43 | Go to previous slide, if existent. 44 | .El 45 | .El 46 | .Sh FORMAT 47 | The presentation file is made up of at least one paragraph, with an 48 | empty line separating two slides. 49 | Each input line is interpreted literally, except from control characters 50 | at the beginning of lines described as follows: 51 | .Bl -tag -width Ds 52 | .It Sy @ 53 | Create individual slide containing the image pointed to by the filename 54 | following the 55 | .Sy @ . 56 | .It Sy # 57 | Ignore this input line. 58 | .It Sy \e 59 | Create input line using the characters following the 60 | .Sy \e 61 | without interpreting them. 62 | .El 63 | .Sh CUSTOMIZATION 64 | .Nm 65 | can be customized by creating a custom config.h and (re)compiling the 66 | source code. This keeps it fast, secure and simple. 67 | .Sh SEE ALSO 68 | .Xr 2ff 1 69 | -------------------------------------------------------------------------------- /sent.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "arg.h" 23 | #include "util.h" 24 | #include "drw.h" 25 | 26 | char *argv0; 27 | 28 | /* macros */ 29 | #define LEN(a) (sizeof(a) / sizeof(a)[0]) 30 | #define LIMIT(x, a, b) (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x) 31 | #define MAXFONTSTRLEN 128 32 | 33 | typedef enum { 34 | NONE = 0, 35 | SCALED = 1, 36 | } imgstate; 37 | 38 | typedef struct { 39 | unsigned char *buf; 40 | unsigned int bufwidth, bufheight; 41 | imgstate state; 42 | XImage *ximg; 43 | int numpasses; 44 | } Image; 45 | 46 | typedef struct { 47 | char *regex; 48 | char *bin; 49 | } Filter; 50 | 51 | typedef struct { 52 | unsigned int linecount; 53 | char **lines; 54 | Image *img; 55 | char *embed; 56 | } Slide; 57 | 58 | /* Purely graphic info */ 59 | typedef struct { 60 | Display *dpy; 61 | Window win; 62 | Atom wmdeletewin, netwmname; 63 | Visual *vis; 64 | XSetWindowAttributes attrs; 65 | int scr; 66 | int w, h; 67 | int uw, uh; /* usable dimensions for drawing text and images */ 68 | } XWindow; 69 | 70 | typedef union { 71 | int i; 72 | unsigned int ui; 73 | float f; 74 | const void *v; 75 | } Arg; 76 | 77 | typedef struct { 78 | unsigned int b; 79 | void (*func)(const Arg *); 80 | const Arg arg; 81 | } Mousekey; 82 | 83 | typedef struct { 84 | KeySym keysym; 85 | void (*func)(const Arg *); 86 | const Arg arg; 87 | } Shortcut; 88 | 89 | static void fffree(Image *img); 90 | static void ffload(Slide *s); 91 | static void ffprepare(Image *img); 92 | static void ffscale(Image *img); 93 | static void ffdraw(Image *img); 94 | 95 | static void getfontsize(Slide *s, unsigned int *width, unsigned int *height); 96 | static void cleanup(int slidesonly); 97 | static void reload(const Arg *arg); 98 | static void load(FILE *fp); 99 | static void advance(const Arg *arg); 100 | static void quit(const Arg *arg); 101 | static void resize(int width, int height); 102 | static void run(); 103 | static void usage(); 104 | static void xdraw(); 105 | static void xhints(); 106 | static void xinit(); 107 | static void xloadfonts(); 108 | 109 | static void bpress(XEvent *); 110 | static void cmessage(XEvent *); 111 | static void expose(XEvent *); 112 | static void kpress(XEvent *); 113 | static void configure(XEvent *); 114 | 115 | /* config.h for applying patches and the configuration. */ 116 | #include "config.h" 117 | 118 | /* Globals */ 119 | static const char *fname = NULL; 120 | static Slide *slides = NULL; 121 | static int idx = 0; 122 | static int slidecount = 0; 123 | static XWindow xw; 124 | static Drw *d = NULL; 125 | static Clr *sc; 126 | static Fnt *fonts[NUMFONTSCALES]; 127 | static int running = 1; 128 | 129 | static void (*handler[LASTEvent])(XEvent *) = { 130 | [ButtonPress] = bpress, 131 | [ClientMessage] = cmessage, 132 | [ConfigureNotify] = configure, 133 | [Expose] = expose, 134 | [KeyPress] = kpress, 135 | }; 136 | 137 | int 138 | filter(int fd, const char *cmd) 139 | { 140 | int fds[2]; 141 | 142 | if (pipe(fds) < 0) 143 | die("sent: Unable to create pipe:"); 144 | 145 | switch (fork()) { 146 | case -1: 147 | die("sent: Unable to fork:"); 148 | case 0: 149 | dup2(fd, 0); 150 | dup2(fds[1], 1); 151 | close(fds[0]); 152 | close(fds[1]); 153 | execlp("sh", "sh", "-c", cmd, (char *)0); 154 | fprintf(stderr, "sent: execlp sh -c '%s': %s\n", cmd, strerror(errno)); 155 | _exit(1); 156 | } 157 | close(fds[1]); 158 | return fds[0]; 159 | } 160 | 161 | void 162 | fffree(Image *img) 163 | { 164 | free(img->buf); 165 | if (img->ximg) 166 | XDestroyImage(img->ximg); 167 | free(img); 168 | } 169 | 170 | void 171 | ffload(Slide *s) 172 | { 173 | uint32_t y, x; 174 | uint16_t *row; 175 | uint8_t opac, fg_r, fg_g, fg_b, bg_r, bg_g, bg_b; 176 | size_t rowlen, off, i; 177 | ssize_t count; 178 | unsigned char hdr[16]; 179 | char *bin = NULL; 180 | char *filename; 181 | regex_t regex; 182 | int fdin, fdout; 183 | 184 | if (s->img || !(filename = s->embed) || !s->embed[0]) 185 | return; /* already done */ 186 | 187 | for (i = 0; i < LEN(filters); i++) { 188 | if (regcomp(®ex, filters[i].regex, 189 | REG_NOSUB | REG_EXTENDED | REG_ICASE)) { 190 | fprintf(stderr, "sent: Invalid regex '%s'\n", filters[i].regex); 191 | continue; 192 | } 193 | if (!regexec(®ex, filename, 0, NULL, 0)) { 194 | bin = filters[i].bin; 195 | regfree(®ex); 196 | break; 197 | } 198 | regfree(®ex); 199 | } 200 | if (!bin) 201 | die("sent: Unable to find matching filter for '%s'", filename); 202 | 203 | if ((fdin = open(filename, O_RDONLY)) < 0) 204 | die("sent: Unable to open '%s':", filename); 205 | 206 | if ((fdout = filter(fdin, bin)) < 0) 207 | die("sent: Unable to filter '%s':", filename); 208 | close(fdin); 209 | 210 | if (read(fdout, hdr, 16) != 16) 211 | die("sent: Unable to read filtered file '%s':", filename); 212 | if (memcmp("farbfeld", hdr, 8)) 213 | die("sent: Filtered file '%s' has no valid farbfeld header", filename); 214 | 215 | s->img = ecalloc(1, sizeof(Image)); 216 | s->img->bufwidth = ntohl(*(uint32_t *)&hdr[8]); 217 | s->img->bufheight = ntohl(*(uint32_t *)&hdr[12]); 218 | 219 | if (s->img->buf) 220 | free(s->img->buf); 221 | /* internally the image is stored in 888 format */ 222 | s->img->buf = ecalloc(s->img->bufwidth * s->img->bufheight, strlen("888")); 223 | 224 | /* scratch buffer to read row by row */ 225 | rowlen = s->img->bufwidth * 2 * strlen("RGBA"); 226 | row = ecalloc(1, rowlen); 227 | 228 | /* extract window background color channels for transparency */ 229 | bg_r = (sc[ColBg].pixel >> 16) % 256; 230 | bg_g = (sc[ColBg].pixel >> 8) % 256; 231 | bg_b = (sc[ColBg].pixel >> 0) % 256; 232 | 233 | for (off = 0, y = 0; y < s->img->bufheight; y++) { 234 | size_t nbytes = 0; 235 | while (nbytes < rowlen) { 236 | count = read(fdout, (char *)row + nbytes, rowlen - nbytes); 237 | if (count < 0) 238 | die("sent: Unable to read from pipe:"); 239 | nbytes += count; 240 | } 241 | for (x = 0; x < rowlen / 2; x += 4) { 242 | fg_r = ntohs(row[x + 0]) / 257; 243 | fg_g = ntohs(row[x + 1]) / 257; 244 | fg_b = ntohs(row[x + 2]) / 257; 245 | opac = ntohs(row[x + 3]) / 257; 246 | /* blend opaque part of image data with window background color to 247 | * emulate transparency */ 248 | s->img->buf[off++] = (fg_r * opac + bg_r * (255 - opac)) / 255; 249 | s->img->buf[off++] = (fg_g * opac + bg_g * (255 - opac)) / 255; 250 | s->img->buf[off++] = (fg_b * opac + bg_b * (255 - opac)) / 255; 251 | } 252 | } 253 | 254 | free(row); 255 | close(fdout); 256 | } 257 | 258 | void 259 | ffprepare(Image *img) 260 | { 261 | int depth = DefaultDepth(xw.dpy, xw.scr); 262 | int width = xw.uw; 263 | int height = xw.uh; 264 | 265 | if (xw.uw * img->bufheight > xw.uh * img->bufwidth) 266 | width = img->bufwidth * xw.uh / img->bufheight; 267 | else 268 | height = img->bufheight * xw.uw / img->bufwidth; 269 | 270 | if (depth < 24) 271 | die("sent: Display color depths < 24 not supported"); 272 | 273 | if (!(img->ximg = XCreateImage(xw.dpy, CopyFromParent, depth, ZPixmap, 0, 274 | NULL, width, height, 32, 0))) 275 | die("sent: Unable to create XImage"); 276 | 277 | img->ximg->data = ecalloc(height, img->ximg->bytes_per_line); 278 | if (!XInitImage(img->ximg)) 279 | die("sent: Unable to initiate XImage"); 280 | 281 | ffscale(img); 282 | img->state |= SCALED; 283 | } 284 | 285 | void 286 | ffscale(Image *img) 287 | { 288 | unsigned int x, y; 289 | unsigned int width = img->ximg->width; 290 | unsigned int height = img->ximg->height; 291 | char* newBuf = img->ximg->data; 292 | unsigned char* ibuf; 293 | unsigned int jdy = img->ximg->bytes_per_line / 4 - width; 294 | unsigned int dx = (img->bufwidth << 10) / width; 295 | 296 | for (y = 0; y < height; y++) { 297 | unsigned int bufx = img->bufwidth / width; 298 | ibuf = &img->buf[y * img->bufheight / height * img->bufwidth * 3]; 299 | 300 | for (x = 0; x < width; x++) { 301 | *newBuf++ = (ibuf[(bufx >> 10)*3+2]); 302 | *newBuf++ = (ibuf[(bufx >> 10)*3+1]); 303 | *newBuf++ = (ibuf[(bufx >> 10)*3+0]); 304 | newBuf++; 305 | bufx += dx; 306 | } 307 | newBuf += jdy; 308 | } 309 | } 310 | 311 | void 312 | ffdraw(Image *img) 313 | { 314 | int xoffset = (xw.w - img->ximg->width) / 2; 315 | int yoffset = (xw.h - img->ximg->height) / 2; 316 | XPutImage(xw.dpy, xw.win, d->gc, img->ximg, 0, 0, 317 | xoffset, yoffset, img->ximg->width, img->ximg->height); 318 | XFlush(xw.dpy); 319 | } 320 | 321 | void 322 | getfontsize(Slide *s, unsigned int *width, unsigned int *height) 323 | { 324 | int i, j; 325 | float lfac = linespacing * (s->linecount - 1) + 1; 326 | 327 | /* fit height */ 328 | for (j = NUMFONTSCALES - 1; j >= 0; j--) 329 | if (fonts[j]->h * lfac <= xw.uh) 330 | break; 331 | LIMIT(j, 0, NUMFONTSCALES - 1); 332 | drw_setfontset(d, fonts[j]); 333 | 334 | /* fit width */ 335 | *width = 0; 336 | for (i = 0; i < s->linecount; i++) { 337 | unsigned int curw = drw_fontset_getwidth(d, s->lines[i]); 338 | const unsigned int newmax = (curw >= *width); 339 | while (j > 0 && curw > xw.uw) { 340 | drw_setfontset(d, fonts[--j]); 341 | curw = drw_fontset_getwidth(d, s->lines[i]); 342 | } 343 | if (newmax) 344 | *width = curw; 345 | } 346 | *height = fonts[j]->h * lfac; 347 | } 348 | 349 | void 350 | cleanup(int slidesonly) 351 | { 352 | if (!slidesonly) { 353 | for (unsigned int i = 0; i < NUMFONTSCALES; i++) 354 | drw_fontset_free(fonts[i]); 355 | free(sc); 356 | drw_free(d); 357 | 358 | XDestroyWindow(xw.dpy, xw.win); 359 | XSync(xw.dpy, False); 360 | XCloseDisplay(xw.dpy); 361 | } 362 | 363 | if (slides) { 364 | for (unsigned int i = 0; i < slidecount; i++) { 365 | for (unsigned int j = 0; j < slides[i].linecount; j++) 366 | free(slides[i].lines[j]); 367 | free(slides[i].lines); 368 | if (slides[i].img) 369 | fffree(slides[i].img); 370 | } 371 | if (!slidesonly) { 372 | free(slides); 373 | slides = NULL; 374 | } 375 | } 376 | } 377 | 378 | void 379 | reload(const Arg *arg) 380 | { 381 | FILE *fp = NULL; 382 | unsigned int i; 383 | 384 | if (!fname) { 385 | fprintf(stderr, "sent: Cannot reload from stdin. Use a file!\n"); 386 | return; 387 | } 388 | 389 | cleanup(1); 390 | slidecount = 0; 391 | 392 | if (!(fp = fopen(fname, "r"))) 393 | die("sent: Unable to open '%s' for reading:", fname); 394 | load(fp); 395 | fclose(fp); 396 | 397 | LIMIT(idx, 0, slidecount-1); 398 | for (i = 0; i < slidecount; i++) 399 | ffload(&slides[i]); 400 | xdraw(); 401 | } 402 | 403 | /** 404 | * Skips the Byte Order Mark (BOM), if present. 405 | * In a text file, "UTF-8 (with BOM)" formatting is indicated 406 | * by a BOM () appearing in the beginning of the file. 407 | * We check for it, by checking whether the first three characters. 408 | * If no BOM is present, we rewind the file pointer 409 | * to the begining of the file, otherwise not, 410 | * in order to exclude the BOM from the content of the file. 411 | */ 412 | static void 413 | skipBom(FILE *fp) 414 | { 415 | int c1, c2, c3; 416 | 417 | c1 = fgetc(fp); 418 | c2 = (c1 == EOF) ? EOF : fgetc(fp); 419 | c3 = (c2 == EOF) ? EOF : fgetc(fp); 420 | if (!(c1 == 0xEF && c2 == 0xBB && c3 == 0xBF)) 421 | { 422 | rewind(fp); 423 | } 424 | } 425 | 426 | void 427 | load(FILE *fp) 428 | { 429 | static size_t size = 0; 430 | unsigned blen; 431 | char buf[BUFSIZ]; 432 | Slide *s; 433 | 434 | skipBom(fp); 435 | 436 | /* read each line from fp and add it to the item list */ 437 | while (1) { 438 | char* p; 439 | /* eat consecutive empty lines */ 440 | while ((p = fgets(buf, sizeof(buf), fp))) 441 | if (strcmp(buf, "\n") != 0 && buf[0] != '#') 442 | break; 443 | if (!p) 444 | break; 445 | 446 | if ((slidecount + 1) * sizeof(*slides) >= size) { 447 | Slide* newSlides = NULL; 448 | if (!(newSlides = realloc(slides, (size += BUFSIZ)))) { 449 | die("sent: Unable to reallocate %u bytes:", size); 450 | } else { 451 | slides = newSlides; 452 | } 453 | } 454 | 455 | /* read one slide */ 456 | unsigned maxlines = 0; 457 | memset((s = &slides[slidecount]), 0, sizeof(Slide)); 458 | do { 459 | /* if there's a leading null, we can't do blen-1 */ 460 | if (buf[0] == '\0') 461 | continue; 462 | 463 | if (buf[0] == '#') 464 | continue; 465 | 466 | /* grow lines array */ 467 | if (s->linecount >= maxlines) { 468 | maxlines = 2 * s->linecount + 1; 469 | if (!(s->lines = realloc(s->lines, maxlines * sizeof(s->lines[0])))) 470 | die("sent: Unable to reallocate %u bytes:", maxlines * sizeof(s->lines[0])); 471 | } 472 | 473 | blen = strlen(buf); 474 | if (!(s->lines[s->linecount] = strdup(buf))) 475 | die("sent: Unable to strdup:"); 476 | if (s->lines[s->linecount][blen-1] == '\n') 477 | s->lines[s->linecount][blen-1] = '\0'; 478 | 479 | /* mark as image slide if first line of a slide starts with @ */ 480 | if (s->linecount == 0 && s->lines[0][0] == '@') 481 | s->embed = &s->lines[0][1]; 482 | 483 | if (s->lines[s->linecount][0] == '\\') 484 | memmove(s->lines[s->linecount], &s->lines[s->linecount][1], blen); 485 | s->linecount++; 486 | } while ((p = fgets(buf, sizeof(buf), fp)) && strcmp(buf, "\n") != 0); 487 | 488 | slidecount++; 489 | if (!p) 490 | break; 491 | } 492 | 493 | if (!slidecount) 494 | die("sent: No slides in file"); 495 | } 496 | 497 | void 498 | advance(const Arg *arg) 499 | { 500 | int new_idx = idx + arg->i; 501 | LIMIT(new_idx, 0, slidecount-1); 502 | if (new_idx != idx) { 503 | if (slides[idx].img) 504 | slides[idx].img->state &= ~SCALED; 505 | idx = new_idx; 506 | xdraw(); 507 | } 508 | } 509 | 510 | void 511 | quit(const Arg *arg) 512 | { 513 | running = 0; 514 | } 515 | 516 | void 517 | resize(int width, int height) 518 | { 519 | xw.w = width; 520 | xw.h = height; 521 | xw.uw = usablewidth * width; 522 | xw.uh = usableheight * height; 523 | drw_resize(d, width, height); 524 | } 525 | 526 | void 527 | run() 528 | { 529 | XEvent ev; 530 | 531 | /* Waiting for window mapping */ 532 | while (1) { 533 | XNextEvent(xw.dpy, &ev); 534 | if (ev.type == ConfigureNotify) { 535 | resize(ev.xconfigure.width, ev.xconfigure.height); 536 | } else if (ev.type == MapNotify) { 537 | break; 538 | } 539 | } 540 | 541 | while (running) { 542 | XNextEvent(xw.dpy, &ev); 543 | if (handler[ev.type]) 544 | (handler[ev.type])(&ev); 545 | } 546 | } 547 | 548 | void 549 | xdraw() 550 | { 551 | unsigned int height, width; 552 | Image *im = slides[idx].img; 553 | 554 | getfontsize(&slides[idx], &width, &height); 555 | XClearWindow(xw.dpy, xw.win); 556 | 557 | if (!im) { 558 | drw_rect(d, 0, 0, xw.w, xw.h, 1, 1); 559 | for (unsigned int i = 0; i < slides[idx].linecount; i++) 560 | drw_text(d, 561 | (xw.w - width) / 2, 562 | (xw.h - height) / 2 + i * linespacing * d->fonts->h, 563 | width, 564 | d->fonts->h, 565 | 0, 566 | slides[idx].lines[i], 567 | 0); 568 | drw_map(d, xw.win, 0, 0, xw.w, xw.h); 569 | } else { 570 | if (!(im->state & SCALED)) 571 | ffprepare(im); 572 | ffdraw(im); 573 | } 574 | } 575 | 576 | void 577 | xhints() 578 | { 579 | XClassHint class = {.res_name = "sent", .res_class = "presenter"}; 580 | XWMHints wm = {.flags = InputHint, .input = True}; 581 | XSizeHints *sizeh = NULL; 582 | 583 | if (!(sizeh = XAllocSizeHints())) 584 | die("sent: Unable to allocate size hints"); 585 | 586 | sizeh->flags = PSize; 587 | sizeh->height = xw.h; 588 | sizeh->width = xw.w; 589 | 590 | XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, &class); 591 | XFree(sizeh); 592 | } 593 | 594 | void 595 | xinit() 596 | { 597 | XTextProperty prop; 598 | unsigned int i; 599 | 600 | if (!(xw.dpy = XOpenDisplay(NULL))) 601 | die("sent: Unable to open display"); 602 | xw.scr = XDefaultScreen(xw.dpy); 603 | xw.vis = XDefaultVisual(xw.dpy, xw.scr); 604 | resize(DisplayWidth(xw.dpy, xw.scr), DisplayHeight(xw.dpy, xw.scr)); 605 | 606 | xw.attrs.bit_gravity = CenterGravity; 607 | xw.attrs.event_mask = KeyPressMask | ExposureMask | StructureNotifyMask | 608 | ButtonMotionMask | ButtonPressMask; 609 | 610 | xw.win = XCreateWindow(xw.dpy, XRootWindow(xw.dpy, xw.scr), 0, 0, 611 | xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), 612 | InputOutput, xw.vis, CWBitGravity | CWEventMask, 613 | &xw.attrs); 614 | 615 | xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); 616 | xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); 617 | XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); 618 | 619 | if (!(d = drw_create(xw.dpy, xw.scr, xw.win, xw.w, xw.h))) 620 | die("sent: Unable to create drawing context"); 621 | sc = drw_scm_create(d, colors, 2); 622 | drw_setscheme(d, sc); 623 | XSetWindowBackground(xw.dpy, xw.win, sc[ColBg].pixel); 624 | 625 | xloadfonts(); 626 | for (i = 0; i < slidecount; i++) 627 | ffload(&slides[i]); 628 | 629 | XStringListToTextProperty(&argv0, 1, &prop); 630 | XSetWMName(xw.dpy, xw.win, &prop); 631 | XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); 632 | XFree(prop.value); 633 | XMapWindow(xw.dpy, xw.win); 634 | xhints(); 635 | XSync(xw.dpy, False); 636 | } 637 | 638 | void 639 | xloadfonts() 640 | { 641 | int i, j; 642 | char *fstrs[LEN(fontfallbacks)]; 643 | 644 | for (j = 0; j < LEN(fontfallbacks); j++) { 645 | fstrs[j] = ecalloc(1, MAXFONTSTRLEN); 646 | } 647 | 648 | for (i = 0; i < NUMFONTSCALES; i++) { 649 | for (j = 0; j < LEN(fontfallbacks); j++) { 650 | if (MAXFONTSTRLEN < snprintf(fstrs[j], MAXFONTSTRLEN, "%s:size=%d", fontfallbacks[j], FONTSZ(i))) 651 | die("sent: Font string too long"); 652 | } 653 | if (!(fonts[i] = drw_fontset_create(d, (const char**)fstrs, LEN(fstrs)))) 654 | die("sent: Unable to load any font for size %d", FONTSZ(i)); 655 | } 656 | 657 | for (j = 0; j < LEN(fontfallbacks); j++) 658 | if (fstrs[j]) 659 | free(fstrs[j]); 660 | } 661 | 662 | void 663 | bpress(XEvent *e) 664 | { 665 | unsigned int i; 666 | 667 | for (i = 0; i < LEN(mshortcuts); i++) 668 | if (e->xbutton.button == mshortcuts[i].b && mshortcuts[i].func) 669 | mshortcuts[i].func(&(mshortcuts[i].arg)); 670 | } 671 | 672 | void 673 | cmessage(XEvent *e) 674 | { 675 | if (e->xclient.data.l[0] == xw.wmdeletewin) 676 | running = 0; 677 | } 678 | 679 | void 680 | expose(XEvent *e) 681 | { 682 | if (0 == e->xexpose.count) 683 | xdraw(); 684 | } 685 | 686 | void 687 | kpress(XEvent *e) 688 | { 689 | unsigned int i; 690 | KeySym sym; 691 | 692 | sym = XkbKeycodeToKeysym(xw.dpy, (KeyCode)e->xkey.keycode, 0, 0); 693 | for (i = 0; i < LEN(shortcuts); i++) 694 | if (sym == shortcuts[i].keysym && shortcuts[i].func) 695 | shortcuts[i].func(&(shortcuts[i].arg)); 696 | } 697 | 698 | void 699 | configure(XEvent *e) 700 | { 701 | resize(e->xconfigure.width, e->xconfigure.height); 702 | if (slides[idx].img) 703 | slides[idx].img->state &= ~SCALED; 704 | xdraw(); 705 | } 706 | 707 | void 708 | usage() 709 | { 710 | die("usage:\n\t%s [file]\n\t%s -h\n\t%s -v", argv0, argv0, argv0); 711 | } 712 | 713 | int 714 | main(int argc, char *argv[]) 715 | { 716 | FILE *fp = NULL; 717 | 718 | ARGBEGIN { 719 | case 'v': 720 | fprintf(stderr, "sent-"VERSION"\n"); 721 | return 0; 722 | default: 723 | usage(); 724 | } ARGEND 725 | 726 | if (!argv[0] || !strcmp(argv[0], "-")) 727 | fp = stdin; 728 | else if (!(fp = fopen(fname = argv[0], "r"))) 729 | die("sent: Unable to open '%s' for reading:", fname); 730 | load(fp); 731 | fclose(fp); 732 | 733 | xinit(); 734 | run(); 735 | 736 | cleanup(0); 737 | return 0; 738 | } 739 | -------------------------------------------------------------------------------- /transparent_test.ff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoijui/sent/2f3bd5e99a5dee89c73a8e21699842e7cbb225e8/transparent_test.ff -------------------------------------------------------------------------------- /util.c: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "util.h" 8 | 9 | void * 10 | ecalloc(size_t nmemb, size_t size) 11 | { 12 | void *p; 13 | 14 | if (!(p = calloc(nmemb, size))) 15 | die("calloc:"); 16 | return p; 17 | } 18 | 19 | void 20 | die(const char *fmt, ...) { 21 | va_list ap; 22 | 23 | va_start(ap, fmt); 24 | vfprintf(stderr, fmt, ap); 25 | va_end(ap); 26 | 27 | if (fmt[0] && fmt[strlen(fmt)-1] == ':') { 28 | fputc(' ', stderr); 29 | perror(NULL); 30 | } else { 31 | fputc('\n', stderr); 32 | } 33 | 34 | exit(1); 35 | } 36 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | /* See LICENSE file for copyright and license details. */ 2 | 3 | #define MAX(A, B) ((A) > (B) ? (A) : (B)) 4 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 5 | #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) 6 | 7 | void die(const char *fmt, ...); 8 | void *ecalloc(size_t nmemb, size_t size); 9 | --------------------------------------------------------------------------------