aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOmar Polo <op@omarpolo.com>2020-09-27 23:09:15 +0200
committerOmar Polo <op@omarpolo.com>2020-09-27 23:09:15 +0200
commite573cb09321087dc40dc088a53a9e079a31ecfe5 (patch)
treeeb9ef20d4da337c7bff2435d0c2ceca9eac7be93
downloadstar-platinum-e573cb09321087dc40dc088a53a9e079a31ecfe5.tar.gz
star-platinum-e573cb09321087dc40dc088a53a9e079a31ecfe5.tar.bz2
initial commit
-rw-r--r--.gitignore7
-rw-r--r--LICENSE13
-rw-r--r--Makefile36
-rw-r--r--README.md34
-rw-r--r--config.mk8
-rw-r--r--lex.l112
-rw-r--r--parse.y91
-rw-r--r--star-platinum.156
-rw-r--r--star-platinum.c568
-rw-r--r--star-platinum.conf.583
-rw-r--r--star-platinum.h99
11 files changed, 1107 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e73266e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+TAGS
+lex.yy.c
+y.tab.c
+y.tab.h
+*.o
+*.tar.gz
+star-platinum
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37af6c3
--- /dev/null
+++ b/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.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..c9661be
--- /dev/null
+++ b/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
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ad55b38
--- /dev/null
+++ b/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...
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..8ec1744
--- /dev/null
+++ b/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
diff --git a/lex.l b/lex.l
new file mode 100644
index 0000000..b02ff03
--- /dev/null
+++ b/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;
+}
diff --git a/parse.y b/parse.y
new file mode 100644
index 0000000..0c76c3c
--- /dev/null
+++ b/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); }
+ ;
diff --git a/star-platinum.1 b/star-platinum.1
new file mode 100644
index 0000000..356d0c5
--- /dev/null
+++ b/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
diff --git a/star-platinum.c b/star-platinum.c
new file mode 100644
index 0000000..9db19aa
--- /dev/null
+++ b/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);
+}
diff --git a/star-platinum.conf.5 b/star-platinum.conf.5
new file mode 100644
index 0000000..3352960
--- /dev/null
+++ b/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
diff --git a/star-platinum.h b/star-platinum.h
new file mode 100644
index 0000000..445302b
--- /dev/null
+++ b/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 */