Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Initial Commit
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 8c27047cfb08c3d0f13741bcbe338631e35910c6a624dae791536e80d302afd1
User & Date: jmcclure 2020-03-25 16:00:26
Context
2020-03-25
21:17
Better 'hushbang' handling; XDG Base Directory use check-in: b90795e12d user: jmcclure tags: trunk
16:00
Initial Commit check-in: 8c27047cfb user: jmcclure tags: trunk
15:58
initial empty check-in check-in: ae297aabb9 user: jmcclure tags: trunk
Changes

Added LICENSE.





































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Copyright 2020 Jesse McClure

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Added Makefile.





































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

EXE       = interrobang
DEPS      = x11 xrender xft
PREFIX   ?= /usr
CC       ?= gcc
PKGCONF  ?= pkgconf
CFLAGS   += $(shell $(PKGCONF) --cflags  $(DEPS) )
LDLIBS   += $(shell $(PKGCONF) --libs    $(DEPS) )

$(EXE):

install: $(EXE)
	install -Dm755  -t $(DESTDIR)$(PREFIX)/bin/                     $(EXE)
	install -Dm644  -t $(DESTDIR)$(PREFIX)/share/licenses/$(EXE)/   LICENSE
	install -Dm644  -t $(DESTDIR)$(PREFIX)/share/man1/              $(EXE).1
	install -Dm755  -t $(DESTDIR)$(PREFIX)/lib/$(EXE)/tab/          tab/*
	install -Dm755  -t $(DESTDIR)$(PREFIX)/lib/$(EXE)/run/          run/*

Added config.h.

















































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

#define BANG_CHAR  "!"

static const char *font = "PT Sans";

static unsigned long
	colBG = 0x404448,
	colFG = 0xEEEEEE,
	colCS = 0xFFDD88,
	colBD = 0x000000;

static int
	x = 0,
	y = 0,
	w = 0,
	/* 'h' is determined by font metrics */
	left = 4, // TODO, I hate this ... needs to come from font metrics
	bpx = 1;

static Key keys[] = {
/*   Modifiers             Keysym           Function     Args */
	{ ControlMask,          XK_q,            quit,        { 0 } },
	{ 0,                    XK_Tab,          complete,    { .v = BANG_CHAR } },
	{ 0,                    XK_Return,       run,         { .v = BANG_CHAR } },

	{ 0,                    XK_Left,         move,        { .i = -1 } },
	{ 0,                    XK_Right,        move,        { .i = +1 } },
	{ 0,                    XK_Home,         move,        { .i = -MAX } },
	{ 0,                    XK_End,          move,        { .i = +MAX } },

	{ 0,                    XK_BackSpace,    del,         { .i = -1 } },
	{ 0,                    XK_Delete,       del,         { .i = +1 } },
	{ ShiftMask,            XK_BackSpace,    del,         { .i = -MAX } },
	{ ShiftMask,            XK_Delete,       del,         { .i = +MAX } },

	{ ShiftMask,            XK_Insert,       paste,       { .i = 0 } },
	{ ControlMask,          XK_v,            paste,       { .i = 1 } },

	{ 0,                    0,               NULL,        { 0 } },
};

static Key extra[] = {
	{ 0,                    0,               NULL,        { 0 } },
};

/* KEYMAPS:
 * Any keymap named "default" is always used.
 * Each additional keymap is only included if it's name is listed on the command line preceded by a "+".
 * If a given key binding is included in more than one keymap, the last one take precedence.
 */
static KeyMap keymaps[] = {
	{ "default",      keys },
	{ "extra",        extra },
	{ NULL,           NULL },
};

Added interrobang.c.









































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
/* Copyright 2020 Jesse McClure <code@jessemcclure.org>
 * See LICENSE for details
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <X11/Xft/Xft.h>

#define MAX 1024

#define ENV_CHAR	"iBangChar"
#define ENV_BANG	"iBangBang"
#define ENV_ARGS	"iBangArgs"
#define ENV_LINE	"iBangLine"
#define ENV_LAST	"iBangLast"
#define ENV_TEMP	"iBangTemp"

#define ENV_RUN_PATH   "/home/jmcclure/code/xin/run/"
#define ENV_TAB_PATH   "/home/jmcclure/code/xin/tab/"

typedef union {
	int i;
	unsigned int ui;
	float f;
	const void *v;
} Arg;

typedef struct {
	unsigned int state;
	KeySym keysym;
	void (*func)(const Arg *);
	const Arg arg;
} Key;

typedef struct {
	const char *name;
	const Key *keys;
} KeyMap;

static void complete(const Arg *arg);
static void del(const Arg *arg);
static void move(const Arg *arg);
static void paste(const Arg *arg);
static void quit(const Arg *arg);
static void run(const Arg *arg);

#include "config.h"

static Display *dpy;
static int scr, fh, h, pos = 0, running = 1;
static Window root, win;
static GC gc;
static XftFont *xfont;
static XftDraw *xdraw;
static XftColor xcol;
static char line[MAX];
static const char *hush;


/* INITIALIZATION FUNCTIONS: IN ORDER OF CALL */

