Compare commits

...

71 Commits
0.1 ... master

Author SHA1 Message Date
Tom Schwindl
882d54c225 remove unnecessary NULL checks and add void for an empty parameter list 2023-01-10 18:55:47 +01:00
Hiltjo Posthuma
08006e0d00 sent.1: fix some mandoc warnings 2023-01-09 18:27:22 +01:00
Hiltjo Posthuma
3f107fcf72 Makefile: be verbose 2023-01-09 18:27:12 +01:00
Augusto Castelo
c271dfb848 free XImage before creating a new one
ffprepare was allocating a new XImage but wasn't freeing the
previously allocated one
2023-01-09 18:22:20 +01:00
Chris Down
2649e8d533 Avoid out-of-bounds access when a slide input line begins with \0
If we read in a line with \0 at the beginning, blen will be 0. However,
we then try to index our copy of the buffer with
s->lines[s->linecount][blen-1], we'll read (and potentially write if the
data happens to be 0x0A) outside of strdup's allocated memory, and may
crash.

Fix this by just rejecting lines with a leading \0. Lines with nulls
embedded in other places don't invoke similar behaviour, since the
length is still >0.
2020-05-14 11:43:34 +02:00
Markus Teich
72d33d463f update license 2017-09-07 23:40:26 +02:00
Laslo Hunhold
a5f760fa0e Correctly handle files that contain no slides
Otherwise, on reloading sent would segfault. Move the check-logic to
load() rather than doing it in main() to prevent other misuses of it in
the future.
2017-09-07 23:37:00 +02:00
Markus Teich
53541ee1bd update license 2017-09-04 00:23:09 +02:00
Markus Teich
4736a6118d
release v1 2017-09-03 23:57:26 +02:00
Markus Teich
16e4843d05 allow to reload file 2017-09-02 19:30:11 +02:00
Markus Teich
ec431df440 fix some spelling in man page 2017-09-02 19:20:00 +02:00
Markus Teich
f925f567d7 update manpage 2017-09-02 02:17:39 +02:00
Markus Teich
278b8da820 migrate to new arg.h 2017-09-02 01:24:47 +02:00
Markus Teich
6df3d039a0 load all images before starting the preso 2017-09-02 01:09:36 +02:00
Markus Teich
0ff43aaf34 replace malloc() with ecalloc() 2016-11-04 19:50:09 +01:00
Markus Teich
ca7978c59b die() on calloc failure 2016-11-04 19:45:08 +01:00
Hiltjo Posthuma
8185d5eacb check memory allocation for calloc, die on failure 2016-10-10 20:51:15 +02:00
ssd
cd307e3ba0 README: add Xft to dependencies 2016-10-10 20:51:15 +02:00
Markus Teich
dca314e7a9 always redraw images
Previously when covering the sent window with an image slide selected and
exposing it again, the image was lost. This change always redraws the image and
also removes the now redundant bit flag in the state enum.
2016-09-26 12:26:11 +02:00
Hiltjo Posthuma
efea6575ca improve some more error messages
- remove \n in die() messages in drw.c
- add a few more ':' suffix for functions that set errno.
- minor typo.
2016-08-12 14:58:15 +02:00
FRIGN
b737e3e6af Add a manpage 2016-08-12 13:20:49 +02:00
FRIGN
c3a0671f68 Use Plan 9 style for ARGEND 2016-08-12 10:02:19 +02:00
FRIGN
b2daba3390 Fix error-messages 2016-08-12 10:01:06 +02:00
FRIGN
072a318adb Fix memory leak and print a warning on invalid regex
This will minimize the already low memory usage of sent.
2016-08-11 23:42:09 +02:00
FRIGN
e8914d5d4b _exit() instead of exit() after failed exec
die() calls exit(), but actually at this point the program could be in a
state not allowing this. It's better to call _exit to stop the libc from
calling any handlers that might fail at this moment anyway.
2016-08-11 23:42:09 +02:00
FRIGN
cd10166b5e Simplify LICENSE wording
Now, before you call your lawyers, here is some explanation:
The MIT/X Consortium-License and ISC-License are equivalent
according to the Berne-convention. This means that a court
of law presented with either licenses will act the same.

The advantage of the ISC license is that it has a simpler wording
and is recommended by the OpenBSD-project for new projects[0].
And as we at suckless.org love removing cruft, this also includes
removing legal cruft wording that is not needed any more.

[0]: http://www.openbsd.org/policy.html
2016-07-18 16:09:23 +02:00
FRIGN
e75676d9a4 Simplify usage() 2016-07-14 22:23:46 +02:00
Markus Teich
93329c72b5 Update example presentation
- Add a small transparent farbfeld test image
2016-07-14 12:44:05 +02:00
FRIGN
3af7e93324 add ff and ff.bz2 support in config.def.h 2016-07-14 12:23:53 +02:00
Markus Teich
e1a0b51d84 Also call ffload on the current slide in advance()
This fixes a bug where the image would not be loaded if the user setup a
keybinding which advances multiple slides at once. Then the preloading for the
previous/next slide in advance() would not catch that case and instead of an
image the text version (`@filename`) would be rendered on the slide.
2016-06-30 16:57:31 +02:00
Markus Teich
827f3de2e7 clean ffread and rename to ffload
- also remove unneeded memmove in load()
2016-06-30 16:42:34 +02:00
Hiltjo Posthuma
fd303ee9c1 load slide image on-demand
On Sat, Jun 04, 2016 at 05:28:31PM +0200, Markus Teich wrote:
> Hiltjo Posthuma wrote:
> > previously an image file would be opened but only ffread when advancing to
> > the slide, but when the slide was not used it gave an error:
> >
> > 	/usr/local/bin/2ff: failed to convert image/png
>
> Heyho Hiltjo,
>
> thanks for the patch. Unfortunately it does not work if the first slide contains
> an image (ffopen is only called in advance). I think it would be good to merge
> ffopen and ffread instead into a single function ffload. This makes the `LOADED`
> state clearer and also enforces that the fd is closed in the same function where
> it is opened. This ffload function should then be called in advance() replacing
> the ffread() calls if the image is not loaded yet and once in load() for the
> first slide if it is an image.
>

Ah yes, sorry for the oversight.

> If you want to take this new approach, go for it, otherwise I'll look into it
> myself.
>

I have attached a patch that does this, I hope you'll like it.

Also I'm not sure if we need the below code in advance(), I have removed it in
the patch:

        if (slidecount > idx + 1 && slides[idx + 1].img)
                ffread(slides[idx + 1].img);
        if (0 < idx && slides[idx - 1].img)
                ffread(slides[idx - 1].img);

That seems to preload the next and previous slide image right? A minor issue I
notice also is that images seem to flicker, it uses XPutImage directly to
xw.win. Maybe it can be replaced with a backbuffer then XCopyArea? What do you
think?

In advance() it should also not always be needed to rescale the image.

--
Kind regards,
Hiltjo

From 97bebdcab4003f9acdfdd4bdf424449299ffd61d Mon Sep 17 00:00:00 2001
From: Hiltjo Posthuma <hiltjo@codemadness.org>
Date: Sat, 4 Jun 2016 21:34:25 +0200
Subject: [PATCH] merge ffread and ffopen into one function
2016-06-06 17:26:50 +02:00
Hiltjo Posthuma
44a50ad948 load slide image on-demand
previously an image file would be opened but only ffread when advancing to
the slide, but when the slide was not used it gave an error:

	/usr/local/bin/2ff: failed to convert image/png

this changes it to load the image on-demand once and "cache" it.
2016-06-04 17:26:50 +02:00
Markus Teich
27a904c1dd import new drw and util from libsl. 2016-05-24 19:28:44 +02:00
Markus Teich
268d1968ea fix include and lib paths for the BSDs 2016-03-15 23:51:30 +01:00
Markus Teich
ceda5d69ac support FreeBSD
On FreeBSD the lib and include directories are in /usr/local rather than /usr.
2016-03-15 20:21:54 +01:00
Markus Teich
c61e9a4b8a Import arg.h OOB fix from Lucas Gabriel Vuotto.
This is a small fix for arg.h where it's possible to perform an out-of-boundary
memory access in argv's internal string.
2016-03-06 20:56:29 +01:00
Markus Teich
b954ed4b9f Fix cmdline argument check
There was a segfault when sent was called without arguments. Now we use stdin
when there's no argument or - is used. Thanks to izabera for the report.
2016-03-06 20:49:18 +01:00
Markus Teich
616aafdcb9 no need to reimplement cat here 2016-02-06 14:12:47 +01:00
Markus Teich
3019229279 improve error handling
- errors are now fatal
- no need to clean up when dying
- no need for status return codes => ffread and ffprepare now return void
2016-02-06 14:08:43 +01:00
Markus Teich
77809fae79 style: move function return types to own line 2016-02-05 16:17:43 +01:00
FRIGN
a95e592992 Use 2ff exclusively in config.def.h 2016-02-05 15:39:24 +01:00
FRIGN
68ac6486b1 Fix alpha blending
- "/ 257", because 255 * 257 = UINT16_MAX
- "/ 255", because that's the maximum possible RGB value
2016-02-05 15:35:59 +01:00
Markus Teich
b0ad67036e readme: update reference to farbfeld 2016-02-05 15:25:29 +01:00
David Phillips
022ae48360 Change an eprintf to a die to stop child from running its own slideshow
The child thread was created because execlp will not return if successful.
The eprintf was placed after the call to execlp to catch any error, but the
child continued to return a (closed) fds[0], resulting in a second slideshow
being run by the child.

