@ -0,0 +1,60 @@ | |||
# Prerequisites | |||
*.d | |||
# Object files | |||
*.o | |||
*.ko | |||
*.obj | |||
*.elf | |||
# Linker output | |||
*.ilk | |||
*.map | |||
*.exp | |||
# Precompiled Headers | |||
*.gch | |||
*.pch | |||
# Libraries | |||
*.lib | |||
*.a | |||
*.la | |||
*.lo | |||
# Shared objects (inc. Windows DLLs) | |||
*.dll | |||
*.so | |||
*.so.* | |||
*.dylib | |||
# Executables | |||
*.exe | |||
*.out | |||
*.app | |||
*.i*86 | |||
*.x86_64 | |||
*.hex | |||
# Debug files | |||
*.dSYM/ | |||
*.su | |||
*.idb | |||
*.pdb | |||
# Kernel Module Compile Results | |||
*.mod* | |||
*.cmd | |||
.tmp_versions/ | |||
modules.order | |||
Module.symvers | |||
Mkfile.old | |||
dkms.conf | |||
dwm | |||
dwm-msg | |||
*.orig | |||
*.rej | |||
.ccls-cache |
@ -0,0 +1,29 @@ | |||
ISC-License | |||
(c) 2014-2017 Markus Teich <markus.teich@stusta.mhn.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. | |||
(c) 2016,2017 Laslo Hunhold <dev@frign.de> | |||
(c) 2016 ssd <ssd@mailless.org> | |||
(c) 2016 Hiltjo Posthuma <hiltjo@codemadness.org> | |||
(c) 2015 David Phillips <dbphillipsnz@gmail.com> | |||
(c) 2015 Grant Mathews <grant.m.mathews@gmail.com> | |||
(c) 2015 Dimitris Papastamos <sin@2f30.org> | |||
(c) 2015 Alexis <surryhill@gmail.com> | |||
(c) 2015 Quentin Rameau <quinq@fifth.space> | |||
(c) 2015 Ivan Tham <pickfire@riseup.net> | |||
(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> |
@ -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 |
@ -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/ |
@ -0,0 +1,49 @@ | |||
/* | |||
* ISC-License | |||
* | |||
* 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 | |||
#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 |
@ -0,0 +1,56 @@ | |||
/* See LICENSE file for copyright and license details. */ | |||
static char *fontfallbacks[] = { | |||
"dejavu sans", | |||
"roboto", | |||
"ubuntu", | |||
}; | |||
#define NUMFONTSCALES 42 | |||
#define FONTSZ(x) ((int)(10.0 * powf(1.1288, (x)))) /* x in [0, NUMFONTSCALES-1] */ | |||
static const char *colors[] = { | |||
"#000000", /* foreground color */ | |||
"#FFFFFF", /* background color */ | |||
}; | |||
static const float linespacing = 1.4; | |||
/* how much screen estate is to be used at max for the content */ | |||
static const float usablewidth = 0.75; | |||
static const float usableheight = 0.75; | |||
static Mousekey mshortcuts[] = { | |||
/* button function argument */ | |||
{ Button1, advance, {.i = +1} }, | |||
{ Button3, advance, {.i = -1} }, | |||
{ Button4, advance, {.i = -1} }, | |||
{ Button5, advance, {.i = +1} }, | |||
}; | |||
static Shortcut shortcuts[] = { | |||
/* keysym function argument */ | |||
{ XK_Escape, quit, {0} }, | |||
{ XK_q, quit, {0} }, | |||
{ XK_Right, advance, {.i = +1} }, | |||
{ XK_Left, advance, {.i = -1} }, | |||
{ XK_Return, advance, {.i = +1} }, | |||
{ XK_space, advance, {.i = +1} }, | |||
{ XK_BackSpace, advance, {.i = -1} }, | |||
{ XK_l, advance, {.i = +1} }, | |||
{ XK_h, advance, {.i = -1} }, | |||
{ XK_j, advance, {.i = +1} }, | |||
{ XK_k, advance, {.i = -1} }, | |||
{ XK_Down, advance, {.i = +1} }, | |||
{ XK_Up, advance, {.i = -1} }, | |||
{ XK_Next, advance, {.i = +1} }, | |||
{ XK_Prior, advance, {.i = -1} }, | |||
{ XK_n, advance, {.i = +1} }, | |||
{ XK_p, advance, {.i = -1} }, | |||
{ XK_r, reload, {0} }, | |||
}; | |||
static Filter filters[] = { | |||
{ "\\.ff$", "cat" }, | |||
{ "\\.ff.bz2$", "bunzip2" }, | |||
{ "\\.[a-z0-9]+$", "2ff" }, | |||
}; |
@ -0,0 +1,56 @@ | |||
/* 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[] = { | |||
"#FFFFFF", /* foreground color */ | |||
"#000000", /* background color */ | |||
}; | |||
static const float linespacing = 1.4; | |||
/* how much screen estate is to be used at max for the content */ | |||
static const float usablewidth = 0.75; | |||
static const float usableheight = 0.75; | |||
static Mousekey mshortcuts[] = { | |||
/* button function argument */ | |||
{ Button1, advance, {.i = +1} }, | |||
{ Button3, advance, {.i = -1} }, | |||
{ Button4, advance, {.i = -1} }, | |||
{ Button5, advance, {.i = +1} }, | |||
}; | |||
static Shortcut shortcuts[] = { | |||
/* keysym function argument */ | |||
{ XK_Escape, quit, {0} }, | |||
{ XK_q, quit, {0} }, | |||
{ XK_Right, advance, {.i = +1} }, | |||
{ XK_Left, advance, {.i = -1} }, | |||
{ XK_Return, advance, {.i = +1} }, | |||
{ XK_space, advance, {.i = +1} }, | |||
{ XK_BackSpace, advance, {.i = -1} }, | |||
{ XK_l, advance, {.i = +1} }, | |||
{ XK_h, advance, {.i = -1} }, | |||
{ XK_j, advance, {.i = +1} }, | |||
{ XK_k, advance, {.i = -1} }, | |||
{ XK_Down, advance, {.i = +1} }, | |||
{ XK_Up, advance, {.i = -1} }, | |||
{ XK_Next, advance, {.i = +1} }, | |||
{ XK_Prior, advance, {.i = -1} }, | |||
{ XK_n, advance, {.i = +1} }, | |||
{ XK_p, advance, {.i = -1} }, | |||
{ XK_r, reload, {0} }, | |||
}; | |||
static Filter filters[] = { | |||
{ "\\.ff$", "cat" }, | |||
{ "\\.ff.bz2$", "bunzip2" }, | |||
{ "\\.[a-z0-9]+$", "2ff" }, | |||
}; |
@ -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 |
@ -0,0 +1,421 @@ | |||
/* See LICENSE file for copyright and license details. */ | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <X11/Xlib.h> | |||
#include <X11/Xft/Xft.h> | |||
#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); | |||
} |
@ -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); |
@ -0,0 +1,68 @@ | |||
.Dd 2016-08-12 | |||
.Dt SENT 1 | |||
.Sh NAME | |||
.Nm sent | |||
.Nd simple plaintext presentation tool | |||
.Sh SYNOPSIS | |||
.Nm | |||
.Op Fl v | |||
.Op Ar file | |||
.Sh DESCRIPTION | |||
.Nm | |||
is a simple plain text presentation tool for X. sent does not need LaTeX, | |||
LibreOffice or any other fancy file format. Instead, sent reads plain text | |||
describing the slides. sent can also draw images. | |||
.Pp | |||
Every paragraph represents a slide in the presentation. Especially for | |||
presentations using the Takahashi method this is very nice and allows | |||
you to write the presentation for a quick lightning talk within a | |||
few minutes. | |||
.Sh OPTIONS | |||
.Bl -tag -width Ds | |||
.It Fl v | |||
Print version information to stdout and exit. | |||
.El | |||
.Sh USAGE | |||
.Bl -tag -width Ds | |||
.It Em Mouse commands | |||
.Bl -tag -width Ds | |||
.It Sy Button1 | Button5 | |||
Go to next slide, if existent. | |||
.It Sy Button3 | Button4 | |||
Go to previous slide, if existent. | |||
.El | |||
.It Em Keyboard commands | |||
.Bl -tag -width Ds | |||
.It Sy Escape | q | |||
Quit. | |||
.It Sy r | |||
Reload the slides. Only works on file input. | |||
.It Sy Right | Return | Space | l | j | Down | Next | n | |||
Go to next slide, if existent. | |||
.It Sy Left | Backspace | h | k | Up | Prior | p | |||
Go to previous slide, if existent. | |||
.El | |||
.El | |||
.Sh FORMAT | |||
The presentation file is made up of at least one paragraph, with an | |||
empty line separating two slides. | |||
Each input line is interpreted literally, except from control characters | |||
at the beginning of lines described as follows: | |||
.Bl -tag -width Ds | |||
.It Sy @ | |||
Create individual slide containing the image pointed to by the filename | |||
following the | |||
.Sy @ . | |||
.It Sy # | |||
Ignore this input line. | |||
.It Sy \e | |||
Create input line using the characters following the | |||
.Sy \e | |||
without interpreting them. | |||
.El | |||
.Sh CUSTOMIZATION | |||
.Nm | |||
can be customized by creating a custom config.h and (re)compiling the | |||
source code. This keeps it fast, secure and simple. | |||
.Sh SEE ALSO | |||
.Xr 2ff 1 |
@ -0,0 +1,710 @@ | |||
/* See LICENSE file for copyright and license details. */ | |||
#include <sys/types.h> | |||
#include <arpa/inet.h> | |||
#include <errno.h> | |||
#include <fcntl.h> | |||
#include <math.h> | |||
#include <regex.h> | |||
#include <stdarg.h> | |||
#include <stdio.h> | |||
#include <stdint.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#include <unistd.h> | |||
#include <X11/keysym.h> | |||
#include <X11/XKBlib.h> | |||
#include <X11/Xatom.h> | |||
#include <X11/Xlib.h> | |||
#include <X11/Xutil.h> | |||
#include <X11/Xft/Xft.h> | |||
#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 there's a leading null, we can't do blen-1 */ | |||
if (buf[0] == '\0') | |||
continue; | |||
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; | |||
} | |||
if (!slidecount) | |||
die("sent: No slides in file"); | |||
} | |||
void | |||
advance(const Arg *arg) | |||
{ | |||
int new_idx = idx + arg->i; | |||
LIMIT(new_idx, 0, slidecount-1); | |||
if (new_idx != idx) { | |||
if (slides[idx].img) | |||
slides[idx].img->state &= ~SCALED; | |||
idx = new_idx; | |||
xdraw(); | |||
} | |||
} | |||
void | |||
quit(const Arg *arg) | |||
{ | |||
running = 0; | |||
} | |||
void | |||
resize(int width, int height) | |||
{ | |||
xw.w = width; | |||
xw.h = height; | |||
xw.uw = usablewidth * width; | |||
xw.uh = usableheight * height; | |||
drw_resize(d, width, height); | |||
} | |||
void | |||
run() | |||
{ | |||
XEvent ev; | |||
/* Waiting for window mapping */ | |||
while (1) { | |||
XNextEvent(xw.dpy, &ev); | |||
if (ev.type == ConfigureNotify) { | |||
resize(ev.xconfigure.width, ev.xconfigure.height); | |||
} else if (ev.type == MapNotify) { | |||
break; | |||
} | |||
} | |||
while (running) { | |||
XNextEvent(xw.dpy, &ev); | |||
if (handler[ev.type]) | |||
(handler[ev.type])(&ev); | |||
} | |||
} | |||
void | |||
xdraw() | |||
{ | |||
unsigned int height, width, i; | |||
Image *im = slides[idx].img; | |||
getfontsize(&slides[idx], &width, &height); | |||
XClearWindow(xw.dpy, xw.win); | |||
if (!im) { | |||
drw_rect(d, 0, 0, xw.w, xw.h, 1, 1); | |||
for (i = 0; i < slides[idx].linecount; i++) | |||
drw_text(d, | |||
(xw.w - width) / 2, | |||
(xw.h - height) / 2 + i * linespacing * d->fonts->h, | |||
width, | |||
d->fonts->h, | |||
0, | |||
slides[idx].lines[i], | |||
0); | |||
drw_map(d, xw.win, 0, 0, xw.w, xw.h); | |||
} else { | |||
if (!(im->state & SCALED)) | |||
ffprepare(im); | |||
ffdraw(im); | |||
} | |||
} | |||
void | |||
xhints() | |||
{ | |||
XClassHint class = {.res_name = "sent", .res_class = "presenter"}; | |||
XWMHints wm = {.flags = InputHint, .input = True}; | |||
XSizeHints *sizeh = NULL; | |||
if (!(sizeh = XAllocSizeHints())) | |||
die("sent: Unable to allocate size hints"); | |||
sizeh->flags = PSize; | |||
sizeh->height = xw.h; | |||
sizeh->width = xw.w; | |||
XSetWMProperties(xw.dpy, xw.win, NULL, NULL, NULL, 0, sizeh, &wm, &class); | |||
XFree(sizeh); | |||
} | |||
void | |||
xinit() | |||
{ | |||
XTextProperty prop; | |||
unsigned int i; | |||
if (!(xw.dpy = XOpenDisplay(NULL))) | |||
die("sent: Unable to open display"); | |||
xw.scr = XDefaultScreen(xw.dpy); | |||
xw.vis = XDefaultVisual(xw.dpy, xw.scr); | |||
resize(DisplayWidth(xw.dpy, xw.scr), DisplayHeight(xw.dpy, xw.scr)); | |||
xw.attrs.bit_gravity = CenterGravity; | |||
xw.attrs.event_mask = KeyPressMask | ExposureMask | StructureNotifyMask | | |||
ButtonMotionMask | ButtonPressMask; | |||
xw.win = XCreateWindow(xw.dpy, XRootWindow(xw.dpy, xw.scr), 0, 0, | |||
xw.w, xw.h, 0, XDefaultDepth(xw.dpy, xw.scr), | |||
InputOutput, xw.vis, CWBitGravity | CWEventMask, | |||
&xw.attrs); | |||
xw.wmdeletewin = XInternAtom(xw.dpy, "WM_DELETE_WINDOW", False); | |||
xw.netwmname = XInternAtom(xw.dpy, "_NET_WM_NAME", False); | |||
XSetWMProtocols(xw.dpy, xw.win, &xw.wmdeletewin, 1); | |||
if (!(d = drw_create(xw.dpy, xw.scr, xw.win, xw.w, xw.h))) | |||
die("sent: Unable to create drawing context"); | |||
sc = drw_scm_create(d, colors, 2); | |||
drw_setscheme(d, sc); | |||
XSetWindowBackground(xw.dpy, xw.win, sc[ColBg].pixel); | |||
xloadfonts(); | |||
for (i = 0; i < slidecount; i++) | |||
ffload(&slides[i]); | |||
XStringListToTextProperty(&argv0, 1, &prop); | |||
XSetWMName(xw.dpy, xw.win, &prop); | |||
XSetTextProperty(xw.dpy, xw.win, &prop, xw.netwmname); | |||
XFree(prop.value); | |||
XMapWindow(xw.dpy, xw.win); | |||
xhints(); | |||
XSync(xw.dpy, False); | |||
} | |||
void | |||
xloadfonts() | |||
{ | |||
int i, j; | |||
char *fstrs[LEN(fontfallbacks)]; | |||
for (j = 0; j < LEN(fontfallbacks); j++) { | |||
fstrs[j] = ecalloc(1, MAXFONTSTRLEN); | |||
} | |||
for (i = 0; i < NUMFONTSCALES; i++) { | |||
for (j = 0; j < LEN(fontfallbacks); j++) { | |||
if (MAXFONTSTRLEN < snprintf(fstrs[j], MAXFONTSTRLEN, "%s:size=%d", fontfallbacks[j], FONTSZ(i))) | |||
die("sent: Font string too long"); | |||
} | |||
if (!(fonts[i] = drw_fontset_create(d, (const char**)fstrs, LEN(fstrs)))) | |||
die("sent: Unable to load any font for size %d", FONTSZ(i)); | |||
} | |||
for (j = 0; j < LEN(fontfallbacks); j++) | |||
if (fstrs[j]) | |||
free(fstrs[j]); | |||
} | |||
void | |||
bpress(XEvent *e) | |||
{ | |||
unsigned int i; | |||
for (i = 0; i < LEN(mshortcuts); i++) | |||
if (e->xbutton.button == mshortcuts[i].b && mshortcuts[i].func) | |||
mshortcuts[i].func(&(mshortcuts[i].arg)); | |||
} | |||
void | |||
cmessage(XEvent *e) | |||
{ | |||
if (e->xclient.data.l[0] == xw.wmdeletewin) | |||
running = 0; | |||
} | |||
void | |||
expose(XEvent *e) | |||
{ | |||
if (0 == e->xexpose.count) | |||
xdraw(); | |||
} | |||
void | |||
kpress(XEvent *e) | |||
{ | |||
unsigned int i; | |||
KeySym sym; | |||
sym = XkbKeycodeToKeysym(xw.dpy, (KeyCode)e->xkey.keycode, 0, 0); | |||
for (i = 0; i < LEN(shortcuts); i++) | |||
if (sym == shortcuts[i].keysym && shortcuts[i].func) | |||
shortcuts[i].func(&(shortcuts[i].arg)); | |||
} | |||
void | |||
configure(XEvent *e) | |||
{ | |||
resize(e->xconfigure.width, e->xconfigure.height); | |||
if (slides[idx].img) | |||
slides[idx].img->state &= ~SCALED; | |||
xdraw(); | |||
} | |||
void | |||
usage() | |||
{ | |||
die("usage: %s [file]", argv0); | |||
} | |||
int | |||
main(int argc, char *argv[]) | |||
{ | |||
FILE *fp = NULL; | |||
ARGBEGIN { | |||
case 'v': | |||
fprintf(stderr, "sent-"VERSION"\n"); | |||
return 0; | |||
default: | |||
usage(); | |||
} ARGEND | |||
if (!argv[0] || !strcmp(argv[0], "-")) | |||
fp = stdin; | |||
else if (!(fp = fopen(fname = argv[0], "r"))) | |||
die("sent: Unable to open '%s' for reading:", fname); | |||
load(fp); | |||
fclose(fp); | |||
xinit(); | |||
run(); | |||
cleanup(0); | |||
return 0; | |||
} |
@ -0,0 +1,35 @@ | |||
/* See LICENSE file for copyright and license details. */ | |||
#include <stdarg.h> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
#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); | |||
} |
@ -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); |
@ -1,138 +0,0 @@ | |||
diff --git a/config.def.h b/config.def.h | |||
index a221c86..9840736 100644 | |||
--- a/config.def.h | |||
+++ b/config.def.h | |||
@@ -32,6 +32,16 @@ static Bool hidebackground = FALSE; | |||
} \ | |||
} | |||
+#define SELNAV { \ | |||
+ .v = (char *[]){ "/bin/sh", "-c", \ | |||
+ "prop=\"`xprop -id $0 _SURF_HIST" \ | |||
+ " | sed -e 's/^.[^\"]*\"//' -e 's/\"$//' -e 's/\\\\\\n/\\n/g'" \ | |||
+ " | dmenu -i -l 10`\"" \ | |||
+ " && xprop -id $0 -f _SURF_NAV 8s -set _SURF_NAV \"$prop\"", \ | |||
+ winid, NULL \ | |||
+ } \ | |||
+} | |||
+ | |||
/* DOWNLOAD(URI, referer) */ | |||
#define DOWNLOAD(d, r) { \ | |||
.v = (char *[]){ "/bin/sh", "-c", \ | |||
@@ -67,6 +77,7 @@ static Key keys[] = { | |||
{ MODKEY, GDK_l, navigate, { .i = +1 } }, | |||
{ MODKEY, GDK_h, navigate, { .i = -1 } }, | |||
+ { MODKEY|GDK_SHIFT_MASK,GDK_h, selhist, SELNAV }, | |||
{ MODKEY, GDK_j, scroll_v, { .i = +1 } }, | |||
{ MODKEY, GDK_k, scroll_v, { .i = -1 } }, | |||
diff --git a/surf.c b/surf.c | |||
index cebd469..8b6d751 100644 | |||
--- a/surf.c | |||
+++ b/surf.c | |||
@@ -32,7 +32,7 @@ char *argv0; | |||
#define COOKIEJAR_TYPE (cookiejar_get_type ()) | |||
#define COOKIEJAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), COOKIEJAR_TYPE, CookieJar)) | |||
-enum { AtomFind, AtomGo, AtomUri, AtomLast }; | |||
+enum { AtomFind, AtomGo, AtomUri, AtomHist, AtomNav, AtomLast }; | |||
typedef union Arg Arg; | |||
union Arg { | |||
@@ -137,6 +137,8 @@ static void loadstatuschange(WebKitWebView *view, GParamSpec *pspec, | |||
Client *c); | |||
static void loaduri(Client *c, const Arg *arg); | |||
static void navigate(Client *c, const Arg *arg); | |||
+static void selhist(Client *c, const Arg *arg); | |||
+static void navhist(Client *c, const Arg *arg); | |||
static Client *newclient(void); | |||
static void newwindow(Client *c, const Arg *arg, gboolean noembed); | |||
static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d); | |||
@@ -649,6 +651,59 @@ navigate(Client *c, const Arg *arg) { | |||
webkit_web_view_go_back_or_forward(c->view, steps); | |||
} | |||
+static void | |||
+selhist(Client *c, const Arg *arg) { | |||
+ WebKitWebBackForwardList *lst; | |||
+ WebKitWebHistoryItem *cur; | |||
+ gint i; | |||
+ gchar *out; | |||
+ gchar *tmp; | |||
+ gchar *line; | |||
+ | |||
+ out = g_strdup(""); | |||
+ | |||
+ if(!(lst = webkit_web_view_get_back_forward_list(c->view))) | |||
+ return; | |||
+ | |||
+ for(i = webkit_web_back_forward_list_get_back_length(lst); i > 0; i--) { | |||
+ if(!(cur = webkit_web_back_forward_list_get_nth_item(lst, -i))) | |||
+ break; | |||
+ line = g_strdup_printf("%d: %s\n", -i, | |||
+ webkit_web_history_item_get_original_uri(cur)); | |||
+ tmp = g_strconcat(out, line, NULL); | |||
+ g_free(out); | |||
+ out = tmp; | |||
+ } | |||
+ | |||
+ if((cur = webkit_web_back_forward_list_get_nth_item(lst, 0))) { | |||
+ line = g_strdup_printf("%d: %s", 0, | |||
+ webkit_web_history_item_get_original_uri(cur)); | |||
+ tmp = g_strconcat(out, line, NULL); | |||
+ g_free(out); | |||
+ out = tmp; | |||
+ } | |||
+ | |||
+ for(i = 1; i <= webkit_web_back_forward_list_get_forward_length(lst); i++) { | |||
+ if(!(cur = webkit_web_back_forward_list_get_nth_item(lst, i))) | |||
+ break; | |||
+ line = g_strdup_printf("\n%d: %s", i, | |||
+ webkit_web_history_item_get_original_uri(cur)); | |||
+ tmp = g_strconcat(out, line, NULL); | |||
+ g_free(out); | |||
+ out = tmp; | |||
+ } | |||
+ | |||
+ setatom(c, AtomHist, out); | |||
+ g_free(out); | |||
+ spawn(c, arg); | |||
+} | |||
+ | |||
+static void | |||
+navhist(Client *c, const Arg *arg) { | |||
+ Arg a = { .i = atoi(arg->v) }; | |||
+ navigate(c, &a); | |||
+} | |||
+ | |||
static Client * | |||
newclient(void) { | |||
Client *c; | |||
@@ -805,6 +860,7 @@ newclient(void) { | |||
setatom(c, AtomFind, ""); | |||
setatom(c, AtomUri, "about:blank"); | |||
+ setatom(c, AtomHist, ""); | |||
if(hidebackground) | |||
webkit_web_view_set_transparent(c->view, TRUE); | |||
@@ -923,6 +979,9 @@ processx(GdkXEvent *e, GdkEvent *event, gpointer d) { | |||
arg.v = getatom(c, AtomGo); | |||
loaduri(c, &arg); | |||
return GDK_FILTER_REMOVE; | |||
+ } else if(ev->atom == atoms[AtomNav]) { | |||
+ arg.v = getatom(c, AtomNav); | |||
+ navhist(c, &arg); | |||
} | |||
} | |||
} | |||
@@ -1004,6 +1063,8 @@ setup(void) { | |||
atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False); | |||
atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False); | |||
atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False); | |||
+ atoms[AtomHist] = XInternAtom(dpy, "_SURF_HIST", False); | |||
+ atoms[AtomNav] = XInternAtom(dpy, "_SURF_NAV", False); | |||
/* dirs and files */ | |||
cookiefile = buildpath(cookiefile); |
@ -1,59 +0,0 @@ | |||
diff --git a/config.def.h b/config.def.h | |||
index 93a3d49..05d81de 100644 | |||
--- a/config.def.h | |||
+++ b/config.def.h | |||
@@ -65,6 +65,18 @@ static Bool allowgeolocation = TRUE; | |||
} \ | |||
} | |||
+#define ONLOAD(u) { \ | |||
+ .v = (char *[]){"/bin/sh", "-c", \ | |||
+ "~/.surf/omnibar addhist \"$0\"", u, NULL \ | |||
+ } \ | |||
+} | |||
+ | |||
+#define GOTO { \ | |||
+ .v = (char *[]){"/bin/sh", "-c", \ | |||
+ "~/.surf/omnibar goto \"$0\" \"$1\"", winid, "_SURF_GO", NULL \ | |||
+ } \ | |||
+} | |||
+ | |||
/* styles */ | |||
/* | |||
* The iteration will stop at the first match, beginning at the beginning of | |||
@@ -112,7 +124,7 @@ static Key keys[] = { | |||
{ MODKEY, GDK_o, source, { 0 } }, | |||
{ MODKEY|GDK_SHIFT_MASK,GDK_o, inspector, { 0 } }, | |||
- { MODKEY, GDK_g, spawn, SETPROP("_SURF_URI", "_SURF_GO") }, | |||
+ { MODKEY, GDK_g, spawn, GOTO }, | |||
{ MODKEY, GDK_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND") }, | |||
{ MODKEY, GDK_slash, spawn, SETPROP("_SURF_FIND", "_SURF_FIND") }, | |||
diff --git a/surf.c b/surf.c | |||
index f2170a4..c8fdab3 100644 | |||
--- a/surf.c | |||
+++ b/surf.c | |||
@@ -789,11 +789,11 @@ loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c) | |||
WebKitWebDataSource *src; | |||
WebKitNetworkRequest *request; | |||
SoupMessage *msg; | |||
- char *uri; | |||
+ char *uri = geturi(c); | |||
+ Arg arg; | |||
switch (webkit_web_view_get_load_status (c->view)) { | |||
case WEBKIT_LOAD_COMMITTED: | |||
- uri = geturi(c); | |||
if (strstr(uri, "https://") == uri) { | |||
frame = webkit_web_view_get_main_frame(c->view); | |||
src = webkit_web_frame_get_data_source(frame); | |||
@@ -809,6 +809,8 @@ loadstatuschange(WebKitWebView *view, GParamSpec *pspec, Client *c) | |||
setstyle(c, getstyle(uri)); | |||
break; | |||
case WEBKIT_LOAD_FINISHED: | |||
+ arg = (Arg)ONLOAD(uri); | |||
+ spawn(NULL, &arg); | |||
c->progress = 100; | |||
updatetitle(c); | |||
if (diskcache) { |
@ -1,93 +0,0 @@ | |||
diff --git a/surf.c b/surf.c | |||
index 93a1629..ba53b94 100644 | |||
--- a/surf.c | |||
+++ b/surf.c | |||
@@ -217,6 +217,7 @@ static void togglefullscreen(Client *c, const Arg *a); | |||
static void togglecookiepolicy(Client *c, const Arg *a); | |||
static void toggleinspector(Client *c, const Arg *a); | |||
static void find(Client *c, const Arg *a); | |||
+static void externalpipe(Client *c, const Arg *a); | |||
/* Buttons */ | |||
static void clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h); | |||
@@ -241,6 +242,80 @@ char *argv0; | |||
/* configuration, allows nested code to access above variables */ | |||
#include "config.h" | |||
+static void | |||
+externalpipe_execute(char* buffer, Arg *arg) { | |||
+ int to[2]; | |||
+ void (*oldsigpipe)(int); | |||
+ | |||
+ if (pipe(to) == -1) | |||
+ return; | |||
+ | |||
+ switch (fork()) { | |||
+ case -1: | |||
+ close(to[0]); | |||
+ close(to[1]); | |||
+ return; | |||
+ case 0: | |||
+ dup2(to[0], STDIN_FILENO); close(to[0]); close(to[1]); | |||
+ execvp(((char **)arg->v)[0], (char **)arg->v); | |||
+ fprintf(stderr, "st: execvp %s\n", ((char **)arg->v)[0]); | |||
+ perror("failed"); | |||
+ exit(0); | |||
+ } | |||
+ | |||
+ close(to[0]); | |||
+ oldsigpipe = signal(SIGPIPE, SIG_IGN); | |||
+ write(to[1], buffer, strlen(buffer)); | |||
+ close(to[1]); | |||
+ signal(SIGPIPE, oldsigpipe); | |||
+} | |||
+ | |||
+static void | |||
+externalpipe_resource_done(WebKitWebResource *r, GAsyncResult *s, Arg *arg) | |||
+{ | |||
+ GError *gerr = NULL; | |||
+ guchar *buffer = webkit_web_resource_get_data_finish(r, s, NULL, &gerr); | |||
+ if (gerr == NULL) { | |||
+ externalpipe_execute((char *) buffer, arg); | |||
+ } else { | |||
+ g_error_free(gerr); | |||
+ } | |||
+ g_free(buffer); | |||
+} | |||
+ | |||
+static void | |||
+externalpipe_js_done(WebKitWebView *wv, GAsyncResult *s, Arg *arg) | |||
+{ | |||
+ WebKitJavascriptResult *j = webkit_web_view_run_javascript_finish( | |||
+ wv, s, NULL); | |||
+ if (!j) { | |||
+ return; | |||
+ } | |||
+ JSCValue *v = webkit_javascript_result_get_js_value(j); | |||
+ if (jsc_value_is_string(v)) { | |||
+ char *buffer = jsc_value_to_string(v); | |||
+ externalpipe_execute(buffer, arg); | |||
+ g_free(buffer); | |||
+ } | |||
+ webkit_javascript_result_unref(j); | |||
+} | |||
+ | |||
+void | |||
+externalpipe(Client *c, const Arg *arg) | |||
+{ | |||
+ if (curconfig[JavaScript].val.i) { | |||
+ webkit_web_view_run_javascript( | |||
+ c->view, "window.document.documentElement.outerHTML", | |||
+ NULL, externalpipe_js_done, arg); | |||
+ } else { | |||
+ WebKitWebResource *resource = webkit_web_view_get_main_resource(c->view); | |||
+ if (resource != NULL) { | |||
+ webkit_web_resource_get_data( | |||
+ resource, NULL, externalpipe_resource_done, arg); | |||
+ } | |||
+ } | |||
+} | |||
+ | |||
void | |||
usage(void) | |||
{ |
@ -1,24 +0,0 @@ | |||
diff --git a/config.def.h b/config.def.h | |||
--- a/config.def.h | |||
+++ b/config.def.h | |||
@@ -164,3 +164,5 @@ static Button buttons[] = { | |||
{ OnAny, 0, 9, clicknavigate, { .i = +1 }, 1 }, | |||
{ OnMedia, MODKEY, 1, clickexternplayer, { 0 }, 1 }, | |||
}; | |||
+ | |||
+#define HOMEPAGE "https://duckduckgo.com/" | |||
diff --git a/surf.c b/surf.c | |||
--- a/surf.c | |||
+++ b/surf.c | |||
@@ -1751,7 +1751,11 @@ main(int argc, char *argv[]) | |||
if (argc > 0) | |||
arg.v = argv[0]; | |||
else | |||
+#ifdef HOMEPAGE | |||
+ arg.v = HOMEPAGE; | |||
+#else | |||
arg.v = "about:blank"; | |||
+#endif | |||
setup(); | |||
c = newclient(NULL); |
@ -1,42 +0,0 @@ | |||
diff --git a/config.def.h b/config.def.h | |||
index 2e735bf..43ad9ab 100644 | |||
--- a/config.def.h | |||
+++ b/config.def.h | |||
@@ -69,8 +69,9 @@ static WebKitFindOptions findopts = WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | | |||
#define SETPROP(r, s, p) { \ | |||
.v = (const char *[]){ "/bin/sh", "-c", \ | |||
"prop=\"$(printf '%b' \"$(xprop -id $1 $2 " \ | |||
- "| sed \"s/^$2(STRING) = //;s/^\\\"\\(.*\\)\\\"$/\\1/\")\" " \ | |||
- "| dmenu -p \"$4\" -w $1)\" && xprop -id $1 -f $3 8s -set $3 \"$prop\"", \ | |||
+ "| sed \"s/^$2(STRING) = //;s/^\\\"\\(.*\\)\\\"$/\\1/\" && cat ~/.surf/bookmarks)\" " \ | |||
+ "| dmenu -l 10 -p \"$4\" -w $1)\" && " \ | |||
+ "xprop -id $1 -f $3 8s -set $3 \"$prop\"", \ | |||
"surf-setprop", winid, r, s, p, NULL \ | |||
} \ | |||
} | |||
@@ -101,6 +102,17 @@ static WebKitFindOptions findopts = WEBKIT_FIND_OPTIONS_CASE_INSENSITIVE | | |||
} \ | |||
} | |||
+/* BM_ADD(readprop) */ | |||
+#define BM_ADD(r) {\ | |||
+ .v = (const char *[]){ "/bin/sh", "-c", \ | |||
+ "(echo $(xprop -id $0 $1) | cut -d '\"' -f2 " \ | |||
+ "| sed 's/.*https*:\\/\\/\\(www\\.\\)\\?//' && cat ~/.surf/bookmarks) " \ | |||
+ "| awk '!seen[$0]++' > ~/.surf/bookmarks.tmp && " \ | |||
+ "mv ~/.surf/bookmarks.tmp ~/.surf/bookmarks", \ | |||
+ winid, r, NULL \ | |||
+ } \ | |||
+} | |||
+ | |||
/* styles */ | |||
/* | |||
* The iteration will stop at the first match, beginning at the beginning of | |||
@@ -132,6 +144,7 @@ static Key keys[] = { | |||
{ MODKEY, GDK_KEY_g, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, | |||
{ MODKEY, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, | |||
{ MODKEY, GDK_KEY_slash, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, | |||
+ { MODKEY, GDK_KEY_m, spawn, BM_ADD("_SURF_URI") }, | |||
{ 0, GDK_KEY_Escape, stop, { 0 } }, | |||
{ MODKEY, GDK_KEY_c, stop, { 0 } }, |
@ -1,67 +0,0 @@ | |||
From a6a8878bb6a203b589d559025b94a78214f22878 Mon Sep 17 00:00:00 2001 | |||
From: Olivier Moreau <m242@protonmail.com> | |||
Date: Sun, 12 Jan 2020 11:23:11 +0000 | |||
Subject: [PATCH] Added choice between PRIMARY and CLIPBOARD Gtk selections, as | |||
a config option | |||
--- | |||
config.def.h | 1 + | |||
surf.c | 11 +++++++++-- | |||
2 files changed, 10 insertions(+), 2 deletions(-) | |||
diff --git a/config.def.h b/config.def.h | |||
index 34265f6..03bbe2b 100644 | |||
--- a/config.def.h | |||
+++ b/config.def.h | |||
@@ -48,6 +48,7 @@ static Parameter defconfig[ParameterLast] = { | |||
[Style] = { { .i = 1 }, }, | |||
[WebGL] = { { .i = 0 }, }, | |||
[ZoomLevel] = { { .f = 1.0 }, }, | |||
+ [ClipboardNotPrimary] = { { .i = 1 }, }, | |||
}; | |||
static UriParameters uriparams[] = { | |||
diff --git a/surf.c b/surf.c | |||
index 2b54e3c..b8a9b2f 100644 | |||
--- a/surf.c | |||
+++ b/surf.c | |||
@@ -82,6 +82,7 @@ typedef enum { | |||
Style, | |||
WebGL, | |||
ZoomLevel, | |||
+ ClipboardNotPrimary, | |||
ParameterLast | |||
} ParamName; | |||
@@ -291,6 +292,7 @@ static ParamName loadcommitted[] = { | |||
SpellLanguages, | |||
Style, | |||
ZoomLevel, | |||
+ ClipboardNotPrimary, | |||
ParameterLast | |||
}; | |||
@@ -1816,13 +1818,18 @@ showcert(Client *c, const Arg *a) | |||
void | |||
clipboard(Client *c, const Arg *a) | |||
{ | |||
+ /* User defined choice of selection, see config.h */ | |||
+ GdkAtom selection = GDK_SELECTION_PRIMARY; | |||
+ if (curconfig[ClipboardNotPrimary].val.i > 0) | |||
+ selection = GDK_SELECTION_CLIPBOARD; | |||
+ | |||
if (a->i) { /* load clipboard uri */ | |||
gtk_clipboard_request_text(gtk_clipboard_get( | |||
- GDK_SELECTION_PRIMARY), | |||
+ selection), | |||
pasteuri, c); | |||
} else { /* copy uri */ | |||
gtk_clipboard_set_text(gtk_clipboard_get( | |||
- GDK_SELECTION_PRIMARY), c->targeturi | |||
+ selection), c->targeturi | |||
? c->targeturi : geturi(c), -1); | |||
} | |||
} | |||
-- | |||
2.24.1 | |||
@ -1,107 +0,0 @@ | |||
diff -up surf-2.0/config.def.h surf-2.0-history/config.def.h | |||
--- surf-2.0/config.def.h 2017-11-26 14:29:37.963786915 +0100 | |||
+++ surf-2.0-history/config.def.h 2017-11-26 19:48:31.300096237 +0100 | |||
@@ -6,6 +6,7 @@ static char *styledir = "~/.surf/s | |||
static char *certdir = "~/.surf/certificates/"; | |||
static char *cachedir = "~/.surf/cache/"; | |||
static char *cookiefile = "~/.surf/cookies.txt"; | |||
+static char *historyfile = "~/.surf/history.txt"; | |||
/* Webkit default features */ | |||
/* Highest priority value will be used. | |||
@@ -101,6 +102,11 @@ static WebKitFindOptions findopts = WEBK | |||
} \ | |||
} | |||
+#define SETURI(p) { .v = (char *[]){ "/bin/sh", "-c", \ | |||
+"prop=\"`surf_history_dmenu.sh`\" &&" \ | |||
+"xprop -id $1 -f $0 8s -set $0 \"$prop\"", \ | |||
+p, winid, NULL } } | |||
+ | |||
/* styles */ | |||
/* | |||
* The iteration will stop at the first match, beginning at the beginning of | |||
@@ -181,6 +187,7 @@ static Key keys[] = { | |||
{ MODKEY|GDK_SHIFT_MASK, GDK_KEY_b, toggle, { .i = ScrollBars } }, | |||
{ MODKEY|GDK_SHIFT_MASK, GDK_KEY_t, toggle, { .i = StrictTLS } }, | |||
{ MODKEY|GDK_SHIFT_MASK, GDK_KEY_m, toggle, { .i = Style } }, | |||
+ { MODKEY , GDK_KEY_Return, spawn, SETURI("_SURF_GO") }, | |||
}; | |||
/* button definitions */ | |||
Only in surf-2.0-history/: config.h | |||
Only in surf-2.0: .git | |||
Only in surf-2.0-history/: surf | |||
diff -up surf-2.0/surf.c surf-2.0-history/surf.c | |||
--- surf-2.0/surf.c 2017-11-26 14:29:37.963786915 +0100 | |||
+++ surf-2.0-history/surf.c 2017-11-26 14:20:36.757100476 +0100 | |||
@@ -171,6 +171,7 @@ static void newwindow(Client *c, const A | |||
static void spawn(Client *c, const Arg *a); | |||
static void destroyclient(Client *c); | |||
static void cleanup(void); | |||
+static void updatehistory(const char *u, const char *t); | |||
/* GTK/WebKit */ | |||
static WebKitWebView *newview(Client *c, WebKitWebView *rv); | |||
@@ -336,10 +337,11 @@ setup(void) | |||
curconfig = defconfig; | |||
/* dirs and files */ | |||
- cookiefile = buildfile(cookiefile); | |||
- scriptfile = buildfile(scriptfile); | |||
- cachedir = buildpath(cachedir); | |||
- certdir = buildpath(certdir); | |||
+ cookiefile = buildfile(cookiefile); | |||
+ historyfile = buildfile(historyfile); | |||
+ scriptfile = buildfile(scriptfile); | |||
+ cachedir = buildpath(cachedir); | |||
+ certdir = buildpath(certdir); | |||
gdkkb = gdk_seat_get_keyboard(gdk_display_get_default_seat(gdpy)); | |||
@@ -1042,12 +1044,28 @@ cleanup(void) | |||
while (clients) | |||
destroyclient(clients); | |||
g_free(cookiefile); | |||
+ g_free(historyfile); | |||
g_free(scriptfile); | |||
g_free(stylefile); | |||
g_free(cachedir); | |||
XCloseDisplay(dpy); | |||
} | |||
+void | |||
+updatehistory(const char *u, const char *t) | |||
+{ | |||
+ FILE *f; | |||
+ f = fopen(historyfile, "a+"); | |||
+ | |||
+ char b[20]; | |||
+ time_t now = time (0); | |||
+ strftime (b, 20, "%Y-%m-%d %H:%M:%S", localtime (&now)); | |||
+ fputs(b, f); | |||
+ | |||
+ fprintf(f, " %s %s\n", u, t); | |||
+ fclose(f); | |||
+} | |||
+ | |||
WebKitWebView * | |||
newview(Client *c, WebKitWebView *rv) | |||
{ | |||
@@ -1417,6 +1435,7 @@ loadfailedtls(WebKitWebView *v, gchar *u | |||
return TRUE; | |||
} | |||
+ | |||
void | |||
loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c) | |||
{ | |||
@@ -1445,6 +1464,7 @@ loadchanged(WebKitWebView *v, WebKitLoad | |||
break; | |||
case WEBKIT_LOAD_FINISHED: | |||
seturiparameters(c, uri, loadfinished); | |||
+ updatehistory(uri, c->title); | |||
/* Disabled until we write some WebKitWebExtension for | |||
* manipulating the DOM directly. | |||
evalscript(c, "document.documentElement.style.overflow = '%s'", | |||
Only in surf-2.0-history/: surf.o |
@ -1,134 +0,0 @@ | |||
From 74a98d9600c50d50b9323cf8e459c88eb15da557 Mon Sep 17 00:00:00 2001 | |||
From: efe <efe@efe.kim> | |||
Date: Sat, 9 Feb 2019 13:16:51 -0500 | |||
Subject: [PATCH] Modal behaviour, 'i' to insert 'Esc' to get to the normal | |||
mode | |||
--- | |||
config.def.h | 53 +++++++++++++++++++++++++++------------------------- | |||
surf.c | 14 +++++++++++++- | |||
2 files changed, 41 insertions(+), 26 deletions(-) | |||
diff --git a/config.def.h b/config.def.h | |||
index 34265f6..8b7d5a2 100644 | |||
--- a/config.def.h | |||
+++ b/config.def.h | |||
@@ -130,41 +130,44 @@ static SiteSpecific certs[] = { | |||
*/ | |||
static Key keys[] = { | |||
/* modifier keyval function arg */ | |||
- { MODKEY, GDK_KEY_g, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, | |||
- { MODKEY, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, | |||
- { MODKEY, GDK_KEY_slash, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, | |||
+ { 0, GDK_KEY_g, spawn, SETPROP("_SURF_URI", "_SURF_GO", PROMPT_GO) }, | |||
+ { 0, GDK_KEY_f, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, | |||
+ { 0, GDK_KEY_slash, spawn, SETPROP("_SURF_FIND", "_SURF_FIND", PROMPT_FIND) }, | |||
- { 0, GDK_KEY_Escape, stop, { 0 } }, | |||
- { MODKEY, GDK_KEY_c, stop, { 0 } }, | |||
+ { 0, GDK_KEY_i, insert, { .i = 1 } }, | |||
+ { 0, GDK_KEY_Escape, insert, { .i = 0 } }, | |||
- { MODKEY|GDK_SHIFT_MASK, GDK_KEY_r, reload, { .i = 1 } }, | |||
- { MODKEY, GDK_KEY_r, reload, { .i = 0 } }, | |||
+ { 0, GDK_KEY_c, stop, { 0 } }, | |||
- { MODKEY, GDK_KEY_l, navigate, { .i = +1 } }, | |||
- { MODKEY, GDK_KEY_h, navigate, { .i = -1 } }, | |||
+ { MODKEY, GDK_KEY_r, reload, { .i = 1 } }, | |||
+ { 0, GDK_KEY_r, reload, { .i = 0 } }, | |||
+ | |||
+ { 0, GDK_KEY_l, navigate, { .i = +1 } }, | |||
+ { 0, GDK_KEY_h, navigate, { .i = -1 } }, | |||
/* vertical and horizontal scrolling, in viewport percentage */ | |||
- { MODKEY, GDK_KEY_j, scrollv, { .i = +10 } }, | |||
- { MODKEY, GDK_KEY_k, scrollv, { .i = -10 } }, | |||
- { MODKEY, GDK_KEY_space, scrollv, { .i = +50 } }, | |||
- { MODKEY, GDK_KEY_b, scrollv, { .i = -50 } }, | |||
- { MODKEY, GDK_KEY_i, scrollh, { .i = +10 } }, | |||
- { MODKEY, GDK_KEY_u, scrollh, { .i = -10 } }, | |||
+ { 0, GDK_KEY_j, scrollv, { .i = +10 } }, | |||
+ { 0, GDK_KEY_k, scrollv, { .i = -10 } }, | |||
+ { 0, GDK_KEY_space, scrollv, { .i = +50 } }, | |||
+ { 0, GDK_KEY_b, scrollv, { .i = -50 } }, | |||
+ { 0, GDK_KEY_i, scrollh, { .i = +10 } }, | |||
+ { 0, GDK_KEY_u, scrollh, { .i = -10 } }, | |||
- { MODKEY|GDK_SHIFT_MASK, GDK_KEY_j, zoom, { .i = -1 } }, | |||
- { MODKEY|GDK_SHIFT_MASK, GDK_KEY_k, zoom, { .i = +1 } }, | |||
- { MODKEY|GDK_SHIFT_MASK, GDK_KEY_q, zoom, { .i = 0 } }, | |||
- { MODKEY, GDK_KEY_minus, zoom, { .i = -1 } }, | |||
- { MODKEY, GDK_KEY_plus, zoom, { .i = +1 } }, | |||
+ { 0|GDK_SHIFT_MASK, GDK_KEY_j, zoom, { .i = -1 } }, | |||
+ { 0|GDK_SHIFT_MASK, GDK_KEY_k, zoom, { .i = +1 } }, | |||
+ { 0|GDK_SHIFT_MASK, GDK_KEY_q, zoom, { .i = 0 } }, | |||
+ { 0, GDK_KEY_minus, zoom, { .i = -1 } }, | |||
+ { 0|GDK_SHIFT_MASK, GDK_KEY_plus, zoom, { .i = +1 } }, | |||
+ { 0, GDK_KEY_equal, zoom, { .i = 0 } }, | |||
- { MODKEY, GDK_KEY_p, clipboard, { .i = 1 } }, | |||
- { MODKEY, GDK_KEY_y, clipboard, { .i = 0 } }, | |||
+ { 0, GDK_KEY_p, clipboard, { .i = 1 } }, | |||
+ { 0, GDK_KEY_y, clipboard, { .i = 0 } }, | |||
- { MODKEY, GDK_KEY_n, find, { .i = +1 } }, | |||
- { MODKEY|GDK_SHIFT_MASK, GDK_KEY_n, find, { .i = -1 } }, | |||
+ { 0, GDK_KEY_n, find, { .i = +1 } }, | |||
+ { 0|GDK_SHIFT_MASK, GDK_KEY_n, find, { .i = -1 } }, | |||
- { MODKEY|GDK_SHIFT_MASK, GDK_KEY_p, print, { 0 } }, | |||
+ { MODKEY, GDK_KEY_p, print, { 0 } }, | |||
{ MODKEY, GDK_KEY_t, showcert, { 0 } }, | |||
{ MODKEY|GDK_SHIFT_MASK, GDK_KEY_a, togglecookiepolicy, { 0 } }, | |||
diff --git a/surf.c b/surf.c | |||
index 2b54e3c..f4cbe68 100644 | |||
--- a/surf.c | |||
+++ b/surf.c | |||
@@ -175,6 +175,7 @@ static void spawn(Client *c, const Arg *a); | |||
static void msgext(Client *c, char type, const Arg *a); | |||
static void destroyclient(Client *c); | |||
static void cleanup(void); | |||
+static int insertmode = 0; | |||
/* GTK/WebKit */ | |||
static WebKitWebView *newview(Client *c, WebKitWebView *rv); | |||
@@ -231,6 +232,7 @@ static void togglefullscreen(Client *c, const Arg *a); | |||
static void togglecookiepolicy(Client *c, const Arg *a); | |||
static void toggleinspector(Client *c, const Arg *a); | |||
static void find(Client *c, const Arg *a); | |||
+static void insert(Client *c, const Arg *a); | |||
/* Buttons */ | |||
static void clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h); | |||
@@ -1333,7 +1335,11 @@ winevent(GtkWidget *w, GdkEvent *e, Client *c) | |||
updatetitle(c); | |||
break; | |||
case GDK_KEY_PRESS: | |||
- if (!curconfig[KioskMode].val.i) { | |||
+ if (!curconfig[KioskMode].val.i && | |||
+ !insertmode || | |||
+ CLEANMASK(e->key.state) == (MODKEY|GDK_SHIFT_MASK) || | |||
+ CLEANMASK(e->key.state) == (MODKEY) || | |||
+ gdk_keyval_to_lower(e->key.keyval) == (GDK_KEY_Escape)) { | |||
for (i = 0; i < LENGTH(keys); ++i) { | |||
if (gdk_keyval_to_lower(e->key.keyval) == | |||
keys[i].keyval && | |||
@@ -1947,6 +1953,12 @@ find(Client *c, const Arg *a) | |||
} | |||
} | |||
+void | |||
+insert(Client *c, const Arg *a) | |||
+{ | |||
+ insertmode = (a->i); | |||
+} | |||
+ | |||
void | |||
clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h) | |||
{ | |||
-- | |||
2.20.1 | |||
@ -1,101 +0,0 @@ | |||
From 8d8ca34a8e61733711e23ce43b88435bfdfd4962 Mon Sep 17 00:00:00 2001 | |||
From: knary <theknary@gmail.com> | |||
Date: Mon, 25 Mar 2019 15:03:15 -0400 | |||
Subject: [PATCH] This patch replaces scriptfile with an array of | |||
scriptfiles[]. This allows for the inclusion of multiple javascript files | |||
instead of filling up one file with multiple script plugins. | |||
--- | |||
config.def.h | 4 +++- | |||
surf.c | 23 +++++++++++++++-------- | |||
2 files changed, 18 insertions(+), 9 deletions(-) | |||
diff --git a/config.def.h b/config.def.h | |||
index 34265f6..7d7d68e 100644 | |||
--- a/config.def.h | |||
+++ b/config.def.h | |||
@@ -1,11 +1,13 @@ | |||
/* modifier 0 means no modifier */ | |||
static int surfuseragent = 1; /* Append Surf version to default WebKit user agent */ | |||
static char *fulluseragent = ""; /* Or override the whole user agent string */ | |||
-static char *scriptfile = "~/.surf/script.js"; | |||
static char *styledir = "~/.surf/styles/"; | |||
static char *certdir = "~/.surf/certificates/"; | |||
static char *cachedir = "~/.surf/cache/"; | |||
static char *cookiefile = "~/.surf/cookies.txt"; | |||
+static char *scriptfiles[] = { | |||
+ "~/.surf/script.js", | |||
+}; | |||
/* Webkit default features */ | |||
/* Highest priority value will be used. | |||
diff --git a/surf.c b/surf.c | |||
index 2b54e3c..34a75de 100644 | |||
--- a/surf.c | |||
+++ b/surf.c | |||
@@ -337,9 +337,11 @@ setup(void) | |||
/* dirs and files */ | |||
cookiefile = buildfile(cookiefile); | |||
- scriptfile = buildfile(scriptfile); | |||
cachedir = buildpath(cachedir); | |||
certdir = buildpath(certdir); | |||
+ for (i = 0; i < LENGTH(scriptfiles); i++) { | |||
+ scriptfiles[i] = buildfile(scriptfiles[i]); | |||
+ } | |||
gdkkb = gdk_seat_get_keyboard(gdk_display_get_default_seat(gdpy)); | |||
@@ -945,9 +947,11 @@ runscript(Client *c) | |||
gchar *script; | |||
gsize l; | |||
- if (g_file_get_contents(scriptfile, &script, &l, NULL) && l) | |||
- evalscript(c, "%s", script); | |||
- g_free(script); | |||
+ for (int i = 0; i < LENGTH(scriptfiles); i++) { | |||
+ if (g_file_get_contents(scriptfiles[i], &script, &l, NULL) && l) | |||
+ evalscript(c, "%s", script); | |||
+ g_free(script); | |||
+ } | |||
} | |||
void | |||
@@ -1010,9 +1014,9 @@ newwindow(Client *c, const Arg *a, int noembed) | |||
cmd[i++] = curconfig[Style].val.i ? "-M" : "-m" ; | |||
cmd[i++] = curconfig[Inspector].val.i ? "-N" : "-n" ; | |||
cmd[i++] = curconfig[Plugins].val.i ? "-P" : "-p" ; | |||
- if (scriptfile && g_strcmp0(scriptfile, "")) { | |||
+ if (scriptfiles[0] && g_strcmp0(scriptfiles[0], "")) { | |||
cmd[i++] = "-r"; | |||
- cmd[i++] = scriptfile; | |||
+ cmd[i++] = scriptfiles[0]; | |||
} | |||
cmd[i++] = curconfig[JavaScript].val.i ? "-S" : "-s"; | |||
cmd[i++] = curconfig[StrictTLS].val.i ? "-T" : "-t"; | |||
@@ -1076,9 +1080,12 @@ cleanup(void) | |||
close(pipein[0]); | |||
close(pipeout[1]); | |||
g_free(cookiefile); | |||
- g_free(scriptfile); | |||
g_free(stylefile); | |||
g_free(cachedir); | |||
+ for (int i = 0; i < LENGTH(scriptfiles); i++) { | |||
+ g_free(scriptfiles[i]); | |||
+ } | |||
+ | |||
XCloseDisplay(dpy); | |||
} | |||
@@ -2067,7 +2074,7 @@ main(int argc, char *argv[]) | |||
defconfig[Plugins].prio = 2; | |||
break; | |||
case 'r': | |||
- scriptfile = EARGF(usage()); | |||
+ scriptfiles[0] = EARGF(usage()); | |||
break; | |||
case 's': | |||
defconfig[JavaScript].val.i = 0; | |||
-- | |||
2.21.0 | |||
@ -1,26 +0,0 @@ | |||
diff --git a/config.def.h b/config.def.h | |||
index 6d3135e..75dc6a6 100644 | |||
--- a/config.def.h | |||
+++ b/config.def.h | |||
@@ -153,6 +153,8 @@ static Key keys[] = { | |||
{ MODKEY|GDK_SHIFT_MASK, GDK_KEY_m, toggle, { .i = Style } }, | |||
}; | |||
+static char *searchengine = "https://duckduckgo.com/?q="; | |||
+ | |||
/* button definitions */ | |||
/* target can be OnDoc, OnLink, OnImg, OnMedia, OnEdit, OnBar, OnSel, OnAny */ | |||
static Button buttons[] = { | |||
diff --git a/surf.c b/surf.c | |||
index 93a1629..c20537e 100644 | |||
--- a/surf.c | |||
+++ b/surf.c | |||
@@ -476,6 +476,8 @@ loaduri(Client *c, const Arg *a) | |||
} else if (!stat(uri, &st) && (path = realpath(uri, NULL))) { | |||
url = g_strdup_printf("file://%s", path); | |||
free(path); | |||
+ } else if (*uri == ' ') { | |||
+ url = g_strdup_printf("%s%s", searchengine, uri + 1); | |||
} else { | |||
url = g_strdup_printf("http://%s", uri); | |||
} |
@ -1,138 +0,0 @@ | |||
diff --git a/config.def.h b/config.def.h | |||
index 5245129..604028f 100644 | |||
--- a/config.def.h | |||
+++ b/config.def.h | |||
@@ -45,6 +45,16 @@ static Bool allowgeolocation = TRUE; | |||
} \ | |||
} | |||
+#define SELNAV { \ | |||
+ .v = (char *[]){ "/bin/sh", "-c", \ | |||
+ "prop=\"`xprop -id $0 _SURF_HIST" \ | |||
+ " | sed -e 's/^.[^\"]*\"//' -e 's/\"$//' -e 's/\\\\\\n/\\n/g'" \ | |||
+ " | dmenu -i -l 10`\"" \ | |||
+ " && xprop -id $0 -f _SURF_NAV 8s -set _SURF_NAV \"$prop\"", \ | |||
+ winid, NULL \ | |||
+ } \ | |||
+} | |||
+ | |||
/* DOWNLOAD(URI, referer) */ | |||
#define DOWNLOAD(d, r) { \ | |||
.v = (char *[]){ "/bin/sh", "-c", \ | |||
@@ -99,6 +109,7 @@ static Key keys[] = { | |||
{ MODKEY, GDK_l, navigate, { .i = +1 } }, | |||
{ MODKEY, GDK_h, navigate, { .i = -1 } }, | |||
+ { MODKEY|GDK_SHIFT_MASK,GDK_h, selhist, SELNAV }, | |||
{ MODKEY, GDK_j, scroll_v, { .i = +1 } }, | |||
{ MODKEY, GDK_k, scroll_v, { .i = -1 } }, | |||
diff --git a/surf.c b/surf.c | |||
index 0fae80b..1c09336 100644 | |||
--- a/surf.c | |||
+++ b/surf.c | |||
@@ -36,7 +36,7 @@ char *argv0; | |||
#define COOKIEJAR_TYPE (cookiejar_get_type ()) | |||
#define COOKIEJAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), COOKIEJAR_TYPE, CookieJar)) | |||
-enum { AtomFind, AtomGo, AtomUri, AtomLast }; | |||
+enum { AtomFind, AtomGo, AtomUri, AtomHist, AtomNav, AtomLast }; | |||
enum { | |||
ClkDoc = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT, | |||
ClkLink = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK, | |||
@@ -177,6 +177,8 @@ static void loadstatuschange(WebKitWebView *view, GParamSpec *pspec, | |||
Client *c); | |||
static void loaduri(Client *c, const Arg *arg); | |||
static void navigate(Client *c, const Arg *arg); | |||
+static void selhist(Client *c, const Arg *arg); | |||
+static void navhist(Client *c, const Arg *arg); | |||
static Client *newclient(void); | |||
static void newwindow(Client *c, const Arg *arg, gboolean noembed); | |||
static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d); | |||
@@ -813,6 +815,59 @@ navigate(Client *c, const Arg *arg) { | |||
webkit_web_view_go_back_or_forward(c->view, steps); | |||
} | |||
+static void | |||
+selhist(Client *c, const Arg *arg) { | |||
+ WebKitWebBackForwardList *lst; | |||
+ WebKitWebHistoryItem *cur; | |||
+ gint i; | |||
+ gchar *out; | |||
+ gchar *tmp; | |||
+ gchar *line; | |||
+ | |||
+ out = g_strdup(""); | |||
+ | |||
+ if(!(lst = webkit_web_view_get_back_forward_list(c->view))) | |||
+ return; | |||
+ | |||
+ for(i = webkit_web_back_forward_list_get_back_length(lst); i > 0; i--) { | |||
+ if(!(cur = webkit_web_back_forward_list_get_nth_item(lst, -i))) | |||
+ break; | |||
+ line = g_strdup_printf("%d: %s\n", -i, | |||
+ webkit_web_history_item_get_original_uri(cur)); | |||
+ tmp = g_strconcat(out, line, NULL); | |||
+ g_free(out); | |||
+ out = tmp; | |||
+ } | |||
+ | |||
+ if((cur = webkit_web_back_forward_list_get_nth_item(lst, 0))) { | |||
+ line = g_strdup_printf("%d: %s", 0, | |||
+ webkit_web_history_item_get_original_uri(cur)); | |||
+ tmp = g_strconcat(out, line, NULL); | |||
+ g_free(out); | |||
+ out = tmp; | |||
+ } | |||
+ | |||
+ for(i = 1; i <= webkit_web_back_forward_list_get_forward_length(lst); i++) { | |||
+ if(!(cur = webkit_web_back_forward_list_get_nth_item(lst, i))) | |||
+ break; | |||
+ line = g_strdup_printf("\n%d: %s", i, | |||
+ webkit_web_history_item_get_original_uri(cur)); | |||
+ tmp = g_strconcat(out, line, NULL); | |||
+ g_free(out); | |||
+ out = tmp; | |||
+ } | |||
+ | |||
+ setatom(c, AtomHist, out); | |||
+ g_free(out); | |||
+ spawn(c, arg); | |||
+} | |||
+ | |||
+static void | |||
+navhist(Client *c, const Arg *arg) { | |||
+ Arg a = { .i = atoi(arg->v) }; | |||
+ navigate(c, &a); | |||
+} | |||
+ | |||
static Client * | |||
newclient(void) { | |||
Client *c; | |||
@@ -1014,6 +1069,7 @@ newclient(void) { | |||
setatom(c, AtomFind, ""); | |||
setatom(c, AtomUri, "about:blank"); | |||
+ setatom(c, AtomHist, ""); | |||
if(hidebackground) | |||
webkit_web_view_set_transparent(c->view, TRUE); | |||
@@ -1153,6 +1209,9 @@ processx(GdkXEvent *e, GdkEvent *event, gpointer d) { | |||
loaduri(c, &arg); | |||
return GDK_FILTER_REMOVE; | |||
+ } else if(ev->atom == atoms[AtomNav]) { | |||
+ arg.v = getatom(c, AtomNav); | |||
+ navhist(c, &arg); | |||
} | |||
} | |||
} | |||
@@ -1247,6 +1306,8 @@ setup(void) { | |||
atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False); | |||
atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False); | |||
atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False); | |||
+ atoms[AtomHist] = XInternAtom(dpy, "_SURF_HIST", False); | |||
+ atoms[AtomNav] = XInternAtom(dpy, "_SURF_NAV", False); | |||
/* dirs and files */ | |||
cookiefile = buildfile(cookiefile); |