Commit Diff


commit - /dev/null
commit + e573cb09321087dc40dc088a53a9e079a31ecfe5
blob - /dev/null
blob + e73266e856b84e6a78ec62202d12c963fe8525cf (mode 644)
--- /dev/null
+++ .gitignore
@@ -0,0 +1,7 @@
+TAGS
+lex.yy.c
+y.tab.c
+y.tab.h
+*.o
+*.tar.gz
+star-platinum
blob - /dev/null
blob + 37af6c334b108aebf1d314ab4e2986358cec9122 (mode 644)
--- /dev/null
+++ LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2020 Omar Polo <op@omarpolo.com>
+
+Permission to use, copy, modify, and 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.
blob - /dev/null
blob + c9661be9fd15f246901aa9ad08c9a83684fa1269 (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,36 @@
+include config.mk
+
+all: star-platinum TAGS
+
+.PHONY: all clean archive
+
+lex.yy.c: lex.l y.tab.c
+	${LEX} lex.l
+
+y.tab.c: parse.y
+	${YACC} -d parse.y
+
+OBJS = star-platinum.o lex.yy.o y.tab.o
+star-platinum: ${OBJS}
+	${CC} -o $@ ${OBJS} ${LDFLAGS}
+
+TAGS: star-platinum.c star-platinum.h parse.y lex.l
+	-${ETAGS} star-platinum.c star-platinum.h parse.y lex.l
+
+clean:
+	rm -f star-platinum ${OBJS} lex.yy.c y.tab.c y.tab.h y.output
+	rm -f star-platinum.tar.gz
+	rm -f TAGS
+
+SRC +=	Makefile config.mk lex.l parse.y star-platinum.c
+SRC +=	star-platinum.h star-platinum.1 star-platinum.conf.5
+star-platinum.tar.gz: ${SRC}
+	rm -f star-platinum.tar.gz
+	mkdir star-platinum-archive
+	cp ${SRC} ./star-platinum-archive
+	tar -czvf star-platinum.tar.gz \
+		-s '/^star-platinum-archive/star-platinum/gp' \
+		star-platinum-archive
+	rm -r star-platinum-archive
+
+archive: star-platinum.tar.gz
blob - /dev/null
blob + ad55b3892535095587bf543c20f62e6fdd4468d0 (mode 644)
--- /dev/null
+++ README.md
@@ -0,0 +1,34 @@
+# star platinum
+
+`star-platinum` is a key binding manager for x11.  It acts primarily
+as a translator: the canonical example is to Emacs-ify various
+programs by customizing how some key are handled.
+
+Check out the [manpage](star-platinum.1) for more information.
+
+## Building
+
+`star-platinum` depends on `xlib` and needs a C compiler, `yacc` and
+`lex` to compile.  With that in place, it's as easy as
+
+    make
+
+Configuration for the build process can be found in `config.mk`, but
+you usually don't need to modify it: passing the variables to make
+should be enough.  For instance, to build with `gcc`
+
+    make CC=gcc
+
+## FAQ
+
+ - *something something* bison *something something*
+
+   `star-platinum` should build with GNU `bison` and flex, but I've
+   still not tried.  `bison` has some defaults different from `yacc`
+   IIRC, so additional flags may be needed.
+
+ - the name is a jojo reference?
+
+   Sort of.  I was listening to 「sono chi kioku」, the fourth opening,
+   while I was playing with the idea of translating the key.  Given
+   that I'm generally bad at naming things...
blob - /dev/null
blob + 8ec174409aebdeb1a9e6838306712a6e199d1ad3 (mode 644)
--- /dev/null
+++ config.mk
@@ -0,0 +1,8 @@
+CC =		cc
+CFLAGS =	-Wall -g -I/usr/X11R6/include
+LDFLAGS =	-L/usr/X11R6/lib/ -lX11
+
+LEX =		lex
+YACC =		yacc
+
+ETAGS =		etags
blob - /dev/null
blob + b02ff034fe7ec6b73083b8f43f90c995c9d0ba9d (mode 644)
--- /dev/null
+++ lex.l
@@ -0,0 +1,112 @@
+/* -*- mode:fundamental; indent-tabs-mode: t; -*- */
+%{
+
+/*
+ * Copyright (c) 2020 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#include "star-platinum.h"
+
+#include <X11/Xlib.h>
+#include <X11/keysymdef.h>
+
+#include <err.h>
+#include <string.h>
+
+#include "y.tab.h"
+
+int state  = 0;
+KeySym key = 0;
+
+%}
+
+%x COMMENT
+%x KEY
+
+%%
+
+"#"		{ BEGIN(COMMENT); }
+<COMMENT>\n	{ yylineno++; BEGIN(INITIAL); }
+<COMMENT>.	;
+
+[ \t\r\v\f]+	;
+
+"match"		return TMATCH;
+"class"		return TCLASS;
+"on"		return TON;
+"do"		return TDO;
+"toggle"	return TTOGGLE;
+"activate"	return TACTIVATE;
+"deactivate"	return TDEACTIVATE;
+"ignore"	return TIGNORE;
+
+"\n"		yylineno++; return '\n';
+
+[-a-zA-Z0-9]+	{
+			char *ident;
+			if ((ident = strdup(yytext)) == NULL)
+				err(1, "strdup");
+			yylval.str = ident;
+			return TSTRING;
+		}
+
+"\""		{ BEGIN(KEY); }
+<KEY>"\""	{
+			BEGIN(INITIAL);
+
+			if (key == NoSymbol)
+				return TERR;
+
+			yylval.key = (struct key){ state, key };
+			state = 0;
+			key = 0;
+			return TKEY;
+		}
+
+<KEY>C-		{ state |= ControlMask; }
+<KEY>S-		{ state |= ShiftMask; }
+<KEY>M-		{ state |= Mod1Mask; }
+<KEY>s-		{ state |= Mod4Mask; }
+
+<KEY><[_a-zA-Z]+> {
+			char *c;
+
+			if ((c = strdup(yytext)) == NULL)
+                                err(1, "strdup");
+
+			c++;			/* skip the < */
+			c[strlen(c)-1] = '\0';	/* trim the > */
+
+			key = XStringToKeysym(c);
+
+			free(--c);
+		}
+
+<KEY>SPC	{ key = XK_space; }
+<KEY>RET	{ key = XK_Return; }
+
+<KEY>"("	{ key = XK_parenleft; }
+<KEY>")"	{ key = XK_parenright; }
+
+<KEY>.		{ key = XStringToKeysym(yytext); }
+
+
+%%
+
+int
+yywrap(void)
+{
+	return 1;
+}
blob - /dev/null
blob + 0c76c3c970710a5bb2ef5bcf34fb6faaaa8e2a8f (mode 644)
--- /dev/null
+++ parse.y
@@ -0,0 +1,91 @@
+/* -*- mode: fundamental; indent-tabs-mode: t; -*- */
+%{
+
+/*
+ * Copyright (c) 2020 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#include <stdio.h>
+
+#include <X11/Xlib.h>
+
+#include "star-platinum.h"
+
+void	 yyerror(const char*);
+
+/*
+ * #define YYDEBUG 1
+ * int yydebug = 1;
+ */
+
+#define SPECIAL(X) ((struct action){.type = (ASPECIAL), .special = (X) })
+#define FAKE_KEY(K) ((struct action){.type = (AFAKE), .send_key = (K) })
+
+%}
+ 
+%union {
+	struct key key;
+	char *str;
+	struct action action;
+	struct match *match;
+	struct rule *rule;
+	struct group *group;
+}
+
+%token TMATCH TCLASS
+%token TON TDO
+%token TTOGGLE TACTIVATE TDEACTIVATE TIGNORE
+%token TERR
+
+%token <key>	TKEY
+%token <str>	TSTRING
+
+%type <action>	action
+%type <match>	matches match
+%type <rule>	keys key
+%type <group>	groups group
+
+%%
+
+groups		: /* empty */		{ $$ = NULL; }
+		| groups group		{ $2->next = $1; config = $$ = $2; }
+		| error '\n'
+		;
+
+group		: matches keys		{ $$ = new_group($1, $2); }
+		;
+
+matches		: /* empty */		{ $$ = NULL; }
+		| matches '\n'		{ $$ = $1; }
+		| matches match '\n'	{ $2->next = $1; $$ = $2; }
+		;
+
+match		: TMATCH TCLASS TSTRING { $$ = new_match(MCLASS, $3); }
+		;
+
+keys		: /* empty */		{ $$ = NULL; }
+		| keys '\n'		{ $$ = $1; }
+		| keys key '\n'		{ $2->next = $1; $$ = $2; }
+		;
+
+key		: TON TKEY TDO action	{ $$ = new_rule($2, $4); }
+		;
+
+action		: TKEY			{ $$ = FAKE_KEY($1); }
+		| TTOGGLE		{ $$ = SPECIAL(ATOGGLE); }
+		| TACTIVATE		{ $$ = SPECIAL(AACTIVATE); }
+		| TDEACTIVATE		{ $$ = SPECIAL(ADEACTIVATE); }
+		| TIGNORE		{ $$ = SPECIAL(AIGNORE); }
+		;
blob - /dev/null
blob + 356d0c55e1a07747c5ca62cd17eb749c192d9e5f (mode 644)
--- /dev/null
+++ star-platinum.1
@@ -0,0 +1,56 @@
+.\" Copyright (c) 2020 Omar Polo <op@omarpolo.com>
+.\"
+.\" Permission to use, copy, modify, and 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.
+.Dd $Mdocdate: September 27 2020$
+.Dt STAR-PLATINUM 1
+.Os
+.Sh NAME
+.Nm star-platinum
+.Nd key translator for xorg
+.Sh SYNOPSIS
+.Nm
+.Bk -words
+.Op Fl dn
+.Op Fl c Ar config
+.Ek
+.Sh DESCRIPTION
+.Nm
+is an utility to translate keybindings for X11 programs.
+It acts by grabbing some keys, say control n, and send a fake events
+when you press that sequence.
+The fake event will be possibly a different key, depending on the
+application that had the focus.
+.Pp
+The options are as follows:
+.Bl -tag -width keyword
+.It Fl c Ar conf
+load the configuration from the given file instead of the default
+.It Fl d
+print the config (as parsed).
+Useful for debugging
+.It Fl n
+config test mode
+.El
+.Sh CONFIGURATION FILE
+If a configuration file is not given with the
+.Fl c
+option, the default behaviour is to search for
+.Pa $HOME/.star-platinum.conf
+or
+.Pa $XDG_CONFIG_HOME/star-platinum.conf
+(where
+.Pa $XDG_CONFIG_HOME
+if not defined is assumed
+.Pa $HOME/.config )
+.Sh SEE ALSO
+.Xr star-platinum.conf 5
blob - /dev/null
blob + 9db19aa52b32d963822a25e3b440f17cfad7e25c (mode 644)
--- /dev/null
+++ star-platinum.c
@@ -0,0 +1,568 @@
+/*
+ * Copyright (c) 2020 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#include "star-platinum.h"
+
+#include <err.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <X11/XKBlib.h>
+#include <X11/Xutil.h>
+
+#ifndef __OpenBSD__
+# define pledge(a, b) 0
+#endif
+
+extern FILE *yyin;
+char *fname;
+
+extern int yylineno;
+int yyparse(void);
+
+struct group *config;
+int goterror = 0;
+
+Display *d;
+
+int ignored_modifiers[] = {
+	0, 			/* no modifiers */
+	LockMask,		/* caps lock */
+	Mod2Mask,		/* num lock */
+	Mod3Mask,		/* scroll lock */
+	Mod5Mask,		/* ? */
+};
+
+#define IGNORE_MODIFIERS (LockMask | Mod2Mask | Mod3Mask | Mod5Mask)
+
+void
+yyerror(const char *e)
+{
+	goterror = 1;
+	fprintf(stderr, "%s:%d %s\n", fname, yylineno, e);
+}
+
+char *
+find_config()
+{
+	char *home, *xdg, *t;
+	int free_xdg = 0;
+
+	home = getenv("HOME");
+	xdg = getenv("XDG_CONFIG_HOME");
+
+	if (home == NULL && xdg == NULL)
+		return NULL;
+
+	/* file in home directory >>>>>> XDG shit */
+	if (home != NULL) {
+		if (asprintf(&t, "%s/.star-platinum.conf", home) == -1)
+			return NULL;
+		if (access(t, R_OK) == 0)
+			return t;
+
+		free(t);
+		/* continue to search */
+	}
+
+	/* sanitize XDG_CONFIG_HOME */
+	if (xdg == NULL) {
+		free_xdg = 1;
+		if (asprintf(&xdg, "%s/.config", home) == -1)
+			return NULL;
+	}
+
+	if (asprintf(&t, "%s/star-platinum.conf", xdg) == -1)
+		goto err;
+
+	if (access(t, R_OK) == 0)
+		return t;
+	free(t);
+
+err:
+	if (free_xdg)
+		free(xdg);
+
+	return NULL;
+}
+
+int
+main(int argc, char **argv)
+{
+	int status = 0, dump_config = 0, conftest = 0, ch;
+	struct group *g;
+	struct rule *r;
+	XEvent e;
+	Window root;
+
+	fname = NULL;
+	while ((ch = getopt(argc, argv, "c:dn")) != -1) {
+		switch (ch) {
+		case 'c':
+			free(fname);
+			if ((fname = strdup(optarg)) == NULL)
+				err(1, "strdup");
+			break;
+
+		case 'd':
+			dump_config = 1;
+			break;
+
+		case 'n':
+			conftest = 1;
+			break;
+
+		default:
+			fprintf(stderr, "USAGE: %s [-dn] [-c conf]\n", *argv);
+			return 1;
+		}
+	}
+
+	if (fname == NULL) {
+		if ((fname = find_config()) == NULL)
+			errx(1, "can't find a configuration file");
+	}
+
+	if ((yyin = fopen(fname, "r")) == NULL)
+		err(1, "cannot open %s", fname);
+	yyparse();
+	fclose(yyin);
+
+	free(fname);
+	fname = NULL;
+
+	if (goterror)
+                return 1;
+
+	if (dump_config)
+		printgroup(config);
+
+	if (conftest) {
+		recfree_group(config);
+		return 0;
+	}
+
+	if ((d = XOpenDisplay(NULL)) == NULL) {
+		recfree_group(config);
+		return 1;
+	}
+
+	root = DefaultRootWindow(d);
+
+	/* grab all the keys */
+	for (g = config; g != NULL; g = g->next)
+		for (r = g->rules; r != NULL; r = r->next)
+			grabkey(r->key);
+
+	XSelectInput(d, root, KeyPressMask);
+	XFlush(d);
+
+	pledge("stdio", "");
+
+	while (1) {
+		XNextEvent(d, &e);
+		switch (e.type) {
+		case KeyRelease:
+		case KeyPress:
+			process_event(config, (XKeyEvent*)&e);
+			break;
+
+		default:
+			printf("Unknown event %d\n", e.type);
+			break;
+		}
+	}
+
+	XCloseDisplay(d);
+	recfree_group(config);
+	return status;
+}
+
+
+/* xlib */
+
+/* TODO: it should grab ALL POSSIBLE COMBINATIONS of `ignored_modifiers`! */
+void
+grabkey(struct key k)
+{
+	static size_t len = sizeof(ignored_modifiers)/sizeof(int);
+	size_t i;
+	Window root;
+
+	root = DefaultRootWindow(d);
+
+	/* printf("Grabbing "); printkey(k); printf("\n"); */
+	for (i = 0; i < len; ++i) {
+                XGrabKey(d, XKeysymToKeycode(d, k.key),
+		    k.modifier | ignored_modifiers[i],
+		    root, False, GrabModeAsync, GrabModeAsync);
+	}
+}
+
+KeySym
+keycode_to_keysym(unsigned int kc)
+{
+	/* group 0 (?). shift level is 0 because we don't want it*/
+	return XkbKeycodeToKeysym(d, kc, 0, 0);
+}
+
+Window
+focused_window()
+{
+	Window w;
+	int revert_to;
+
+	/* one can use (at least) three way to obtain the current
+	 * focused window using xlib:
+	 *
+	 * - XQueryTree : you traverse tre tree until you find the
+	 *     window
+	 *
+	 * - looking at _NET_ACTIVE_WINDOW in the root window, but
+	 *    depedns on the window manager to set it
+	 *
+	 * - using XGetInputFocus
+	 *
+	 * I don't know the pro/cons of these, but XGetInputFocus
+	 * seems the easiest.
+	 */
+	XGetInputFocus(d, &w, &revert_to);
+	return w;
+}
+
+void
+send_fake(Window w, struct key k, int pressed)
+{
+	XKeyEvent e;
+
+	e.type = pressed ? KeyPress : KeyRelease;
+
+	e.display = d;
+	e.window = w;
+	e.root = DefaultRootWindow(d);
+	e.subwindow = None;
+	e.time = CurrentTime;
+
+	/* TODO: fix these */
+	e.x = 1;
+	e.y = 1;
+	e.x_root = 1;
+	e.y_root = 1;
+
+	e.same_screen = True;
+	e.keycode = XKeysymToKeycode(d, k.key);
+	e.state = k.modifier;
+
+	XSendEvent(d, w, True, KeyPressMask, (XEvent*)&e);
+	XFlush(d);
+}
+
+int
+window_match_class(Window w, const char *class)
+{
+	XClassHint ch;
+	int matched;
+
+        if (!XGetClassHint(d, w, &ch)) {
+		fprintf(stderr, "XGetClassHint failed\n");
+		return 0;
+	}
+
+	matched = !strcmp(ch.res_class, class);
+
+	XFree(ch.res_name);
+	XFree(ch.res_class);
+
+	return matched;
+}
+
+
+/* action */
+
+void
+do_action(struct action a, Window focused, int pressed)
+{
+	switch (a.type) {
+	case AFAKE:
+		send_fake(focused, a.send_key, pressed);
+		break;
+
+	case ASPECIAL:
+		switch (a.special) {
+		case ATOGGLE:
+		case AACTIVATE:
+		case ADEACTIVATE:
+			printf("TODO\n");
+			break;
+
+		case AIGNORE:
+			break;
+
+		default:
+			/* unreachable */
+			abort();
+		}
+                break;
+
+	default:
+		/* unreachable */
+		abort();
+	}
+}
+
+
+/* match */
+
+struct match *
+new_match(int prop, char *s)
+{
+	struct match *m;
+
+	if ((m = calloc(1, sizeof(*m))) == NULL)
+		err(1, "calloc");
+	m->prop = prop;
+	m->str = s;
+	return m;
+}
+
+void
+recfree_match(struct match *m)
+{
+	struct match *mt;
+
+	if (m == NULL)
+		return;
+
+	mt = m->next;
+	free(m->str);
+	free(m);
+	recfree_match(mt);
+}
+
+int
+match_window(struct match *m, Window w)
+{
+        switch (m->prop) {
+	case MCLASS:
+		return window_match_class(w, m->str);
+		break;
+
+	default:
+		/* unreachable */
+		abort();
+	}
+}
+
+
+/* rule */
+
+struct rule *
+new_rule(struct key k, struct action a)
+{
+	struct rule *r;
+
+	if ((r = calloc(1, sizeof(*r))) == NULL)
+		err(1, "calloc");
+	memcpy(&r->key, &k, sizeof(k));
+	memcpy(&r->action, &a, sizeof(a));
+	return r;
+}
+
+void
+recfree_rule(struct rule *r)
+{
+	struct rule *rt;
+
+	if (r == NULL)
+		return;
+
+	rt = r->next;
+	free(r);
+	recfree_rule(rt);
+}
+
+int
+rule_matched(struct rule *r, struct key k)
+{
+	unsigned int m;
+
+	m = k.modifier;
+	m &= ~IGNORE_MODIFIERS; /* clear ignored modifiers */
+
+	return r->key.modifier == m
+		&& r->key.key == k.key;
+}
+
+
+/* group */
+
+struct group *
+new_group(struct match *matches, struct rule *rules)
+{
+	struct group *g;
+
+	if ((g = calloc(1, sizeof(*g))) == NULL)
+		err(1, "calloc");
+	g->matches = matches;
+	g->rules = rules;
+	return g;
+}
+
+void
+recfree_group(struct group *g)
+{
+	struct group *gt;
+
+	if (g == NULL)
+		return;
+
+	gt = g->next;
+	recfree_match(g->matches);
+	recfree_rule(g->rules);
+	free(g);
+	recfree_group(gt);
+}
+
+void
+process_event(struct group *g, XKeyEvent *e)
+{
+	Window focused;
+	struct rule *r;
+	struct key pressed = {
+		.modifier = e->state,
+		.key = keycode_to_keysym(e->keycode),
+	};
+
+	focused = focused_window();
+
+	for (; g != NULL; g = g->next) {
+		if (!group_match(g, focused))
+			continue;
+
+		for (r = g->rules; r != NULL; r = r->next) {
+			if (rule_matched(r, pressed)) {
+                                do_action(r->action, focused, e->type == KeyPress);
+				return;
+			}
+		}
+	}
+
+	send_fake(focused, pressed, e->type == KeyPress);
+}
+
+int
+group_match(struct group *g, Window w)
+{
+	struct match *m;
+
+	for (m = g->matches; m != NULL; m = m->next)
+		if (match_window(m, w))
+			return 1;
+	return 0;
+}
+
+
+/* debug/dump stuff */
+
+void
+printkey(struct key k)
+{
+	if (k.modifier & ControlMask)
+		printf("C-");
+	if (k.modifier & ShiftMask)
+		printf("S-");
+	if (k.modifier & Mod1Mask)
+		printf("M-");
+	if (k.modifier & Mod4Mask)
+		printf("s-");
+
+	printf("%s", XKeysymToString(k.key));
+}
+
+void
+printaction(struct action a)
+{
+	if (a.type == ASPECIAL) {
+		switch (a.special) {
+		case ATOGGLE: printf("toggle"); break;
+		case AACTIVATE: printf("activate"); break;
+		case ADEACTIVATE: printf("deactivate"); break;
+		case AIGNORE: printf("ignore"); break;
+		}
+	} else {
+		printf("send key ");
+		printkey(a.send_key);
+	}
+}
+
+void
+printmatch(struct match *m)
+{
+	if (m == NULL) {
+		printf("(null)");
+		return;
+	}
+	printf("match ");
+	switch (m->prop) {
+	case MCLASS:
+		printf("class");
+		break;
+	default:
+		abort();
+	}
+	printf(" %s", m->str);
+
+	if (m->next == NULL)
+		printf("\n");
+	else {
+		printf(" ; ");
+		printmatch(m->next);
+	}
+}
+
+void
+printrule(struct rule *r)
+{
+	if (r == NULL) {
+		printf("(null)");
+		return;
+	}
+	printf("on ");
+	printkey(r->key);
+	printf(" do ");
+	printaction(r->action);
+	printf("\n");
+
+	if (r->next != NULL)
+		printrule(r->next);
+}
+
+void
+printgroup(struct group *g)
+{
+	if (g == NULL) {
+		printf("(null)");
+		return;
+	}
+	printmatch(g->matches);
+	printf("\n");
+	printrule(g->rules);
+	printf("\n\n");
+
+	if (g->next != NULL)
+		printgroup(g->next);
+}
blob - /dev/null
blob + 3352960340b4db625d7226c68a4612bc101bcf72 (mode 644)
--- /dev/null
+++ star-platinum.conf.5
@@ -0,0 +1,83 @@
+.\" Copyright (c) 2020 Omar Polo <op@omarpolo.com>
+.\"
+.\" Permission to use, copy, modify, and 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.
+.Dd $Mdocdate: September 27 2020$
+.Dt STAR-PLATINUM.CONF 5
+.Os
+.Sh NAME
+.Nm star-platinum.conf
+.Nd star-platinum configuration file
+.Sh DESCRIPTION
+.Nm
+is the configuration file for the
+.Xr star-platinum 1
+program.
+.Pp
+Empty lines are ignored, and comments too.
+Comments starts with a # sign and continue until the end of the line
+and can be placed everywhere in the configuration file.
+.Pp
+The configuration file is made of blocks.
+Every block starts with one or more
+.Ic match
+directives and continues with several keybinding directives.
+If more than a single
+.Ic match
+directive is given, the keybinding will be used if at least one of the
+.Ic match
+directives matches the window.
+That is, if both match class Firefox and match class Chromium is
+given, the directives will be available for both Firefox and Chromium
+windows.
+.Pp
+A keybinding directive is made of the
+.Ic on
+keyword followed by a keybinding, followed by the
+.Ic do
+keyword and an action.
+The action can be another keybinding, or a special internal command.
+.Pp
+The syntax for the keybindings is inspired
+.Xr emacs 1 .
+A keybindings is written within double quotes and is made of modifiers
+followed by the key.
+Modifiers follows the
+.Xr emacs 1
+notation of using C- to mean control, S- for shift, M- for alt (mod1)
+and s- for super (mod4).
+The key can be either a plain letter (e.g. x) or the name of the key
+written within angular brackets (e.g. <Down>).
+.Pp
+The only internal command available now is
+.Ic ignore Ns : it's a no-op.
+.Sh EXAMPLES
+The following is an example of configuration file that binds some
+.Xr emacs 1 Ns -esque keys for both Firefox and Chromium:
+.Bd -literal -offset indent
+match class Firefox
+match class Chromium-browser
+on "C-s" do "C-f"
+
+# clipboard
+on "C-w" do "C-x"
+on "M-w" do "C-c"
+on "C-y" do "C-v"
+
+# movements
+on "C-n" do "<Down>"
+on "C-p" do "<Up>"
+on "C-f" do "<Right>"
+on "C-b" do "<Left>"
+
+on "C-(" do ignore
+.Ed
\ No newline at end of file
blob - /dev/null
blob + 445302b1c862912606adc693d2f237a3a02060b3 (mode 644)
--- /dev/null
+++ star-platinum.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2020 Omar Polo <op@omarpolo.com>
+ *
+ * Permission to use, copy, modify, and 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 STAR_PLATINUM_H
+#define STAR_PLATINUM_H
+
+/* XXX: why I need to define this to get XK_space/...?
+ * is this a bad idea?  there is another way? */
+#define XK_LATIN1
+#define XK_MISCELLANY
+
+#include <X11/Xlib.h>
+
+#include <err.h>
+#include <stdlib.h>
+
+struct key {
+	unsigned int modifier;
+	KeySym key;
+};
+
+struct action {
+#define ASPECIAL	1
+#define AFAKE		2
+	int type;
+	union {
+#define ATOGGLE		3
+#define AACTIVATE	4
+#define ADEACTIVATE	5
+#define AIGNORE		6
+		int special;
+		struct key send_key;
+	};
+};
+
+void			 do_action(struct action, Window, int);
+
+struct match {
+#define MCLASS		1
+	int prop;
+	char *str;
+	struct match *next;
+};
+
+struct match		*new_match(int, char*);
+void			 recfree_match(struct match*);
+int			 match_window(struct match*, Window);
+
+struct rule {
+	struct key key;
+	struct action action;
+	struct rule *next;
+};
+
+struct rule		*new_rule(struct key, struct action);
+void			 recfree_rule(struct rule*);
+int			 rule_matched(struct rule*, struct key);
+
+struct group {
+	struct match *matches;
+	struct rule *rules;
+	struct group *next;
+};
+
+extern struct group *config;
+
+struct group		*new_group(struct match*, struct rule*);
+void			 recfree_group(struct group*);
+void			 process_event(struct group*, XKeyEvent*);
+int			 group_match(struct group*, Window);
+
+/* xlib-related */
+void			 grabkey(struct key);
+KeySym			 keycode_to_keysym(unsigned int);
+Window			 focused_window();
+void			 send_fake(Window, struct key, int);
+int			 window_match_class(Window, const char*);
+
+/* debugging */
+void			 printkey(struct key);
+void			 printaction(struct action);
+void			 printmatch(struct match*);
+void			 printrule(struct rule*);
+void			 printgroup(struct group*);
+
+#endif /* STAR_PLATINUM_H */