This commit fixes the issue by killing the child.
2015-12-24 01:57:50 +01:00
Grant Mathews
54daf38b08 filter via shell pipeline
Instead of requiring an executable, allow building arbitrary shell
pipelines to filter filetypes through.
2015-12-24 01:51:29 +01:00
Markus Teich
11f2c648eb unbreak the build 2015-12-09 00:52:39 +01:00
Markus Teich
008ff1b88b fix 2 uninitialized variables and bin == NULL check 2015-12-09 00:50:05 +01:00
Markus Teich
edb4630564 check for bin == NULL 2015-12-08 22:34:48 +01:00
Markus Teich
24762d0172 minor syntax, style and README fixes 2015-12-08 22:09:26 +01:00
Markus Teich
0798e82d23 support transparent images 2015-12-08 21:46:18 +01:00
sin
b516f468fa Support farbfeld as an intermediate format
Sent now uses farbfeld[0] as an intermediate format.  A series of
filters is specified in config.h that matches file extensions to
filter programs.  The programs will convert between formats such as
png to farbfeld.  Internally in sent we do not need to worry on how
to parse png or any other format.

This also works with jpg and gif and others.  The 2ff wrapper will
use imagemagick conversion tools.  This is temporary as jpg2ff and
gif2ff will also be implemented.

To make this work, you will have to clone[0] and put png2ff and 2ff
in your PATH.

[0] http://git.2f30.org/farbfeld/
2015-12-08 20:01:36 +01:00
Markus Teich
7e558105e6 add 'sent: ' prefix to errors 2015-12-03 23:02:14 +01:00
Alexis
98e1854189 Add 'n' and 'p' keys to navigate slides 2015-12-03 23:00:53 +01:00
Alexis
91aeb01dce Refactor die() to use eprintf() 2015-12-03 23:00:44 +01:00
Alexis
824dae147e Use consistent wording for error messages 2015-12-03 23:00:28 +01:00
Ivan Tham
95af430d97 Use right click as previous and enable scrolling to switch slides 2015-11-19 19:37:08 +01:00
Quentin Rameau
a1dcdad14f Bail out before allocating slides if file is empty
In load() we allocated slides before checking if we actually read
anything from the FILE fp and then continue with an allocated but
“empty” space wich would lead to errors.
2015-11-18 23:26:33 +01:00
Markus Teich
eac14478e9 fix dying on empty slidefile 2015-11-18 23:24:53 +01:00
Quentin Rameau
996a4c5c6e drw.c: Avoid potential memory leak in drw_cur_create()
If drw was NULL, memory was still allocated for cur.
2015-11-18 22:50:07 +01:00
Ivan Tham
243c8cf7b6 Add Escape key to quit sent 2015-11-18 12:20:55 +01:00
Markus Teich
cc57a28eba die if no font could be loaded 2015-11-18 00:41:38 +01:00
Jan Christoph Ebersbach
c3cd2edca6 Register space to advance to the next slide 2015-11-17 11:23:20 +01:00
Markus Teich
448fe33370 shorten bulletpoints in example presentation 2015-11-17 02:26:34 +01:00
Markus Teich
31be676486 fix dejavu font name 2015-11-17 02:07:56 +01:00
Markus Teich
4ae761b8be support even larger screens 2015-11-17 01:39:40 +01:00
Markus Teich
4a828c2d6c prevent flickering when changing slides
When the window background is left at WhitePixel (from initialization) and the
user has set a dark background color in config.h, the window shortly displays
the white default background when clearing after advancing to another slide.
This was very disturbing and is fixed now by setting the default window
background color to the bgcol chosen in config.h.
2015-11-17 01:04:04 +01:00
Markus Teich
aa713a8a34 update README 2015-11-17 00:32:45 +01:00
Markus Teich
b1cb3bbc20 fix another index overflow issue 2015-11-17 00:23:51 +01:00
Markus Teich
84734c73e8 fix several number overflow issues 2015-11-17 00:19:39 +01:00
Markus Teich
eb07f3dc10 update version 2015-11-17 00:18:54 +01:00
14 changed files with 717 additions and 601 deletions

44
LICENSE
View File

@ -1,23 +1,29 @@
The MIT License (MIT) ISC-License
Copyright (c) 2014-2015 Markus Teich (c) 2014-2017 Markus Teich <markus.teich@stusta.mhn.de>
png handling stuff adapted from meh by John Hawthorn 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.
Permission is hereby granted, free of charge, to any person obtaining a copy THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
of this software and associated documentation files (the "Software"), to deal WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
in the Software without restriction, including without limitation the rights MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
copies of the Software, and to permit persons to whom the Software is WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
furnished to do so, subject to the following conditions: ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
The above copyright notice and this permission notice shall be included in all (c) 2016,2017 Laslo Hunhold <dev@frign.de>
copies or substantial portions of the Software. (c) 2016 ssd <ssd@mailless.org>
(c) 2016 Hiltjo Posthuma <hiltjo@codemadness.org>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR (c) 2015 David Phillips <dbphillipsnz@gmail.com>
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, (c) 2015 Grant Mathews <grant.m.mathews@gmail.com>
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE (c) 2015 Dimitris Papastamos <sin@2f30.org>
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER (c) 2015 Alexis <surryhill@gmail.com>
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, (c) 2015 Quentin Rameau <quinq@fifth.space>
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE (c) 2015 Ivan Tham <pickfire@riseup.net>
SOFTWARE. (c) 2015 Jan Christoph Ebersbach <jceb@e-jc.de>
(c) 2015 Tony Lainson <t.lainson@gmail.com>
(c) 2015 Szabolcs Nagy <nsz@port70.net>
(c) 2015 Jonas Jelten <jj@sft.mx>

View File

@ -18,39 +18,35 @@ config.h:
cp config.def.h config.h cp config.def.h config.h
.c.o: .c.o:
@echo CC $< ${CC} -c ${CFLAGS} $<
@${CC} -c ${CFLAGS} $<
${OBJ}: config.h config.mk ${OBJ}: config.h config.mk
sent: ${OBJ} sent: ${OBJ}
@echo CC -o $@ ${CC} -o $@ ${OBJ} ${LDFLAGS}
@${CC} -o $@ ${OBJ} ${LDFLAGS}
cscope: ${SRC} config.h cscope: ${SRC} config.h
@echo cScope cscope -R -b || echo cScope not installed
@cscope -R -b || echo cScope not installed
clean: clean:
@echo cleaning rm -f sent ${OBJ} sent-${VERSION}.tar.gz
@rm -f sent ${OBJ} sent-${VERSION}.tar.gz
dist: clean dist: clean
@echo creating dist tarball mkdir -p sent-${VERSION}
@mkdir -p sent-${VERSION} cp -R LICENSE Makefile config.mk config.def.h ${SRC} sent-${VERSION}
@cp -R LICENSE Makefile config.mk config.def.h ${SRC} sent-${VERSION} tar -cf sent-${VERSION}.tar sent-${VERSION}
@tar -cf sent-${VERSION}.tar sent-${VERSION} gzip sent-${VERSION}.tar
@gzip sent-${VERSION}.tar rm -rf sent-${VERSION}
@rm -rf sent-${VERSION}
install: all install: all
@echo installing executable file to ${DESTDIR}${PREFIX}/bin mkdir -p ${DESTDIR}${PREFIX}/bin
@mkdir -p ${DESTDIR}${PREFIX}/bin cp -f sent ${DESTDIR}${PREFIX}/bin
@cp -f sent ${DESTDIR}${PREFIX}/bin chmod 755 ${DESTDIR}${PREFIX}/bin/sent
@chmod 755 ${DESTDIR}${PREFIX}/bin/sent mkdir -p ${DESTDIR}${MANPREFIX}/man1
cp sent.1 ${DESTDIR}${MANPREFIX}/man1/sent.1
chmod 644 ${DESTDIR}${MANPREFIX}/man1/sent.1
uninstall: uninstall:
@echo removing executable file from ${DESTDIR}${PREFIX}/bin rm -f ${DESTDIR}${PREFIX}/bin/sent
@rm -f ${DESTDIR}${PREFIX}/bin/sent
.PHONY: all options clean dist install uninstall cscope .PHONY: all options clean dist install uninstall cscope

View File

