From 621cc22b6e5b4587bdc26c58060516d41b2c5c79 Mon Sep 17 00:00:00 2001 From: inigoortega Date: Sat, 14 Sep 2019 17:03:35 +0200 Subject: [PATCH] Sent added, scripts and environment --- .config/sxhkd/sxhkdrc | 3 + .config/vlc/vlc-qt-interface.conf | 2 +- .gitignore | 1 + .mkshrc | 3 +- .profile | 2 +- .zshrc | 3 +- dwm/dwm-6.2/config.h | 7 +- dwm/dwm-6.2/dwm | Bin 71424 -> 71424 bytes dwm/dwm-6.2/dwm.o | Bin 60288 -> 60480 bytes scripts/play-multimedia.sh | 7 +- sent/LICENSE | 28 + sent/Makefile | 60 ++ sent/README.md | 59 ++ sent/arg.h | 49 ++ sent/config.def.h | 59 ++ sent/config.h | 59 ++ sent/config.mk | 30 + sent/drw.c | 421 ++++++++++++++ sent/drw.h | 57 ++ sent/example | 69 +++ sent/nyan.png | Bin 0 -> 901 bytes sent/sent-1.tar.gz | Bin 0 -> 15285 bytes sent/sent-options-20190213-72d33d4.diff | 72 +++ sent/sent-progress-bar-1.0.diff | 31 + sent/sent.1 | 79 +++ sent/sent.c | 721 ++++++++++++++++++++++++ sent/sent.c.orig | 715 +++++++++++++++++++++++ sent/transparent_test.ff | Bin 0 -> 20016 bytes sent/util.c | 35 ++ sent/util.h | 8 + 30 files changed, 2572 insertions(+), 8 deletions(-) create mode 100644 sent/LICENSE create mode 100644 sent/Makefile create mode 100644 sent/README.md create mode 100644 sent/arg.h create mode 100644 sent/config.def.h create mode 100644 sent/config.h create mode 100644 sent/config.mk create mode 100644 sent/drw.c create mode 100644 sent/drw.h create mode 100644 sent/example create mode 100644 sent/nyan.png create mode 100644 sent/sent-1.tar.gz create mode 100644 sent/sent-options-20190213-72d33d4.diff create mode 100644 sent/sent-progress-bar-1.0.diff create mode 100644 sent/sent.1 create mode 100644 sent/sent.c create mode 100644 sent/sent.c.orig create mode 100644 sent/transparent_test.ff create mode 100644 sent/util.c create mode 100644 sent/util.h diff --git a/.config/sxhkd/sxhkdrc b/.config/sxhkd/sxhkdrc index 47c8136..83127a6 100644 --- a/.config/sxhkd/sxhkdrc +++ b/.config/sxhkd/sxhkdrc @@ -45,3 +45,6 @@ Print shift + Shift_R mksh $SCRIPTS/toggle-layout.sh && mksh $SCRIPTS/reset-dwmbar.sh + +alt + e + mksh $SCRIPTS/vifm.sh diff --git a/.config/vlc/vlc-qt-interface.conf b/.config/vlc/vlc-qt-interface.conf index 5061302..52c5131 100644 --- a/.config/vlc/vlc-qt-interface.conf +++ b/.config/vlc/vlc-qt-interface.conf @@ -1,6 +1,6 @@ [General] filedialog-path=@Variant(\0\0\0\x11\0\0\0\r/home/initega) -geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\0\0\0\0\x13\0\0\x4\xff\0\0\x3\x1f\0\0\0\x1\0\0\0\x14\0\0\x4\xfe\0\0\x3\x1e\0\0\0\0\0\0\0\0\x5\0\0\0\0\x1\0\0\0\x14\0\0\x4\xfe\0\0\x3\x1e) +geometry=@ByteArray(\x1\xd9\xd0\xcb\0\x3\0\0\0\0\0\0\0\0\0\x13\0\0\x4\xff\0\0\x3\x1f\0\0\0\x2\0\0\0\x15\0\0\x4\xfd\0\0\x3\x1d\0\0\0\0\0\0\0\0\x5\0\0\0\0\x2\0\0\0\x15\0\0\x4\xfd\0\0\x3\x1d) [FullScreen] pos=@Point(0 0) diff --git a/.gitignore b/.gitignore index d2b08f1..3f20709 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ .config/mutt/ .local/share/ termscripts/test.sh +.config/tox diff --git a/.mkshrc b/.mkshrc index 069a134..8b59098 100644 --- a/.mkshrc +++ b/.mkshrc @@ -17,8 +17,9 @@ export PASSWORD_STORE_DIR="$HOME/.password-store" export ANDROID_MOUNTPOINT="$HOME/Android" export BRIGHTNESS="0.9" export SUDO_ASKPASS="/usr/bin/x11-ssh-askpass" -export TRASH="$HOME/.local/trash" +export TRASH="$HOME/.local/trash:$HOME/.local/share/vifm/Trash" export DEVICES_FOLDER="/media" +export LC_ALL=en_US.utf8 # custom prompt see http://comments.gmane.org/gmane.os.miros.mksh/126 # USER diff --git a/.profile b/.profile index 83b502c..dcb8d99 100644 --- a/.profile +++ b/.profile @@ -18,6 +18,6 @@ export PASSWORD_STORE_DIR="$HOME/.password-store" export ANDROID_MOUNTPOINT="$HOME/Android" export BRIGHTNESS="0.9" export SUDO_ASKPASS="/usr/bin/x11-ssh-askpass" -export TRASH="$HOME/.local/trash" +export TRASH="$HOME/.local/trash:$HOME/.local/share/vifm/Trash" export DEVICES_FOLDER="/media" export LC_ALL=en_US.utf8 diff --git a/.zshrc b/.zshrc index 48acc76..2ec4b8a 100644 --- a/.zshrc +++ b/.zshrc @@ -99,8 +99,9 @@ export PASSWORD_STORE_DIR="$HOME/.password-store" export ANDROID_MOUNTPOINT="$HOME/Android" export BRIGHTNESS="0.9" export SUDO_ASKPASS="/usr/bin/x11-ssh-askpass" -export TRASH="$HOME/.local/trash" +export TRASH="$HOME/.local/trash:$HOME/.local/share/vifm/Trash" export DEVICES_FOLDER="/media" +export LC_ALL=en_US.utf8 # # ALIASES diff --git a/dwm/dwm-6.2/config.h b/dwm/dwm-6.2/config.h index 7d76aef..d3f8a7c 100644 --- a/dwm/dwm-6.2/config.h +++ b/dwm/dwm-6.2/config.h @@ -35,8 +35,11 @@ static const Rule rules[] = { /* class instance title tags mask isfloating monitor */ { "Gimp", NULL, NULL, 0, 1, -1 }, { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, -{ "TelegramDesktop", NULL, NULL, 1 << 6, 0, -1 }, - { "Workrave", NULL, NULL, ~0, 1, -1 }, +{ "Tor Browser", NULL, NULL, 1 << 8, 0, -1 }, +{ "TelegramDesktop", NULL, NULL, 1 << 6, 0, -1 }, + { "Workrave", NULL, NULL, ~0, 1, -1 }, + { "mpv", "gl", NULL, 1 << 5, 0, -1 }, + { "vlc", "vlc", NULL, 1 << 5, 0, -1 }, }; /* layout(s) */ diff --git a/dwm/dwm-6.2/dwm b/dwm/dwm-6.2/dwm index 85798477e5bb14e0bd54e35f3fc4152b4ebc22b5..88d6f1393d5b05bdcb22bea2da0e261b0af6f019 100755 GIT binary patch delta 1011 zcmZ`&O=uHA6rNePWxEG!>f-L^u(pCL2&RdL5(?G?iqu;#qAj9`qL&Iiwcy3(;!S9M zV5Cwctv`5)l7R+1Xz)~{q+9%9T}zEcYY0fzQ^kwUWM+geIG1_v`@Z*f-p9U~qB>Jl zXFXj@q4v>IbHR;1ugWto*mUOB|FHx^3b?0yJ#Fnv!gulMR$ph z3cMb{7nZob5y6d191kOSLFmz^Hs6Ef2ObDQgEiBP^>X?j`xtVj341Kfo{TWdqV5i) zEOV?9VOIsa62adJyb-}a2t179khM%4z?juMA%JOqwH6~ zo{6wqfNc{uh;2W?7?D@;)-I(Uz%wVIgaI_21O@H={hhc;P98iRFJ+Fo_#PR$+?iDT zVK{O7Cw@=T`Lk+Q`Zz+TMs6NSlZCrSHT~-36uL5jrY0^zZRt?yzUM;n^{-($J|6&~ zUJ?AEhlSDxc?)`n4zh}o4a7W&MJ|!vYzSPsu_HAnFS7_1eQ zj0;Vgp>EJu&*6C}B=aTDQkezsUZ~8Mz;~E0zkP>+AE%iwbDz60E3ND^V7dO1^`xP? z7tyXPFc2-I0eSxA8Z(y+ejC!db|?z2I;wL2Jo)%fWHE1#kNw+R&%N zRNW;S}NU1WtieWE=#9;z-$L6d1;1fJ}!cwh3R z7(ZVV`Q;dI+!eeZ;}>OqMezMZ9q>#FFu*oP))F0;3m)w~FY*BV+U zk6#&D=*86fZJ0$U1}mYZGu!sZH#(oCy_4m${MyNKa`!%;WoG{b@Y1)Z3|#HJ<2_lZ zpCqg+4-CQzSwLTZe!xEn9X=RI_Nd>uKqtSw0Pf&i*I~7Ew3~&D+A%R4AzzO3sGl(X z@%P>VW-(YwgOlpyMMfvzY^PyJOR1%GTS3Nvp_FRmwSrNG(`rde@~Tvtiw4}${sJMU B$0Ps% diff --git a/dwm/dwm-6.2/dwm.o b/dwm/dwm-6.2/dwm.o index a7668a1804378c61db3c3e7faa5f1c17d87dba7b..02c4b6f50c033e2df3f714c21af7b24bcb00d5aa 100644 GIT binary patch delta 788 zcmZ9~F-RLx7zgnCp3<5iwk6Wz21&$%SLYca{44x&z3f~S;{s8x}uv4iMhvb9hs zns3qIpqrbKH|Tg-I@BTAnnVk2sU|s>2-4Y?zE>&l;CSzT|NH(2cbERw;*?spba0NJ zf3&6~B zUVQ}6pfBGS{GK2ve~%N)Z>uzr33QPl(BY#$0DJWH@dv_|JoUGdexScjHht_ee%9Lo z%NVcvaIbu;MG0?c6HU#flbtr+RMeC$QcFtT2p^UFt?-ECG2yo4)52qt&vKtu7NlTK z3?wCA5WX(?qVTlj3E?>$I#)I4zqA=F_4Aif#8sA-r1bL(p4RZ-TumvWsd-a#`$XfZ zF8PX>8jyTYxFxw0;yn?`f9u@Er|DDvJvi&*L0TFp8r%;texX8+Zd~}iWb_keuf9N< z{Ram8z}>0|B`jA{L@(e}Z3G-Vs2QF{nUBWsXU%|p)aoWQaI$Wa$2Ml`VbW=1g)IjA nTgIN^fUA>NHcq)=68VlD3T7U=upsZvq8+j@G{Rc?{8+9^le%6rYzhd32sbh2oXhPW$$)iBKX9p_4M^_hX01|X->B??V1vZY*xh>J?wxgM-0^L9O z0O+Oh0Rop}+za_^QA!V*gObSJq#%{e|9nc$#|<^RO8aSA`=etxqm$CVSEss4-yQ~ zt_OP+$!}ob^$eEv5Anb?j^$+tV>}-rnM-Uc3_uV=1&<|`W32-26+Ga>TtS8y+BBIQ yDj3kbB&Fc4UTze8^EY5gbCFvGn~Gi%Q!!BVLKNdgSr5wXr&He*-FN=Wy diff --git a/scripts/play-multimedia.sh b/scripts/play-multimedia.sh index ce5d5ea..2f0f91c 100644 --- a/scripts/play-multimedia.sh +++ b/scripts/play-multimedia.sh @@ -7,10 +7,13 @@ formats="$(echo "$formats" | tr ',' '|')" regex=".*\\.($formats)" +trash="{$(echo "$TRASH" | tr ':' ',')}" +echo $trash + multimedia="$(find "$HOME" -regextype posix-extended -regex "$regex" \ - -not -regex "$TRASH.*" | sed "s|^$HOME/||")" + -not -regex "$trash.*" | sed "s|^$HOME/||")" multimedia="${multimedia}$(find "$DEVICES_FOLDER" -regextype posix-extended \ - -regex "$regex" -not -regex "$TRASH.*")" + -regex "$regex" -not -regex "$trash.*")" media="$(echo "${multimedia}" | dmenu -i -l 9)" diff --git a/sent/LICENSE b/sent/LICENSE new file mode 100644 index 0000000..699b2fd --- /dev/null +++ b/sent/LICENSE @@ -0,0 +1,28 @@ +ISC-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 diff --git a/sent/Makefile b/sent/Makefile new file mode 100644 index 0000000..56e6367 --- /dev/null +++ b/sent/Makefile @@ -0,0 +1,60 @@ +# 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 diff --git a/sent/README.md b/sent/README.md new file mode 100644 index 0000000..c1e9385 --- /dev/null +++ b/sent/README.md @@ -0,0 +1,59 @@ +sent 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/ diff --git a/sent/arg.h b/sent/arg.h new file mode 100644 index 0000000..7f503ec --- /dev/null +++ b/sent/arg.h @@ -0,0 +1,49 @@ +/* + * 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 diff --git a/sent/config.def.h b/sent/config.def.h new file mode 100644 index 0000000..25d89e0 --- /dev/null +++ b/sent/config.def.h @@ -0,0 +1,59 @@ +/* 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; + +/* height of the presentation progress bar */ +static const int progressheight = 5; + +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" }, +}; diff --git a/sent/config.h b/sent/config.h new file mode 100644 index 0000000..25d89e0 --- /dev/null +++ b/sent/config.h @@ -0,0 +1,59 @@ +/* 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; + +/* height of the presentation progress bar */ +static const int progressheight = 5; + +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" }, +}; diff --git a/sent/config.mk b/sent/config.mk new file mode 100644 index 0000000..d61c554 --- /dev/null +++ b/sent/config.mk @@ -0,0 +1,30 @@ +# 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 diff --git a/sent/drw.c b/sent/drw.c new file mode 100644 index 0000000..c1582e7 --- /dev/null +++ b/sent/drw.c @@ -0,0 +1,421 @@ +/* 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); +} diff --git a/sent/drw.h b/sent/drw.h new file mode 100644 index 0000000..4c67419 --- /dev/null +++ b/sent/drw.h @@ -0,0 +1,57 @@ +/* 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); diff --git a/sent/example b/sent/example new file mode 100644 index 0000000..300577a --- /dev/null +++ b/sent/example @@ -0,0 +1,69 @@ +sent + +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? diff --git a/sent/nyan.png b/sent/nyan.png new file mode 100644 index 0000000000000000000000000000000000000000..377b9d0843dac6e6ef1318612973a780a79f01a8 GIT binary patch literal 901 zcmeAS@N?(olHy`uVBq!ia0y~yV7vyzvMkI%5ohUWIUvQ7?&#~tz_78O`%fY(kgt&J z5#-CjP^HGe(9pub@Czu^@PdJ%)PRBERRRNp)eHs(@q#(K0&Rd2q5(c3uK)l42QvSk znfZU_|Nq7_f#Cnlnaj4cJ^+d`CV9KNFm$lWdH^|``EG8-K$;(jB^fr=2e~tO*8aRmFqGvw) z!{6oc+F=6y)nQ2BT3YtszWREf)c(WmiT07m;C-@fsAE{?R2cYrbxm^A=2DMonRXo4 zc%$-lxBPp~fY1b=3O*Q~g@ES|YnwH~RRBYrnIm=@*W%M3M1aT zF*3F?G_f)=&^9o#GB7x|&hZtBhTQy=%(O~04F;B0h87SFW{;mN0%~CJboFyt=akR{ E0CNm&1ONa4 literal 0 HcmV?d00001 diff --git a/sent/sent-1.tar.gz b/sent/sent-1.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..e09f7766068f2370727e5919c80f05e738f2c3d8 GIT binary patch literal 15285 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 +Date: Wed, 13 Feb 2019 14:28:17 -0500 +Subject: [PATCH] Commandline Options + +A simple patch that adds extra commandline options to sent. +--- + sent.1 | 11 +++++++++++ + sent.c | 11 ++++++++++- + 2 files changed, 21 insertions(+), 1 deletion(-) + +diff --git a/sent.1 b/sent.1 +index fabc614..5d55bf4 100644 +--- a/sent.1 ++++ b/sent.1 +@@ -5,6 +5,9 @@ + .Nd simple plaintext presentation tool + .Sh SYNOPSIS + .Nm ++.Op Fl f Ar font ++.Op Fl c Ar fgcolor ++.Op Fl b Ar bgcolor + .Op Fl v + .Op Ar file + .Sh DESCRIPTION +@@ -21,6 +24,14 @@ few minutes. + .Bl -tag -width Ds + .It Fl v + Print version information to stdout and exit. ++.It Fl f Ar font ++Defines the ++.Ar font ++when sent is run. ++.It Fl c Ar fgcolor ++Defines the foreground color when sent is run. ++.It Fl b Ar bgcolor ++Defines the background color when sent is run. + .El + .Sh USAGE + .Bl -tag -width Ds +diff --git a/sent.c b/sent.c +index c50a572..0b36e32 100644 +--- a/sent.c ++++ b/sent.c +@@ -675,7 +675,7 @@ configure(XEvent *e) + void + usage() + { +- die("usage: %s [file]", argv0); ++ die("usage: %s [-c fgcolor] [-b bgcolor] [-f font] [file]", argv0); + } + + int +@@ -687,6 +687,15 @@ main(int argc, char *argv[]) + case 'v': + fprintf(stderr, "sent-"VERSION"\n"); + return 0; ++ case 'f': ++ fontfallbacks[0] = EARGF(usage()); ++ break; ++ case 'c': ++ colors[0] = EARGF(usage()); ++ break; ++ case 'b': ++ colors[1] = EARGF(usage()); ++ break; + default: + usage(); + } ARGEND +-- +2.20.1 + diff --git a/sent/sent-progress-bar-1.0.diff b/sent/sent-progress-bar-1.0.diff new file mode 100644 index 0000000..9d0e7d2 --- /dev/null +++ b/sent/sent-progress-bar-1.0.diff @@ -0,0 +1,31 @@ +diff --git a/config.def.h b/config.def.h +index 60eb376..25d89e0 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -19,6 +19,9 @@ static const float linespacing = 1.4; + static const float usablewidth = 0.75; + static const float usableheight = 0.75; + ++/* height of the presentation progress bar */ ++static const int progressheight = 5; ++ + static Mousekey mshortcuts[] = { + /* button function argument */ + { Button1, advance, {.i = +1} }, +diff --git a/sent.c b/sent.c +index c50a572..046466e 100644 +--- a/sent.c ++++ b/sent.c +@@ -533,6 +533,12 @@ xdraw() + 0, + slides[idx].lines[i], + 0); ++ if (idx != 0 && progressheight != 0) { ++ drw_rect(d, ++ 0, xw.h - progressheight, ++ (xw.w * idx)/(slidecount - 1), progressheight, ++ 1, 0); ++ } + drw_map(d, xw.win, 0, 0, xw.w, xw.h); + } else { + if (!(im->state & SCALED)) diff --git a/sent/sent.1 b/sent/sent.1 new file mode 100644 index 0000000..5d55bf4 --- /dev/null +++ b/sent/sent.1 @@ -0,0 +1,79 @@ +.Dd 2016-08-12 +.Dt SENT 1 +.Sh NAME +.Nm sent +.Nd simple plaintext presentation tool +.Sh SYNOPSIS +.Nm +.Op Fl f Ar font +.Op Fl c Ar fgcolor +.Op Fl b Ar bgcolor +.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. +.It Fl f Ar font +Defines the +.Ar font +when sent is run. +.It Fl c Ar fgcolor +Defines the foreground color when sent is run. +.It Fl b Ar bgcolor +Defines the background color when sent is run. +.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 diff --git a/sent/sent.c b/sent/sent.c new file mode 100644 index 0000000..bb66d17 --- /dev/null +++ b/sent/sent.c @@ -0,0 +1,721 @@ +/* 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); + if (idx != 0 && progressheight != 0) { + drw_rect(d, + 0, xw.h - progressheight, + (xw.w * idx)/(slidecount - 1), progressheight, + 1, 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 [-c fgcolor] [-b bgcolor] [-f font] [file]", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp = NULL; + + ARGBEGIN { + case 'v': + fprintf(stderr, "sent-"VERSION"\n"); + return 0; + case 'f': + fontfallbacks[0] = EARGF(usage()); + break; + case 'c': + colors[0] = EARGF(usage()); + break; + case 'b': + colors[1] = EARGF(usage()); + break; + 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; +} diff --git a/sent/sent.c.orig b/sent/sent.c.orig new file mode 100644 index 0000000..b5cd7db --- /dev/null +++ b/sent/sent.c.orig @@ -0,0 +1,715 @@ +/* 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 [-c fgcolor] [-b bgcolor] [-f font] [file]", argv0); +} + +int +main(int argc, char *argv[]) +{ + FILE *fp = NULL; + + ARGBEGIN { + case 'v': + fprintf(stderr, "sent-"VERSION"\n"); + return 0; + case 'f': + fontfallbacks[0] = EARGF(usage()); + break; + case 'c': + colors[0] = EARGF(usage()); + break; + case 'b': + colors[1] = EARGF(usage()); + break; + 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; +} diff --git a/sent/transparent_test.ff b/sent/transparent_test.ff new file mode 100644 index 0000000000000000000000000000000000000000..505acaa36ca0058c016817256a4618af6268a798 GIT binary patch literal 20016 zcmeI1U24NX41{y0-l(O`Q=fW&NF%m_#kkf;t69ga3_*5Q(vH4y{>c6P=i~nM{dv3H z-u@oXP)89c0!08P@OVh><9To+u4wH{wh!8WZOZuc%8MN_MNNggURypZVrtIFV*8-| zvDsMsw|y}+XJjcacEo1ea${X%YR<@F`=I@?*;xFyeK9p>WGOFp#Ae%a8@ZP6j8e~z z*kouH+XwBB=^5cKJM{HzAGAL<8JeZM*b&py^OTB_Yvsj`*kouH+XwBB=^5cKJM{Hz zAGAL<8JeZM*b&py^OTB_Yvsj`*ksqt8sWe7kL`o@zb0u+b@_pDx?+~zzuYL@<$V>6 zS0tIE8E1sD+{@d0?#Q2<*{;~7{CF+b%8%Fb!>7jkz7x&&saiKX%GX=BE@9u~Uarx6 zpQ?4UqkO$}>k{@&?&TWI_o-SpJIdEvw=QAdsP%`{kqu}$E@{r3Hv6` z;%)WusaiMN;+VDmI)vLq?Y}7Id2He=-d3NOIz|=KgT8l6yTvhUJ*JLP)iYzB$Fyhh zw)%*=_HAuE#wOJ&rU!j*-Om>-j#=xXs{F9M$>MGGDL-s48l!gKT0BSB%a8J7pFq2_ z +#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); +} diff --git a/sent/util.h b/sent/util.h new file mode 100644 index 0000000..f633b51 --- /dev/null +++ b/sent/util.h @@ -0,0 +1,8 @@ +/* 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);