static int xlibInit(int argc, const char *argv[]) {
	hush = "default";
	int i;
	dpy = XOpenDisplay(0x0);
	scr = DefaultScreen(dpy);
	root = RootWindow(dpy, scr);
	w = (w ? w : DisplayWidth(dpy,scr) - bpx * 2);
	for (i = 0; i < 1000; i++) {
		if (XGrabKeyboard(dpy,root,True,GrabModeAsync,GrabModeAsync,
			CurrentTime) == GrabSuccess) break;
		usleep(1000);
	}
	if (i == 1000) {
		fprintf(stderr, "Unable to grab keyboard\n");
		XCloseDisplay(dpy);
		exit(1);
	}
	return 0;
}

static int colorsInit(int argc, const char *argv[]) {
	XRenderColor xrcol;
	xrcol.red = ((colFG & 0xFF0000) >> 8) | 0xFF;
	xrcol.green = (colFG & 0x00FF00) | 0xFF;
	xrcol.blue = (colFG << 8) | 0xFF;
	xrcol.alpha = 0xFFFF;
	XftColorAllocValue(dpy, DefaultVisual(dpy, scr), DefaultColormap(dpy, scr), &xrcol, &xcol);
	gc = DefaultGC(dpy, scr);
	XSetForeground(dpy, gc, colCS);
	XSetBackground(dpy, gc, colBG);
	return 0;
}

static int fontInit(int argc, const char *argv[]) {
	xfont = XftFontOpenName(dpy, scr, font);
	fh = xfont->ascent;
	h = fh + xfont->descent;
	return 0;
}

static int winInit(int argc, const char *argv[]) {
	XSetWindowAttributes wa;
	wa.override_redirect = True;
	wa.border_pixel = colBD;
	wa.background_pixel = colBG;
	win = XCreateWindow(dpy, root, x, y, w, h, bpx, DefaultDepth(dpy,scr), CopyFromParent,
			DefaultVisual(dpy,scr), CWOverrideRedirect|CWBorderPixel|CWBackPixel, &wa);
	xdraw = XftDrawCreate(dpy, win, DefaultVisual(dpy, scr), DefaultColormap(dpy, scr));
	XMapWindow(dpy, win);
	XFillRectangle(dpy, win, gc, left + 2, h-fh-3, 2, fh-2);
	return 0;
}

static int keymapInit(int argc, const char *argv[]) {
	int i, j;
	const char *name;
	for (j = 0; keymaps[j].name; j++) {
		name = keymaps[j].name;
		if (strncmp(name, "default", strlen(name)) == 0) continue;
		for (i = 0; i < argc; i++) {
			if (argv[i][0] != '+') continue;
			if (strncmp(name, argv[i] + 1, strlen(name)) == 0) break;
		}
		/* not found on cmd line, null out the keys entry */
		if (i == argc) keymaps[j].keys = NULL;
	}
	return 0;
}


/* EVEN LOOP FUNCTIONS */

static int keymapCheck(int state, KeySym key) {
	int i, j, ret;
	const Key *match = NULL, *list = NULL;
	for (i = 0; keymaps[i].name; i++) {
		list = keymaps[i].keys;
		if (!list) continue; /* if nulled out, skip to next keymap */
		for (j = 0; list[j].func; j++) {
			if (list[j].state == state && list[j].keysym == key)
				break;
		}
		if (list[j].func)
			match = &list[j];
	}
	if (match && match->func) match->func(&(match->arg));
	return match != NULL;
}

static int insertText(const char *txt, int len) {
	char *part = strdup(&line[pos]);
	line[pos] = '\0';
	strncat(line,txt,len);
	strcat(line, part); free(part);
	pos += len;
	return 0;
}

static int redraw() {
	XGlyphInfo ext;
	XClearWindow(dpy, win);
	XftDrawStringUtf8(xdraw, &xcol, xfont, left, fh-2, (XftChar8 *) line, strlen(line));
	XftTextExtentsUtf8(dpy, xfont, (XftChar8 *) line, pos, &ext);
	XFillRectangle(dpy, win, gc, left + ext.xOff + 2, h-fh-3, 2, fh-2);
	return 0;
}