@ -1,70 +1,59 @@
sent sent is a simple plaintext presentation tool.
====
A simple plaintext presentation tool.
sent does not need latex, libreoffice or any other fancy file format, it uses sent does not need latex, libreoffice or any other fancy file format, it uses
plaintext files and png images. Every line represents a slide in the plaintext files to describe the slides and can include images via farbfeld.
presentation. This may limit the use, but for presentations using the [Takahashi Every paragraph represents a slide in the presentation.
method](https://en.wikipedia.org/wiki/Takahashi_method) this is very nice and
allows you to write down the presentation for a quick lightning talk within a
few minutes.
The presentation is displayed in a simple X11 window colored black on white for The presentation is displayed in a simple X11 window. The content of each slide
maximum contrast even if the sun shines directly onto the projected image. The is automatically scaled to fit the window and centered so you also don't have to
content of each slide is automatically scaled to fit the window so you don't worry about alignment. Instead you can really concentrate on the content.
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 Demo
----
To get a little demo, just type To get a little demo, just type
make && ./sent example make && ./sent example
You can navigate with the arrow keys and quit with `q`. If you get You can navigate with the arrow keys and quit with `q`.
sent: could not find a scalable font matching -*-dejavu sans condensed-bold-r-*-*-0-0-*-*-*-0-*-*
you should add the dejavu fonts dir (customize path to fit your distribution)
with:
xset fp+ /usr/share/fonts/dejavu
Configuration
-------------
Edit config.h to fit your needs. The font has to be in the X servers font path,
see `man xset` for how to add it.
Usage Usage
-----
sent [-f FONTSTRING] FILE1 [FILE2 ...] sent [FILE]
If one FILE equals `-`, stdin will be read. Use png images by prepending a `@` If FILE is omitted or equals `-`, stdin will be read. Produce image slides by
before the filename. Lines starting with `#` will be ignored. A presentation prepending a `@` in front of the filename as a single paragraph. Lines starting
file could look like this: with `#` will be ignored. A `\` at the beginning of the line escapes `@` and
`#`. A presentation file could look like this:
sent sent
why?
@nyan.png @nyan.png
easy to use
depends on Xlib, libpng depends on
no bloat - Xlib
how? - Xft
- farbfeld
sent FILENAME sent FILENAME
one slide per line one slide per paragraph
# This is a comment and will not be part of the presentation # This is a comment and will not be part of the presentation
# The next line starts with a whitespace, it will not produce an image slide \# This and the next line start with backslashes
@FILE.png
\@FILE.png
thanks / questions? thanks / questions?
future features
---------------
* multiple lines per slide? Development
* light colored background and table of contents
* second window for speakers laptop (progress, time, notes?) sent is developed at http://tools.suckless.org/sent
* markdown?
0: http://tools.suckless.org/farbfeld/

98
arg.h
View File

@ -1,63 +1,49 @@
/* /*
* Copy me if you can. * ISC-License
* by 20h *
* Copyright 2017 Laslo Hunhold <dev@frign.de>
*
* 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
#ifndef ARG_H__ #define ARG_H
#define ARG_H__
extern char *argv0; extern char *argv0;
/* use main(int argc, char *argv[]) */ /* int main(int argc, char *argv[]) */
#define ARGBEGIN for (argv0 = *argv, argv++, argc--;\ #define ARGBEGIN for (argv0 = *argv, *argv ? (argc--, argv++) : ((void *)0); \
argv[0] && argv[0][1]\ *argv && (*argv)[0] == '-' && (*argv)[1]; argc--, argv++) { \
&& argv[0][0] == '-';\ int i_, argused_; \
argc--, argv++) {\ if ((*argv)[1] == '-' && !(*argv)[2]) { \
char argc_;\ argc--, argv++; \
char **argv_;\ break; \
int brk_;\ } \
if (argv[0][1] == '-' && argv[0][2] == '\0') {\ for (i_ = 1, argused_ = 0; (*argv)[i_]; i_++) { \
argv++;\ switch((*argv)[i_])
argc--;\ #define ARGEND if (argused_) { \
break;\ if ((*argv)[i_ + 1]) { \
}\ break; \
for (brk_ = 0, argv[0]++, argv_ = argv;\ } else { \
argv[0][0] && !brk_;\ argc--, argv++; \
argv[0]++) {\ break; \
if (argv_ != argv)\ } \
break;\ } \
argc_ = argv[0][0];\ } \
switch (argc_) }
#define ARGC() ((*argv)[i_])
/* Handles obsolete -NUM syntax */ #define ARGF_(x) (((*argv)[i_ + 1]) ? (argused_ = 1, &((*argv)[i_ + 1])) : \
#define ARGNUM case '0':\ (*(argv + 1)) ? (argused_ = 1, *(argv + 1)) : (x))
case '1':\ #define EARGF(x) ARGF_(((x), exit(1), (char *)0))
case '2':\ #define ARGF() ARGF_((char *)0)
case '3':\
case '4':\
case '5':\
case '6':\
case '7':\
case '8':\
case '9'
#define ARGEND }\
}
#define ARGC() argc_
#define ARGNUMF(base) (brk_ = 1, estrtol(argv[0], (base)))
#define EARGF(x) ((argv[0][1] == '\0' && argv[1] == NULL)?\
((x), abort(), (char *)0) :\
(brk_ = 1, (argv[0][1] != '\0')?\
(&argv[0][1]) :\
(argc--, argv++, argv[0])))
#define ARGF() ((argv[0][1] == '\0' && argv[1] == NULL)?\
(char *)0 :\
(brk_ = 1, (argv[0][1] != '\0')?\
(&argv[0][1]) :\
(argc--, argv++, argv[0])))
#endif #endif

View File

@ -1,15 +1,17 @@
/* See LICENSE file for copyright and license details. */ /* See LICENSE file for copyright and license details. */
static char *fontfallbacks[] = { static char *fontfallbacks[] = {
"dejavu", "dejavu sans",
"roboto", "roboto",
"ubuntu", "ubuntu",
}; };
#define NUMFONTSCALES 30 #define NUMFONTSCALES 42
#define FONTSZ(x) ((int)(10.0 * powf(1.1288, (x)))) /* x in [0, NUMFONTSCALES-1] */ #define FONTSZ(x) ((int)(10.0 * powf(1.1288, (x)))) /* x in [0, NUMFONTSCALES-1] */
static const char *fgcol = "#000000"; static const char *colors[] = {
static const char *bgcol = "#FFFFFF"; "#000000", /* foreground color */
"#FFFFFF", /* background color */
};
static const float linespacing = 1.4; static const float linespacing = 1.4;
@ -20,15 +22,19 @@ static const float usableheight = 0.75;
static Mousekey mshortcuts[] = { static Mousekey mshortcuts[] = {
/* button function argument */ /* button function argument */
{ Button1, advance, {.i = +1} }, { Button1, advance, {.i = +1} },
{ Button2, advance, {.i = -1} }, { Button3, advance, {.i = -1} },
{ Button4, advance, {.i = -1} },
{ Button5, advance, {.i = +1} },
}; };
static Shortcut shortcuts[] = { static Shortcut shortcuts[] = {
/* keysym function argument */ /* keysym function argument */
{ XK_Escape, quit, {0} },
{ XK_q, quit, {0} }, { XK_q, quit, {0} },
{ XK_Right, advance, {.i = +1} }, { XK_Right, advance, {.i = +1} },
{ XK_Left, advance, {.i = -1} }, { XK_Left, advance, {.i = -1} },
{ XK_Return, advance, {.i = +1} }, { XK_Return, advance, {.i = +1} },
{ XK_space, advance, {.i = +1} },
{ XK_BackSpace, advance, {.i = -1} }, { XK_BackSpace, advance, {.i = -1} },
{ XK_l, advance, {.i = +1} }, { XK_l, advance, {.i = +1} },
{ XK_h, advance, {.i = -1} }, { XK_h, advance, {.i = -1} },
@ -38,4 +44,13 @@ static Shortcut shortcuts[] = {
{ XK_Up, advance, {.i = -1} }, { XK_Up, advance, {.i = -1} },
{ XK_Next, advance, {.i = +1} }, { XK_Next, advance, {.i = +1} },
{ XK_Prior, 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" },
}; };

View File

@ -1,5 +1,5 @@
# sent version # sent version
VERSION = 0.1 VERSION = 1
# Customize below to fit your system # Customize below to fit your system
@ -12,7 +12,12 @@ X11LIB = /usr/X11R6/lib
# includes and libs # includes and libs
INCS = -I. -I/usr/include -I/usr/include/freetype2 -I${X11INC} INCS = -I. -I/usr/include -I/usr/include/freetype2 -I${X11INC}
LIBS = -L/usr/lib -lc -lm -L${X11LIB} -lXft -lfontconfig -lX11 -lpng 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 # flags
CPPFLAGS = -DVERSION=\"${VERSION}\" -D_XOPEN_SOURCE=600 CPPFLAGS = -DVERSION=\"${VERSION}\" -D_XOPEN_SOURCE=600

326
drw.c
View File

@ -9,9 +9,7 @@
#include "util.h" #include "util.h"
#define UTF_INVALID 0xFFFD #define UTF_INVALID 0xFFFD
#define UTF_SIZ 4 #define UTF_SIZ 4
static void drw_xfont_free(Fnt *font);
static const unsigned char utfbyte[UTF_SIZ + 1] = {0x80, 0, 0xC0, 0xE0, 0xF0}; 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 unsigned char utfmask[UTF_SIZ + 1] = {0xC0, 0x80, 0xE0, 0xF0, 0xF8};
@ -19,50 +17,54 @@ 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 const long utfmax[UTF_SIZ + 1] = {0x10FFFF, 0x7F, 0x7FF, 0xFFFF, 0x10FFFF};
static long static long
utf8decodebyte(const char c, size_t *i) { utf8decodebyte(const char c, size_t *i)
for(*i = 0; *i < (UTF_SIZ + 1); ++(*i)) {
if(((unsigned char)c & utfmask[*i]) == utfbyte[*i]) for (*i = 0; *i < (UTF_SIZ + 1); ++(*i))
if (((unsigned char)c & utfmask[*i]) == utfbyte[*i])
return (unsigned char)c & ~utfmask[*i]; return (unsigned char)c & ~utfmask[*i];
return 0; return 0;
} }
static size_t static size_t
utf8validate(long *u, size_t i) { utf8validate(long *u, size_t i)
if(!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF)) {
if (!BETWEEN(*u, utfmin[i], utfmax[i]) || BETWEEN(*u, 0xD800, 0xDFFF))
*u = UTF_INVALID; *u = UTF_INVALID;
for(i = 1; *u > utfmax[i]; ++i) for (i = 1; *u > utfmax[i]; ++i)
; ;
return i; return i;
} }
static size_t static size_t
utf8decode(const char *c, long *u, size_t clen) { utf8decode(const char *c, long *u, size_t clen)
{
size_t i, j, len, type; size_t i, j, len, type;
long udecoded; long udecoded;
*u = UTF_INVALID; *u = UTF_INVALID;
if(!clen) if (!clen)
return 0; return 0;
udecoded = utf8decodebyte(c[0], &len); udecoded = utf8decodebyte(c[0], &len);
if(!BETWEEN(len, 1, UTF_SIZ)) if (!BETWEEN(len, 1, UTF_SIZ))
return 1; return 1;
for(i = 1, j = 1; i < clen && j < len; ++i, ++j) { for (i = 1, j = 1; i < clen && j < len; ++i, ++j) {
udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type); udecoded = (udecoded << 6) | utf8decodebyte(c[i], &type);
if(type != 0) if (type)
return j; return j;
} }
if(j < len) if (j < len)
return 0; return 0;
*u = udecoded; *u = udecoded;
utf8validate(u, len); utf8validate(u, len);
return len; return len;
} }
Drw * Drw *
drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h) { drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h)
Drw *drw = (Drw *)calloc(1, sizeof(Drw)); {
if(!drw) Drw *drw = ecalloc(1, sizeof(Drw));
return NULL;
drw->dpy = dpy; drw->dpy = dpy;
drw->screen = screen; drw->screen = screen;
drw->root = root; drw->root = root;
@ -71,22 +73,26 @@ drw_create(Display *dpy, int screen, Window root, unsigned int w, unsigned int h
drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen)); drw->drawable = XCreatePixmap(dpy, root, w, h, DefaultDepth(dpy, screen));
drw->gc = XCreateGC(dpy, root, 0, NULL); drw->gc = XCreateGC(dpy, root, 0, NULL);
XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter); XSetLineAttributes(dpy, drw->gc, 1, LineSolid, CapButt, JoinMiter);
return drw; return drw;
} }
void void
drw_resize(Drw *drw, unsigned int w, unsigned int h) { drw_resize(Drw *drw, unsigned int w, unsigned int h)
if(!drw) {
if (!drw)
return; return;
drw->w = w; drw->w = w;
drw->h = h; drw->h = h;
if(drw->drawable != 0) if (drw->drawable)
XFreePixmap(drw->dpy, drw->drawable); XFreePixmap(drw->dpy, drw->drawable);
drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen)); drw->drawable = XCreatePixmap(drw->dpy, drw->root, w, h, DefaultDepth(drw->dpy, drw->screen));
} }
void void
drw_free(Drw *drw) { drw_free(Drw *drw)
{
XFreePixmap(drw->dpy, drw->drawable); XFreePixmap(drw->dpy, drw->drawable);
XFreeGC(drw->dpy, drw->gc); XFreeGC(drw->dpy, drw->gc);
free(drw); free(drw);
@ -96,119 +102,123 @@ drw_free(Drw *drw) {
* drw_fontset_create instead. * drw_fontset_create instead.
*/ */
static Fnt * static Fnt *
drw_font_xcreate(Drw *drw, const char *fontname, FcPattern *fontpattern) { xfont_create(Drw *drw, const char *fontname, FcPattern *fontpattern)
{
Fnt *font; Fnt *font;
XftFont *xfont = NULL;
if (!(fontname || fontpattern)) FcPattern *pattern = NULL;
die("No font specified.\n");
if (!(font = (Fnt *)calloc(1, sizeof(Fnt))))
return NULL;
if (fontname) { if (fontname) {
/* Using the pattern found at font->xfont->pattern does not yield the /* Using the pattern found at font->xfont->pattern does not yield the
* same substitution results as using the pattern returned by * same substitution results as using the pattern returned by
* FcNameParse; using the latter results in the desired fallback * FcNameParse; using the latter results in the desired fallback
* behaviour whereas the former just results in * behaviour whereas the former just results in missing-character
* missing-character-rectangles being drawn, at least with some fonts. * rectangles being drawn, at least with some fonts. */
*/ if (!(xfont = XftFontOpenName(drw->dpy, drw->screen, fontname))) {
if (!(font->xfont = XftFontOpenName(drw->dpy, drw->screen, fontname)) || fprintf(stderr, "error, cannot load font from name: '%s'\n", fontname);
!(font->pattern = FcNameParse((const FcChar8 *) fontname))) { return NULL;
if (font->xfont) { }
XftFontClose(drw->dpy, font->xfont); if (!(pattern = FcNameParse((FcChar8 *) fontname))) {
font->xfont = NULL; fprintf(stderr, "error, cannot parse font name to pattern: '%s'\n", fontname);
} XftFontClose(drw->dpy, xfont);
fprintf(stderr, "error, cannot load font: '%s'\n", fontname); return NULL;
} }
} else if (fontpattern) { } else if (fontpattern) {
if (!(font->xfont = XftFontOpenPattern(drw->dpy, fontpattern))) { if (!(xfont = XftFontOpenPattern(drw->dpy, fontpattern))) {
fprintf(stderr, "error, cannot load font pattern.\n"); fprintf(stderr, "error, cannot load font from pattern.\n");
} else { return NULL;
font->pattern = NULL;
} }
} else {
die("no font specified.");
} }
if (!font->xfont) { font = ecalloc(1, sizeof(Fnt));
free(font); font->xfont = xfont;
return NULL; font->pattern = pattern;
} font->h = xfont->ascent + xfont->descent;
font->ascent = font->xfont->ascent;
font->descent = font->xfont->descent;
font->h = font->ascent + font->descent;
font->dpy = drw->dpy; font->dpy = drw->dpy;
return font; return font;
} }
void static void
drw_xfont_free(Fnt *font) { xfont_free(Fnt *font)
if(!font) {
if (!font)
return; return;
if(font->pattern) if (font->pattern)
FcPatternDestroy(font->pattern); FcPatternDestroy(font->pattern);
XftFontClose(font->dpy, font->xfont); XftFontClose(font->dpy, font->xfont);
free(font); free(font);
} }
Fnt* Fnt*
drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount) { drw_fontset_create(Drw* drw, const char *fonts[], size_t fontcount)
Fnt *ret = NULL; {
Fnt *cur = NULL; Fnt *cur, *ret = NULL;
ssize_t i; size_t i;
for (i = fontcount - 1; i >= 0; i--) {
if ((cur = drw_font_xcreate(drw, fonts[i], NULL))) { if (!drw || !fonts)
return NULL;
for (i = 1; i <= fontcount; i++) {
if ((cur = xfont_create(drw, fonts[fontcount - i], NULL))) {
cur->next = ret; cur->next = ret;
ret = cur; 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; return ret;
} }
void void
drw_fontset_free(Fnt *font) { drw_setfontset(Drw *drw, Fnt *set)
Fnt *nf = font; {
while ((font = nf)) {
nf = font->next;
drw_xfont_free(font);
}
}
Scm *
drw_scm_create(Drw *drw, const char *fgname, const char *bgname) {
Scm *scm;
Colormap cmap;
Visual *vis;
if (!drw || !(scm = (Scm *)calloc(1, sizeof(Scm))))
return NULL;
cmap = DefaultColormap(drw->dpy, drw->screen);
vis = DefaultVisual(drw->dpy, drw->screen);
if (!XftColorAllocName(drw->dpy, vis, cmap, fgname, &scm->fg.rgb))
die("error, cannot allocate color '%s'\n", fgname);
if (!XftColorAllocName(drw->dpy, vis, cmap, bgname, &scm->bg.rgb))
die("error, cannot allocate color '%s'\n", bgname);
scm->fg.pix = scm->fg.rgb.pixel;
scm->bg.pix = scm->bg.rgb.pixel;
return scm;
}
void
drw_scm_free(Scm *scm) {
if (scm)
free(scm);
}
void
drw_setfontset(Drw *drw, Fnt *set) {
if (drw) if (drw)
drw->fonts = set; drw->fonts = set;
} }
void void
drw_setscheme(Drw *drw, Scm *scm) { drw_setscheme(Drw *drw, Clr *scm)
if (drw && scm) {
if (drw)
drw->scheme = scm; drw->scheme = scm;
} }
@ -217,24 +227,23 @@ drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int
{ {
if (!drw || !drw->scheme) if (!drw || !drw->scheme)
return; return;
XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->bg.pix : drw->scheme->fg.pix); XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme[ColBg].pixel : drw->scheme[ColFg].pixel);
if (filled) if (filled)
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
else else
XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); XDrawRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w - 1, h - 1);
} }
int int
drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *text, int invert) { 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]; char buf[1024];
int tx, ty, th; int ty;
unsigned int ew; unsigned int ew;
Colormap cmap; XftDraw *d = NULL;
Visual *vis;
XftDraw *d;
Fnt *usedfont, *curfont, *nextfont; Fnt *usedfont, *curfont, *nextfont;
size_t i, len; size_t i, len;
int utf8strlen, utf8charlen, render; int utf8strlen, utf8charlen, render = x || y || w || h;
long utf8codepoint = 0; long utf8codepoint = 0;
const char *utf8str; const char *utf8str;
FcCharSet *fccharset; FcCharSet *fccharset;
@ -243,23 +252,19 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *tex
XftResult result; XftResult result;
int charexists = 0; int charexists = 0;
if (!(render = x || y || w || h)) { if (!drw || (render && !drw->scheme) || !text || !drw->fonts)
return 0;
if (!render) {
w = ~w; w = ~w;
} } else {
XSetForeground(drw->dpy, drw->gc, drw->scheme[invert ? ColFg : ColBg].pixel);
if (!drw || !drw->scheme) {
return 0;
} else if (render) {
XSetForeground(drw->dpy, drw->gc, invert ? drw->scheme->fg.pix : drw->scheme->bg.pix);
XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h); XFillRectangle(drw->dpy, drw->drawable, drw->gc, x, y, w, h);
} d = XftDrawCreate(drw->dpy, drw->drawable,
DefaultVisual(drw->dpy, drw->screen),
if (!text || !drw->fonts) { DefaultColormap(drw->dpy, drw->screen));
return 0; x += lpad;
} else if (render) { w -= lpad;
cmap = DefaultColormap(drw->dpy, drw->screen);
vis = DefaultVisual(drw->dpy, drw->screen);
d = XftDrawCreate(drw->dpy, drw->drawable, vis, cmap);
} }
usedfont = drw->fonts; usedfont = drw->fonts;
@ -282,32 +287,30 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *tex
} }
} }
if (!charexists || nextfont) { if (!charexists || nextfont)
break; break;
} else { else
charexists = 0; charexists = 0;
}
} }
if (utf8strlen) { if (utf8strlen) {
drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL); drw_font_getexts(usedfont, utf8str, utf8strlen, &ew, NULL);
/* shorten text if necessary */ /* shorten text if necessary */
for(len = MIN(utf8strlen, (sizeof buf) - 1); len && (ew > w - drw->fonts->h || w < drw->fonts->h); len--) for (len = MIN(utf8strlen, sizeof(buf) - 1); len && ew > w; len--)
drw_font_getexts(usedfont, utf8str, len, &ew, NULL); drw_font_getexts(usedfont, utf8str, len, &ew, NULL);
if (len) { if (len) {
memcpy(buf, utf8str, len); memcpy(buf, utf8str, len);
buf[len] = '\0'; buf[len] = '\0';
if(len < utf8strlen) if (len < utf8strlen)
for(i = len; i && i > len - 3; buf[--i] = '.'); for (i = len; i && i > len - 3; buf[--i] = '.')
; /* NOP */
if (render) { if (render) {
th = usedfont->ascent + usedfont->descent; ty = y + (h - usedfont->h) / 2 + usedfont->xfont->ascent;
ty = y + (h / 2) - (th / 2) + usedfont->ascent; XftDrawStringUtf8(d, &drw->scheme[invert ? ColBg : ColFg],
tx = x + (h / 2); usedfont->xfont, x, ty, (XftChar8 *)buf, len);
XftDrawStringUtf8(d, invert ? &drw->scheme->bg.rgb : &drw->scheme->fg.rgb, usedfont->xfont, tx, ty, (XftChar8 *)buf, len);
} }
x += ew; x += ew;
w -= ew; w -= ew;
} }
@ -320,18 +323,15 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *tex
usedfont = nextfont; usedfont = nextfont;
} else { } else {
/* Regardless of whether or not a fallback font is found, the /* Regardless of whether or not a fallback font is found, the
* character must be drawn. * character must be drawn. */
*/
charexists = 1; charexists = 1;
fccharset = FcCharSetCreate(); fccharset = FcCharSetCreate();
FcCharSetAddChar(fccharset, utf8codepoint); FcCharSetAddChar(fccharset, utf8codepoint);
if (!drw->fonts->pattern) { if (!drw->fonts->pattern) {
/* Refer to the comment in drw_font_xcreate for more /* Refer to the comment in xfont_create for more information. */
* information. die("the first font in the cache must be loaded from a font string.");
*/
die("The first font in the cache must be loaded from a font string.\n");
} }
fcpattern = FcPatternDuplicate(drw->fonts->pattern); fcpattern = FcPatternDuplicate(drw->fonts->pattern);
@ -346,47 +346,50 @@ drw_text(Drw *drw, int x, int y, unsigned int w, unsigned int h, const char *tex
FcPatternDestroy(fcpattern); FcPatternDestroy(fcpattern);
if (match) { if (match) {
usedfont = drw_font_xcreate(drw, NULL, match); usedfont = xfont_create(drw, NULL, match);
if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) { if (usedfont && XftCharExists(drw->dpy, usedfont->xfont, utf8codepoint)) {
for (curfont = drw->fonts; curfont->next; curfont = curfont->next) for (curfont = drw->fonts; curfont->next; curfont = curfont->next)
; /* just find the end of the linked list */ ; /* NOP */
curfont->next = usedfont; curfont->next = usedfont;
} else { } else {
drw_xfont_free(usedfont); xfont_free(usedfont);
usedfont = drw->fonts; usedfont = drw->fonts;
} }
} }
} }
} }
if (d)
if (render) {
XftDrawDestroy(d); XftDrawDestroy(d);
}
return x; return x + (render ? w : 0);
} }
void void
drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h) { drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h)
if(!drw) {
if (!drw)
return; return;
XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y); XCopyArea(drw->dpy, drw->drawable, win, drw->gc, x, y, w, h, x, y);
XSync(drw->dpy, False); XSync(drw->dpy, False);
} }
unsigned int unsigned int
drw_fontset_getwidth(Drw *drw, const char *text) { drw_fontset_getwidth(Drw *drw, const char *text)
{
if (!drw || !drw->fonts || !text) if (!drw || !drw->fonts || !text)
return 0; return 0;
return drw_text(drw, 0, 0, 0, 0, text, 0); return drw_text(drw, 0, 0, 0, 0, 0, text, 0);
} }
void void
drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h) { drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h)
{
XGlyphInfo ext; XGlyphInfo ext;
if(!font || !text) if (!font || !text)
return; return;
XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext); XftTextExtentsUtf8(font->dpy, font->xfont, (XftChar8 *)text, len, &ext);
if (w) if (w)
*w = ext.xOff; *w = ext.xOff;
@ -395,19 +398,24 @@ drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w,
} }
Cur * Cur *
drw_cur_create(Drw *drw, int shape) { drw_cur_create(Drw *drw, int shape)
Cur *cur = (Cur *)calloc(1, sizeof(Cur)); {
Cur *cur;
if(!drw || !cur) if (!drw || !(cur = ecalloc(1, sizeof(Cur))))
return NULL; return NULL;
cur->cursor = XCreateFontCursor(drw->dpy, shape); cur->cursor = XCreateFontCursor(drw->dpy, shape);
return cur; return cur;
} }
void void
drw_cur_free(Drw *drw, Cur *cursor) { drw_cur_free(Drw *drw, Cur *cursor)
if(!drw || !cursor) {
if (!cursor)
return; return;
XFreeCursor(drw->dpy, cursor->cursor); XFreeCursor(drw->dpy, cursor->cursor);
free(cursor); free(cursor);
} }