static int mainLoop() {
	XEvent ev;
	KeySym key;
	int len;
	char txt[32];
	snprintf(txt, 32, "/tmp/iBang%d", getpid());
	setenv(ENV_TEMP, txt, 1);
	while (!XNextEvent(dpy, &ev) && running) {
		if (XFilterEvent(&ev,win) || ev.type != KeyPress)
			continue;
		key = NoSymbol;
		XLookupString(&ev.xkey, txt, sizeof txt, &key, NULL);
		len = strlen(txt);
		if (!keymapCheck(ev.xkey.state, key) && !iscntrl(*txt))
			insertText(txt,len);
		redraw();
	}
	return 0;
}

static int cleanup() {
	XftFontClose(dpy, xfont);
	XftDrawDestroy(xdraw);
	XDestroyWindow(dpy, win);
	XUngrabKeyboard(dpy, CurrentTime);
	XCloseDisplay(dpy);
	unlink(getenv(ENV_TEMP));
	return 0;
}

int main(int argc, const char *argv[]) {
	xlibInit(argc, argv);
	colorsInit(argc, argv);
	fontInit(argc, argv);
	winInit(argc, argv);
	keymapInit(argc, argv);
	mainLoop();
	cleanup();
	return 0;
}


/* KEY BINDABLE ACTION FUNCTIONS */

void complete_run_helper(const Arg *arg, const char *path, int run) {
	if (line[0] == '\0' || line[1] == '\0') return;
	char arg0[MAX], arg1[MAX], *p;
	pid_t pid;
	int fd[2];
	FILE *in;
	setenv(ENV_LINE, line, 1);
	strcpy(arg0, path);
	if (line[0] == ((char *)arg->v)[0]) {
		p = strchr(line, ' ');
		if (p) { *p = '\0'; p++; }
		strcat(arg0, line + 1);
		strcpy(arg1, p ? p : "");
		setenv(ENV_CHAR, (char*)arg->v, 1);
		setenv(ENV_BANG, line + 1, 1);
	}
	else {
		strcat(arg0, hush);
		strcpy(arg1, line);
		setenv(ENV_CHAR, "", 1);
		setenv(ENV_BANG, "", 1);
	}
	setenv(ENV_ARGS, arg1, 1);
	pipe(fd);
	pid = fork();
	if (pid == 0) {
		close(fd[0]);
		if (run) close(fd[1]);
		else dup2(fd[1], STDOUT_FILENO);
		execl(arg0, arg0, arg1, NULL);
		fprintf(stderr, "%s for '%s' not found\n", run ? "Runner": "Completer", getenv(ENV_BANG));
		_exit(1);
	}
	else {
		close(fd[1]);
		if (run) { close(fd[0]); cleanup(); exit(0); }
		in = fdopen(fd[0], "r");
		if (!fgets(line, MAX, in)) {
			strcpy(line, getenv(ENV_LINE));
			fclose(in);
			return;
		}
		p = strchr(line, '\n');
		if (p) *p = '\0';
		pos = strlen(line);
		setenv(ENV_LAST, line, 1);
		while(fgets(line, MAX, in));
		fclose(in);
	}
}

void complete(const Arg *arg) {
	complete_run_helper(arg, ENV_TAB_PATH, 0);
}

void del(const Arg *arg) {
	int p = pos + arg->i;
	char *part;
	if (p < pos) { /* Backwards delete, e.g., Backspace */
		if (p < 0) p = 0;
		part = strdup(line + pos);
		strcpy(line + p, part);
		pos = p;
	}
	else { /* Forwards delete, e.g., Del */
		if (p > strlen(line)) p = strlen(line);
		part = strdup(line + p);
		strcpy(line + pos, part);
	}
	free(part);
}

void move(const Arg *arg) {
	pos += arg->i;
	if (pos < 0) pos = 0;
	else if (pos > strlen(line)) pos = strlen(line);
}

void paste(const Arg *arg) {
	// TODO: coming soon (import Atom code from old interrobang code)
}

void quit(const Arg *arg) {
	running = 0;
}

void run(const Arg *arg) {
	complete_run_helper(arg, ENV_RUN_PATH, 1);
}

Added run/calc.







>
>
>
1
2
3
#!/bin/sh

# Do nothing

Added run/default.







>
>
>
1
2
3
#!/bin/sh

eval ${iBangArgs}

Added run/desk.















>
>
>
>
>
>
>
1
2
3
4
5
6
7
#!/bin/sh

deskPath=/usr/share/applications
deskRunner=dex

# Note: if more than 1 file matches the basename, each of them will open in sequence
find ${deskPath} -name "${iBangArgs}*.desktop" -exec ${deskRunner} '{}' \;

Added run/man.











>
>
>
>
>
1
2
3
4
5
#!/bin/sh

manPath=/usr/share/man

st -e /bin/sh -c "man $(find ${manPath} -name "${iBangArgs}*.gz" | sed 1q)"

Added run/pdf.















>
>
>
>
>
>
>
1
2
3
4
5
6
7
#!/bin/sh

pdfPath=${HOME}
pdfViewer=mupdf

# Note: if more than 1 file matches the basename, each of them will open in sequence
find ${pdfPath} -name "${iBangArgs}*.pdf" -exec ${pdfViewer} '{}' \;

Added run/term.











>
>
>
>
>
1
2
3
4
5
#!/bin/sh

term=st

${term} -e ${iBangArgs}

Added run/web.











>
>
>
>
>
1
2
3
4
5
#!/bin/sh

[ -z "${iBangArgs}" ] && exit

${BROWSER:-qutebrowser} "${iBangArgs}" >/dev/null 2>&1 &

Added tab/calc.













>
>
>
>
>
>
1
2
3
4
5
6
#!/bin/sh

echo "${iBangArgs}" \
	| bc \
	| sed "s/^/${iBangChar}${iBangBang} /;s/^ *//"

Added tab/default.





































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh

# if the current line matches the last completition provided, send next one
if [ "${iBangLast}" == "${iBangLine}" ] && [ -f ${iBangTemp} ]; then
	sed -n "/^${iBangLast}$/{n;p;q}" ${iBangTemp}
	exit
fi

# other wise, generate new completition list
find $(echo $PATH | tr ':' ' ') \
	-name "${iBangArgs}*" \
	-exec basename '{}' \; \
	| sort -u \
	| sed "s/^/${iBangChar}${iBangBang} /;s/^ *//" \
	>| ${iBangTemp}

# send first option, and append to end of the list to allow for cycling the list
sed 1q ${iBangTemp} | tee -a ${iBangTemp}

Added tab/desk.





































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh

deskPath=/usr/share/applications

# if the current line matches the last completition provided, send next one
if [ "${iBangLast}" == "${iBangLine}" ] && [ -f ${iBangTemp} ]; then
	sed -n "/^${iBangLast}$/{n;p;q}" ${iBangTemp}
	exit
fi

# other wise, generate new completition list
find ${deskPath} -name "${iBangArgs}*.desktop" -exec basename '{}' .desktop \; \
	| sort -u \
	| sed "s/^/${iBangChar}${iBangBang} /;s/^ *//" \
	>| ${iBangTemp}

# send first option, and append to end of the list to allow for cycling the list
sed 1q ${iBangTemp} | tee -a ${iBangTemp}

Added tab/man.





































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh

manPath=/usr/share/man

# if the current line matches the last completition provided, send next one
if [ "${iBangLast}" == "${iBangLine}" ] && [ -f ${iBangTemp} ]; then
	sed -n "/^${iBangLast}$/{n;p;q}" ${iBangTemp}
	exit
fi

# other wise, generate new completition list
find ${manPath} -name "${iBangArgs}*.gz" -exec basename '{}' .gz \; \
	| sort -u \
	| sed "s/^/${iBangChar}${iBangBang} /;s/^ *//" \
	>| ${iBangTemp}

# send first option, and append to end of the list to allow for cycling the list
sed 1q ${iBangTemp} | tee -a ${iBangTemp}

Added tab/pdf.







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/sh

pdfPath=${HOME}

# If the current line matches the last completition provided, send next one
if [ "${iBangLast}" == "${iBangLine}" ] && [ -f ${iBangTemp} ]; then
	sed -n "/^${iBangLast}$/{n;p;q}" ${iBangTemp}
	exit
fi

# Other wise, generate new completition list
find ${pdfPath} -name "${iBangArgs}*.pdf" -exec basename '{}' .pdf \; \
	| sort -u \
	| sed "s/^/${iBangChar}${iBangBang} /;s/^ *//" \
	>| ${iBangTemp}

# Send first option
# Also append it to the end of the list to allow for cycling through the list
sed 1q ${iBangTemp} | tee -a ${iBangTemp}

Added tab/term.



>
1
default