28
drw.h
View File

@ -1,27 +1,19 @@
/* See LICENSE file for copyright and license details. */ /* See LICENSE file for copyright and license details. */
#define DRW_FONT_CACHE_SIZE 32
typedef struct { typedef struct {
Cursor cursor; Cursor cursor;
} Cur; } Cur;
typedef struct Fnt Fnt; typedef struct Fnt {
struct Fnt {
Display *dpy; Display *dpy;
int ascent;
int descent;
unsigned int h; unsigned int h;
XftFont *xfont; XftFont *xfont;
FcPattern *pattern; FcPattern *pattern;
Fnt *next; struct Fnt *next;
}; } Fnt;
typedef struct { enum { ColFg, ColBg }; /* Clr scheme index */
struct { typedef XftColor Clr;
unsigned long pix;
XftColor rgb;
} fg, bg;
} Scm;
typedef struct { typedef struct {
unsigned int w, h; unsigned int w, h;
@ -30,7 +22,7 @@ typedef struct {
Window root; Window root;
Drawable drawable; Drawable drawable;
GC gc; GC gc;
Scm *scheme; Clr *scheme;
Fnt *fonts; Fnt *fonts;
} Drw; } Drw;
@ -46,8 +38,8 @@ 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); void drw_font_getexts(Fnt *font, const char *text, unsigned int len, unsigned int *w, unsigned int *h);
/* Colorscheme abstraction */ /* Colorscheme abstraction */
Scm *drw_scm_create(Drw *drw, const char *fgname, const char *bgname); void drw_clr_create(Drw *drw, Clr *dest, const char *clrname);
void drw_scm_free(Scm *scm); Clr *drw_scm_create(Drw *drw, const char *clrnames[], size_t clrcount);
/* Cursor abstraction */ /* Cursor abstraction */
Cur *drw_cur_create(Drw *drw, int shape); Cur *drw_cur_create(Drw *drw, int shape);
@ -55,11 +47,11 @@ void drw_cur_free(Drw *drw, Cur *cursor);
/* Drawing context manipulation */ /* Drawing context manipulation */
void drw_setfontset(Drw *drw, Fnt *set); void drw_setfontset(Drw *drw, Fnt *set);
void drw_setscheme(Drw *drw, Scm *scm); void drw_setscheme(Drw *drw, Clr *scm);
/* Drawing functions */ /* Drawing functions */
void drw_rect(Drw *drw, int x, int y, unsigned int w, unsigned int h, int filled, int invert); 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, const char *text, 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 */ /* Map functions */
void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h); void drw_map(Drw *drw, Window win, int x, int y, unsigned int w, unsigned int h);

19
example
View File

@ -20,7 +20,8 @@ easy to use
depends on depends on
♽ Xlib ♽ Xlib
☢ libpng ☢ Xft
☃ farbfeld
~1000 lines of code ~1000 lines of code
@ -29,8 +30,8 @@ $ sent FILE1 [FILE2 …]
▸ one slide per paragraph ▸ one slide per paragraph
▸ lines starting with # are ignored ▸ lines starting with # are ignored
paragraphs starting with a @ line are png images image slide: paragraph containing @FILENAME
for an empty slide just use a \ as a paragraph ▸ empty slide: just use a \ as a paragraph
# This is a comment and will not be part of the presentation # This is a comment and will not be part of the presentation
@ -49,10 +50,20 @@ $ sent FILE1 [FILE2 …]
\#This line as well \#This line as well
⇒ Prepend a backslash to kill behaviour of special characters ⇒ 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 thanks.
questions? questions?

72
sent.1 Normal file
View File

@ -0,0 +1,72 @@
.Dd August 12, 2016
.Dt SENT 1
.Os
.Sh NAME
.Nm sent
.Nd simple plaintext presentation tool
.Sh SYNOPSIS
.Nm
.Op Fl v
.Op Ar file
.Sh DESCRIPTION
.Nm
is a simple plain text presentation tool for X. sent does not need LaTeX,
LibreOffice or any other fancy file format.
Instead, sent reads plain text describing the slides. sent can also draw
images.
.Pp
Every paragraph represents a slide in the presentation.
Especially for presentations using the Takahashi method this is very nice and
allows you to write the presentation for a quick lightning talk within a few
minutes.
.Sh OPTIONS
.Bl -tag -width Ds
.It Fl v
Print version information to stdout and exit.
.El
.Sh USAGE
.Bl -tag -width Ds
.It Em Mouse commands
.Bl -tag -width Ds
.It Sy Button1 | Button5
Go to next slide, if existent.
.It Sy Button3 | Button4
Go to previous slide, if existent.
.El
.It Em Keyboard commands
.Bl -tag -width Ds
.It Sy Escape | q
Quit.
.It Sy r
Reload the slides.
Only works on file input.
.It Sy Right | Return | Space | l | j | Down | Next | n
Go to next slide, if existent.
.It Sy Left | Backspace | h | k | Up | Prior | p
Go to previous slide, if existent.
.El
.El
.Sh FORMAT
The presentation file is made up of at least one paragraph, with an
empty line separating two slides.
Each input line is interpreted literally, except from control characters
at the beginning of lines described as follows:
.Bl -tag -width Ds
.It Sy @
Create individual slide containing the image pointed to by the filename
following the
.Sy @ .
.It Sy #
Ignore this input line.
.It Sy \e
Create input line using the characters following the
.Sy \e
without interpreting them.
.El
.Sh CUSTOMIZATION
.Nm
can be customized by creating a custom config.h and (re)compiling the
source code.
This keeps it fast, secure and simple.
.Sh SEE ALSO
.Xr 2ff 1

543
sent.c
View File

@ -1,12 +1,17 @@
/* See LICENSE for licence details. */ /* See LICENSE file for copyright and license details. */
#include <sys/types.h>
#include <arpa/inet.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <math.h> #include <math.h>
#include <png.h> #include <regex.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdint.h> #include <stdint.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include <X11/keysym.h> #include <X11/keysym.h>
#include <X11/XKBlib.h> #include <X11/XKBlib.h>
#include <X11/Xatom.h> #include <X11/Xatom.h>
@ -15,6 +20,7 @@
#include <X11/Xft/Xft.h> #include <X11/Xft/Xft.h>
#include "arg.h" #include "arg.h"
#include "util.h"
#include "drw.h" #include "drw.h"
char *argv0; char *argv0;
@ -26,9 +32,7 @@ char *argv0;
typedef enum { typedef enum {
NONE = 0, NONE = 0,
LOADED = 1, SCALED = 1,
SCALED = 2,
DRAWN = 4
} imgstate; } imgstate;
typedef struct { typedef struct {
@ -36,16 +40,19 @@ typedef struct {
unsigned int bufwidth, bufheight; unsigned int bufwidth, bufheight;
imgstate state; imgstate state;
XImage *ximg; XImage *ximg;
FILE *f;
png_structp png_ptr;
png_infop info_ptr;
int numpasses; int numpasses;
} Image; } Image;
typedef struct {
char *regex;
char *bin;
} Filter;
typedef struct { typedef struct {
unsigned int linecount; unsigned int linecount;
char **lines; char **lines;
Image *img; Image *img;
char *embed;
} Slide; } Slide;
/* Purely graphic info */ /* Purely graphic info */
@ -79,27 +86,25 @@ typedef struct {
const Arg arg; const Arg arg;
} Shortcut; } Shortcut;
static Image *pngopen(char *filename); static void fffree(Image *img);
static void pngfree(Image *img); static void ffload(Slide *s);
static int pngread(Image *img); static void ffprepare(Image *img);
static int pngprepare(Image *img); static void ffscale(Image *img);
static void pngscale(Image *img); static void ffdraw(Image *img);
static void pngdraw(Image *img);
static void getfontsize(Slide *s, unsigned int *width, unsigned int *height); static void getfontsize(Slide *s, unsigned int *width, unsigned int *height);
static void cleanup(); static void cleanup(int slidesonly);
static void eprintf(const char *, ...); static void reload(const Arg *arg);
static void die(const char *, ...);
static void load(FILE *fp); static void load(FILE *fp);
static void advance(const Arg *arg); static void advance(const Arg *arg);
static void quit(const Arg *arg); static void quit(const Arg *arg);
static void resize(int width, int height); static void resize(int width, int height);
static void run(); static void run(void);
static void usage(); static void usage(void);
static void xdraw(); static void xdraw(void);
static void xhints(); static void xhints(void);
static void xinit(); static void xinit(void);
static void xloadfonts(); static void xloadfonts(void);
static void bpress(XEvent *); static void bpress(XEvent *);
static void cmessage(XEvent *); static void cmessage(XEvent *);
@ -111,12 +116,13 @@ static void configure(XEvent *);
#include "config.h" #include "config.h"
/* Globals */ /* Globals */
static const char *fname = NULL;
static Slide *slides = NULL; static Slide *slides = NULL;
static int idx = 0; static int idx = 0;
static int slidecount = 0; static int slidecount = 0;
static XWindow xw; static XWindow xw;
static Drw *d = NULL; static Drw *d = NULL;
static Scm *sc; static Clr *sc;
static Fnt *fonts[NUMFONTSCALES]; static Fnt *fonts[NUMFONTSCALES];
static int running = 1; static int running = 1;
@ -128,118 +134,128 @@ static void (*handler[LASTEvent])(XEvent *) = {
[KeyPress] = kpress, [KeyPress] = kpress,
}; };
Image *pngopen(char *filename) int
filter(int fd, const char *cmd)
{ {
FILE *f; int fds[2];
unsigned char buf[8];
Image *img;
if (!(f = fopen(filename, "rb"))) { if (pipe(fds) < 0)
eprintf("could not open file %s:", filename); die("sent: Unable to create pipe:");
return NULL;
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]);
if (fread(buf, 1, 8, f) != 8 || png_sig_cmp(buf, 1, 8)) return fds[0];
return NULL;
img = malloc(sizeof(Image));
memset(img, 0, sizeof(Image));
if (!(img->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
NULL, NULL))) {
free(img);
return NULL;
}
if (!(img->info_ptr = png_create_info_struct(img->png_ptr))
|| setjmp(png_jmpbuf(img->png_ptr))) {
pngfree(img);
return NULL;
}
img->f = f;
rewind(f);
png_init_io(img->png_ptr, f);
png_read_info(img->png_ptr, img->info_ptr);
img->bufwidth = png_get_image_width(img->png_ptr, img->info_ptr);
img->bufheight = png_get_image_height(img->png_ptr, img->info_ptr);
return img;
} }
void pngfree(Image *img) void
fffree(Image *img)
{ {
png_destroy_read_struct(&img->png_ptr, img->info_ptr ? &img->info_ptr : NULL, NULL);
free(img->buf); free(img->buf);
if (img->ximg) if (img->ximg)
XDestroyImage(img->ximg); XDestroyImage(img->ximg);
free(img); free(img);
} }
int pngread(Image *img) void
ffload(Slide *s)
{ {
unsigned int y; uint32_t y, x;
png_bytepp row_pointers; 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 (!img) if (s->img || !(filename = s->embed) || !s->embed[0])
return 0; return; /* already done */
if (img->state & LOADED) for (i = 0; i < LEN(filters); i++) {
return 2; if (regcomp(&regex, filters[i].regex,
REG_NOSUB | REG_EXTENDED | REG_ICASE)) {
fprintf(stderr, "sent: Invalid regex '%s'\n", filters[i].regex);
continue;
}
if (!regexec(&regex, filename, 0, NULL, 0)) {
bin = filters[i].bin;
regfree(&regex);
break;
}
regfree(&regex);
}
if (!bin)
die("sent: Unable to find matching filter for '%s'", filename);
if (img->buf) if ((fdin = open(filename, O_RDONLY)) < 0)
free(img->buf); die("sent: Unable to open '%s':", filename);
if (!(img->buf = malloc(3 * img->bufwidth * img->bufheight)))
return 0;
if (setjmp(png_jmpbuf(img->png_ptr))) { if ((fdout = filter(fdin, bin)) < 0)
png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL); die("sent: Unable to filter '%s':", filename);
return 0; 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]);
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);
int color_type = png_get_color_type(img->png_ptr, img->info_ptr); close(fdout);
int bit_depth = png_get_bit_depth(img->png_ptr, img->info_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_expand(img->png_ptr);
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand(img->png_ptr);
if (png_get_valid(img->png_ptr, img->info_ptr, PNG_INFO_tRNS))
png_set_expand(img->png_ptr);
if (bit_depth == 16)
png_set_strip_16(img->png_ptr);
if (color_type == PNG_COLOR_TYPE_GRAY
|| color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(img->png_ptr);
png_color_16 my_background = {.red = 0xff, .green = 0xff, .blue = 0xff};
png_color_16p image_background;
if (png_get_bKGD(img->png_ptr, img->info_ptr, &image_background))
png_set_background(img->png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);
else
png_set_background(img->png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 2, 1.0);
if (png_get_interlace_type(img->png_ptr, img->info_ptr) == PNG_INTERLACE_ADAM7)
img->numpasses = png_set_interlace_handling(img->png_ptr);
else
img->numpasses = 1;
png_read_update_info(img->png_ptr, img->info_ptr);
}
row_pointers = (png_bytepp)malloc(img->bufheight * sizeof(png_bytep));
for (y = 0; y < img->bufheight; y++)
row_pointers[y] = img->buf + y * img->bufwidth * 3;
png_read_image(img->png_ptr, row_pointers);
free(row_pointers);
png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL);
fclose(img->f);
img->state |= LOADED;
return 1;
} }
int pngprepare(Image *img) void
ffprepare(Image *img)
{ {
int depth = DefaultDepth(xw.dpy, xw.scr); int depth = DefaultDepth(xw.dpy, xw.scr);
int width = xw.uw; int width = xw.uw;
@ -250,38 +266,26 @@ int pngprepare(Image *img)
else else
height = img->bufheight * xw.uw / img->bufwidth; height = img->bufheight * xw.uw / img->bufwidth;
if (depth < 24) { if (depth < 24)
eprintf("display depths <24 not supported."); die("sent: Display color depths < 24 not supported");
return 0;
} if (img->ximg)
XDestroyImage(img->ximg);
if (!(img->ximg = XCreateImage(xw.dpy, CopyFromParent, depth, ZPixmap, 0, if (!(img->ximg = XCreateImage(xw.dpy, CopyFromParent, depth, ZPixmap, 0,
NULL, width, height, 32, 0))) { NULL, width, height, 32, 0)))
eprintf("could not create XImage"); die("sent: Unable to create XImage");
return 0;
}
if (!(img->ximg->data = malloc(img->ximg->bytes_per_line * height))) { img->ximg->data = ecalloc(height, img->ximg->bytes_per_line);
eprintf("could not alloc data section for XImage"); if (!XInitImage(img->ximg))
XDestroyImage(img->ximg); die("sent: Unable to initiate XImage");
img->ximg = NULL;
return 0;
}
if (!XInitImage(img->ximg)) { ffscale(img);
eprintf("could not init XImage");
free(img->ximg->data);
XDestroyImage(img->ximg);
img->ximg = NULL;
return 0;
}
pngscale(img);
img->state |= SCALED; img->state |= SCALED;
return 1;
} }
void pngscale(Image *img) void
ffscale(Image *img)
{ {
unsigned int x, y; unsigned int x, y;
unsigned int width = img->ximg->width; unsigned int width = img->ximg->width;
@ -306,104 +310,103 @@ void pngscale(Image *img)
} }
} }
void pngdraw(Image *img) void
ffdraw(Image *img)
{ {
int xoffset = (xw.w - img->ximg->width) / 2; int xoffset = (xw.w - img->ximg->width) / 2;
int yoffset = (xw.h - img->ximg->height) / 2; int yoffset = (xw.h - img->ximg->height) / 2;
XPutImage(xw.dpy, xw.win, d->gc, img->ximg, 0, 0, XPutImage(xw.dpy, xw.win, d->gc, img->ximg, 0, 0,
xoffset, yoffset, img->ximg->width, img->ximg->height); xoffset, yoffset, img->ximg->width, img->ximg->height);
XFlush(xw.dpy); XFlush(xw.dpy);
img->state |= DRAWN;
} }
void getfontsize(Slide *s, unsigned int *width, unsigned int *height) void
getfontsize(Slide *s, unsigned int *width, unsigned int *height)
{ {
size_t i, j; int i, j;
unsigned int curw, imax; unsigned int curw, newmax;
float lfac = linespacing * (s->linecount - 1) + 1; float lfac = linespacing * (s->linecount - 1) + 1;
/* fit height */ /* fit height */
for (j = NUMFONTSCALES - 1; j >= 0; j--) for (j = NUMFONTSCALES - 1; j >= 0; j--)
if (fonts[j]->h * lfac <= xw.uh) if (fonts[j]->h * lfac <= xw.uh)
break; break;
LIMIT(j, 0, NUMFONTSCALES - 1);
drw_setfontset(d, fonts[j]); drw_setfontset(d, fonts[j]);
/* fit width */ /* fit width */
*width = 0; *width = 0;
for (i = 0; i < s->linecount; i++) { for (i = 0; i < s->linecount; i++) {
curw = drw_fontset_getwidth(d, s->lines[i]); curw = drw_fontset_getwidth(d, s->lines[i]);
if (curw >= *width) newmax = (curw >= *width);
imax = i; while (j > 0 && curw > xw.uw) {
while (j >= 0 && curw > xw.uw) {
drw_setfontset(d, fonts[--j]); drw_setfontset(d, fonts[--j]);
curw = drw_fontset_getwidth(d, s->lines[i]); curw = drw_fontset_getwidth(d, s->lines[i]);
} }
if (imax == i) if (newmax)
*width = curw; *width = curw;
} }
*height = fonts[j]->h * lfac; *height = fonts[j]->h * lfac;
*width += fonts[j]->h;
} }
void cleanup() void
cleanup(int slidesonly)
{ {
unsigned int i, j; unsigned int i, j;
for (i = 0; i < NUMFONTSCALES; i++) if (!slidesonly) {
drw_fontset_free(fonts[i]); for (i = 0; i < NUMFONTSCALES; i++)
drw_scm_free(sc); drw_fontset_free(fonts[i]);
drw_free(d); free(sc);
drw_free(d);
XDestroyWindow(xw.dpy, xw.win);
XSync(xw.dpy, False);
XCloseDisplay(xw.dpy);
}
XDestroyWindow(xw.dpy, xw.win);
XSync(xw.dpy, False);
XCloseDisplay(xw.dpy);
if (slides) { if (slides) {
for (i = 0; i < slidecount; i++) { for (i = 0; i < slidecount; i++) {
for (j = 0; j < slides[i].linecount; j++) for (j = 0; j < slides[i].linecount; j++)
free(slides[i].lines[j]); free(slides[i].lines[j]);
free(slides[i].lines); free(slides[i].lines);
if (slides[i].img) if (slides[i].img)
pngfree(slides[i].img); fffree(slides[i].img);
}
if (!slidesonly) {
free(slides);
slides = NULL;
} }
free(slides);
slides = NULL;
} }
} }
void die(const char *fmt, ...) void
reload(const Arg *arg)
{ {
va_list ap; FILE *fp = NULL;
unsigned int i;
va_start(ap, fmt); if (!fname) {
vfprintf(stderr, fmt, ap); fprintf(stderr, "sent: Cannot reload from stdin. Use a file!\n");
va_end(ap); return;
if (fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
} }
exit(1);
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 eprintf(const char *fmt, ...) void
{ load(FILE *fp)
va_list ap;
va_start(ap, fmt);
vfprintf(stderr, fmt, ap);
va_end(ap);
if (fmt[0] != '\0' && fmt[strlen(fmt)-1] == ':') {
fputc(' ', stderr);
perror(NULL);
} else {
fputc('\n', stderr);
}
}
void load(FILE *fp)
{ {
static size_t size = 0; static size_t size = 0;
size_t blen, maxlines; size_t blen, maxlines;
@ -412,10 +415,6 @@ void load(FILE *fp)
/* read each line from fp and add it to the item list */ /* read each line from fp and add it to the item list */
while (1) { while (1) {
if ((slidecount+1) * sizeof(*slides) >= size)
if (!(slides = realloc(slides, (size += BUFSIZ))))
die("cannot realloc %u bytes:", size);
/* eat consecutive empty lines */ /* eat consecutive empty lines */
while ((p = fgets(buf, sizeof(buf), fp))) while ((p = fgets(buf, sizeof(buf), fp)))
if (strcmp(buf, "\n") != 0 && buf[0] != '#') if (strcmp(buf, "\n") != 0 && buf[0] != '#')
@ -423,10 +422,18 @@ void load(FILE *fp)
if (!p) if (!p)
break; break;
if ((slidecount+1) * sizeof(*slides) >= size)
if (!(slides = realloc(slides, (size += BUFSIZ))))
die("sent: Unable to reallocate %u bytes:", size);
/* read one slide */ /* read one slide */
maxlines = 0; maxlines = 0;
memset((s = &slides[slidecount]), 0, sizeof(Slide)); memset((s = &slides[slidecount]), 0, sizeof(Slide));
do { do {
/* if there's a leading null, we can't do blen-1 */
if (buf[0] == '\0')
continue;
if (buf[0] == '#') if (buf[0] == '#')
continue; continue;
@ -434,53 +441,54 @@ void load(FILE *fp)
if (s->linecount >= maxlines) { if (s->linecount >= maxlines) {
maxlines = 2 * s->linecount + 1; maxlines = 2 * s->linecount + 1;
if (!(s->lines = realloc(s->lines, maxlines * sizeof(s->lines[0])))) if (!(s->lines = realloc(s->lines, maxlines * sizeof(s->lines[0]))))
die("cannot realloc %u bytes:", maxlines * sizeof(s->lines[0])); die("sent: Unable to reallocate %u bytes:", maxlines * sizeof(s->lines[0]));
} }
blen = strlen(buf); blen = strlen(buf);
if (!(s->lines[s->linecount] = strdup(buf))) if (!(s->lines[s->linecount] = strdup(buf)))
die("cannot strdup:"); die("sent: Unable to strdup:");
if (s->lines[s->linecount][blen-1] == '\n') if (s->lines[s->linecount][blen-1] == '\n')
s->lines[s->linecount][blen-1] = '\0'; s->lines[s->linecount][blen-1] = '\0';
/* only make image slide if first line of a slide starts with @ */ /* mark as image slide if first line of a slide starts with @ */
if (s->linecount == 0 && s->lines[0][0] == '@') { if (s->linecount == 0 && s->lines[0][0] == '@')
memmove(s->lines[0], &s->lines[0][1], blen); s->embed = &s->lines[0][1];
s->img = pngopen(s->lines[0]);
}
if (s->lines[s->linecount][0] == '\\') if (s->lines[s->linecount][0] == '\\')
memmove(s->lines[s->linecount], &s->lines[s->linecount][1], blen); memmove(s->lines[s->linecount], &s->lines[s->linecount][1], blen);
s->linecount++; s->linecount++;
} while ((p = fgets(buf, sizeof(buf), fp)) && strcmp(buf, "\n") != 0); } while ((p = fgets(buf, sizeof(buf), fp)) && strcmp(buf, "\n") != 0);
slidecount++; slidecount++;
if (!p) if (!p)
break; break;
} }
if (!slidecount)
die("sent: No slides in file");
} }
void advance(const Arg *arg) void
advance(const Arg *arg)
{ {
int new_idx = idx + arg->i; int new_idx = idx + arg->i;
LIMIT(new_idx, 0, slidecount-1); LIMIT(new_idx, 0, slidecount-1);
if (new_idx != idx) { if (new_idx != idx) {
if (slides[idx].img) if (slides[idx].img)
slides[idx].img->state &= ~(DRAWN | SCALED); slides[idx].img->state &= ~SCALED;
idx = new_idx; idx = new_idx;
xdraw(); xdraw();
if (slidecount > idx + 1 && slides[idx + 1].img && !pngread(slides[idx + 1].img))
die("could not read image %s", slides[idx + 1].lines[0]);
if (0 < idx && slides[idx - 1].img && !pngread(slides[idx - 1].img))
die("could not read image %s", slides[idx - 1].lines[0]);
} }
} }
void quit(const Arg *arg) void
quit(const Arg *arg)
{ {
running = 0; running = 0;
} }
void resize(int width, int height) void
resize(int width, int height)
{ {
xw.w = width; xw.w = width;
xw.h = height; xw.h = height;
@ -489,7 +497,8 @@ void resize(int width, int height)
drw_resize(d, width, height); drw_resize(d, width, height);
} }
void run() void
run(void)
{ {
XEvent ev; XEvent ev;
@ -510,13 +519,8 @@ void run()
} }
} }
void usage() void
{ xdraw(void)
die("sent " VERSION " (c) 2014-2015 markus.teich@stusta.mhn.de\n" \
"usage: sent FILE1 [FILE2 ...]", argv0);
}
void xdraw()
{ {
unsigned int height, width, i; unsigned int height, width, i;
Image *im = slides[idx].img; Image *im = slides[idx].img;
@ -532,26 +536,26 @@ void xdraw()
(xw.h - height) / 2 + i * linespacing * d->fonts->h, (xw.h - height) / 2 + i * linespacing * d->fonts->h,
width, width,
d->fonts->h, d->fonts->h,
0,
slides[idx].lines[i], slides[idx].lines[i],
0); 0);
drw_map(d, xw.win, 0, 0, xw.w, xw.h); drw_map(d, xw.win, 0, 0, xw.w, xw.h);
} else if (!(im->state & LOADED) && !pngread(im)) { } else {
eprintf("could not read image %s", slides[idx].lines[0]); if (!(im->state & SCALED))
} else if (!(im->state & SCALED) && !pngprepare(im)) { ffprepare(im);
eprintf("could not prepare image %s for drawing", slides[idx].lines[0]); ffdraw(im);
} else if (!(im->state & DRAWN)) {
pngdraw(im);
} }
} }
void xhints() void
xhints(void)
{ {
XClassHint class = {.res_name = "sent", .res_class = "presenter"}; XClassHint class = {.res_name = "sent", .res_class = "presenter"};
XWMHints wm = {.flags = InputHint, .input = True}; XWMHints wm = {.flags = InputHint, .input = True};
XSizeHints *sizeh = NULL; XSizeHints *sizeh = NULL;
if (!(sizeh = XAllocSizeHints())) if (!(sizeh = XAllocSizeHints()))
die("sent: Could not alloc size hints"); die("sent: Unable to allocate size hints");
sizeh->flags = PSize; sizeh->flags = PSize;
sizeh->height = xw.h; sizeh->height = xw.h;
@ -561,35 +565,40 @@ void xhints()
XFree(sizeh); XFree(sizeh);
} }
void xinit() void
xinit(void)
{ {
XTextProperty prop; XTextProperty prop;
unsigned int i;
if (!(xw.dpy = XOpenDisplay(NULL))) if (!(xw.dpy = XOpenDisplay(NULL)))
die("Can't open display."); die("sent: Unable to open display");
xw.scr = XDefaultScreen(xw.dpy); xw.scr = XDefaultScreen(xw.dpy);
xw.vis = XDefaultVisual(xw.dpy, xw.scr); xw.vis = XDefaultVisual(xw.dpy, xw.scr);
resize(DisplayWidth(xw.dpy, xw.scr), DisplayHeight(xw.dpy, xw.scr)); resize(DisplayWidth(xw.dpy, xw.scr), DisplayHeight(xw.dpy, xw.scr));
xw.attrs.background_pixel = WhitePixel(xw.dpy, xw.scr);
xw.attrs.bit_gravity = CenterGravity; xw.attrs.bit_gravity = CenterGravity;
xw.attrs.event_mask = KeyPressMask | ExposureMask | StructureNotifyMask xw.attrs.event_mask = KeyPressMask | ExposureMask | StructureNotifyMask |
| ButtonMotionMask | ButtonPressMask; ButtonMotionMask | ButtonPressMask;
xw.win = XCreateWindow(xw.dpy, XRootWindow(xw.dpy, xw.scr), 0, 0, 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, xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr),
CWBackPixel | CWBitGravity | CWEventMask, &xw.attrs); InputOutput, xw.vis, CWBitGravity | CWEventMask,
&xw.attrs);
xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False);
xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False);
XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1);
if (!(d = drw_create(xw.dpy, xw.scr, xw.win, xw.w, xw.h))) if (!(d = drw_create(xw.dpy, xw.scr, xw.win, xw.w, xw.h)))
die("Can't create drawing context."); die("sent: Unable to create drawing context");
sc = drw_scm_create(d, fgcol, bgcol); sc = drw_scm_create(d, colors, 2);
drw_setscheme(d, sc); drw_setscheme(d, sc);
XSetWindowBackground(xw.dpy, xw.win, sc[ColBg].pixel);
xloadfonts(); xloadfonts();
for (i = 0; i < slidecount; i++)
ffload(&slides[i]);
XStringListToTextProperty(&argv0, 1, &prop); XStringListToTextProperty(&argv0, 1, &prop);
XSetWMName(xw.dpy, xw.win, &prop); XSetWMName(xw.dpy, xw.win, &prop);
@ -600,30 +609,31 @@ void xinit()
XSync(xw.dpy, False); XSync(xw.dpy, False);
} }
void xloadfonts() void
xloadfonts(void)
{ {
int i, j; int i, j;
char *fstrs[LEN(fontfallbacks)]; char *fstrs[LEN(fontfallbacks)];
for (j = 0; j < LEN(fontfallbacks); j++) { for (j = 0; j < LEN(fontfallbacks); j++) {
if (!(fstrs[j] = malloc(MAXFONTSTRLEN))) fstrs[j] = ecalloc(1, MAXFONTSTRLEN);
die("could not malloc fstrs");
} }
for (i = 0; i < NUMFONTSCALES; i++) { for (i = 0; i < NUMFONTSCALES; i++) {
for (j = 0; j < LEN(fontfallbacks); j++) { for (j = 0; j < LEN(fontfallbacks); j++) {
if (MAXFONTSTRLEN < snprintf(fstrs[j], MAXFONTSTRLEN, "%s:size=%d", fontfallbacks[j], FONTSZ(i))) if (MAXFONTSTRLEN < snprintf(fstrs[j], MAXFONTSTRLEN, "%s:size=%d", fontfallbacks[j], FONTSZ(i)))
die("font string too long"); die("sent: Font string too long");
} }
fonts[i] = drw_fontset_create(d, (const char**)fstrs, LEN(fstrs)); 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++) for (j = 0; j < LEN(fontfallbacks); j++)
if (fstrs[j]) free(fstrs[j]);
free(fstrs[j]);
} }
void bpress(XEvent *e) void
bpress(XEvent *e)
{ {
unsigned int i; unsigned int i;
@ -632,19 +642,22 @@ void bpress(XEvent *e)
mshortcuts[i].func(&(mshortcuts[i].arg)); mshortcuts[i].func(&(mshortcuts[i].arg));
} }
void cmessage(XEvent *e) void
cmessage(XEvent *e)
{ {
if (e->xclient.data.l[0] == xw.wmdeletewin) if (e->xclient.data.l[0] == xw.wmdeletewin)
running = 0; running = 0;
} }
void expose(XEvent *e) void
expose(XEvent *e)
{ {
if (0 == e->xexpose.count) if (0 == e->xexpose.count)
xdraw(); xdraw();
} }
void kpress(XEvent *e) void
kpress(XEvent *e)
{ {
unsigned int i; unsigned int i;
KeySym sym; KeySym sym;
@ -655,40 +668,44 @@ void kpress(XEvent *e)
shortcuts[i].func(&(shortcuts[i].arg)); shortcuts[i].func(&(shortcuts[i].arg));
} }
void configure(XEvent *e) void
configure(XEvent *e)
{ {
resize(e->xconfigure.width, e->xconfigure.height); resize(e->xconfigure.width, e->xconfigure.height);
if (slides[idx].img) if (slides[idx].img)
slides[idx].img->state &= ~(DRAWN | SCALED); slides[idx].img->state &= ~SCALED;
xdraw(); xdraw();
} }
int main(int argc, char *argv[]) void
usage(void)
{
die("usage: %s [file]", argv0);
}
int
main(int argc, char *argv[])
{ {
int i;
FILE *fp = NULL; FILE *fp = NULL;
ARGBEGIN { ARGBEGIN {
case 'v': case 'v':
fprintf(stderr, "sent-"VERSION"\n");
return 0;
default: default:
usage(); usage();
} ARGEND; } ARGEND
for (i = 0; i < argc; i++) { if (!argv[0] || !strcmp(argv[0], "-"))
if ((fp = strcmp(argv[i], "-") ? fopen(argv[i], "r") : stdin)) { fp = stdin;
load(fp); else if (!(fp = fopen(fname = argv[0], "r")))
fclose(fp); die("sent: Unable to open '%s' for reading:", fname);
} else { load(fp);
eprintf("could not open %s for reading:", argv[i]); fclose(fp);
}
}
if (!slides || !slides[0].lines)
usage();
xinit(); xinit();
run(); run();
cleanup(); cleanup(0);
return 0; return 0;
} }

BIN
transparent_test.ff Normal file

Binary file not shown.

32
util.c
View File

@ -2,16 +2,34 @@
#include <stdarg.h> #include <stdarg.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h>
#include "util.h" #include "util.h"
void void *
die(const char *errstr, ...) { ecalloc(size_t nmemb, size_t size)
va_list ap; {
void *p;
va_start(ap, errstr); if (!(p = calloc(nmemb, size)))
vfprintf(stderr, errstr, ap); die("calloc:");
va_end(ap); return p;
exit(1);
} }
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);
}

3
util.h
View File

@ -4,4 +4,5 @@
#define MIN(A, B) ((A) < (B) ? (A) : (B)) #define MIN(A, B) ((A) < (B) ? (A) : (B))
#define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B)) #define BETWEEN(X, A, B) ((A) <= (X) && (X) <= (B))
void die(const char *errstr, ...); void die(const char *fmt, ...);
void *ecalloc(size_t nmemb, size_t size);