diff
Makefile | 12 +++
README.md | 1 +
hkd.c | 277 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
hkdrc.example | 20 +++++
4 files changed, 310 insertions(+)
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e543ad9
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,12 @@
+CFLAGS = -std=c89 -Wall -Wextra -Werror -pedantic
+CPPFLAGS = -I/usr/X11R6/include
+LDFLAGS = -L/usr/X11R6/lib
+LDLIBS = -lX11
+
+all: hkd
+
+hkd: hkd.c
+ cc $(CPPFLAGS) $(CFLAGS) -o hkd hkd.c $(LDFLAGS) $(LDLIBS)
+
+clean:
+ rm -f hkd
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9f099b6
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+hot key daemon
diff --git a/hkd.c b/hkd.c
new file mode 100644
index 0000000..dcb3540
--- /dev/null
+++ b/hkd.c
@@ -0,0 +1,277 @@
+#include <X11/Xlib.h>
+#include <X11/keysym.h>
+
+#include <ctype.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+enum {
+ MAX_LINE = 2048
+};
+
+typedef struct {
+ unsigned int mod;
+ KeyCode code;
+ char *cmd;
+} binding;
+
+static unsigned int numlock_mask;
+
+static char *
+trim(char *s)
+{
+ char *e;
+
+ while (*s != '\0' && isspace((unsigned char)*s))
+ s++;
+ e = s + strlen(s);
+ while (e > s && isspace((unsigned char)e[-1]))
+ *--e = '\0';
+ return s;
+}
+
+static int
+x_strdup(char **out, const char *s)
+{
+ size_t n;
+ char *p;
+
+ n = strlen(s) + 1;
+ p = malloc(n);
+ if (p == NULL)
+ return 0;
+ memcpy(p, s, n);
+ *out = p;
+ return 1;
+}
+
+static int
+add_binding(binding **b, int *n, unsigned int mod, KeyCode code, const char *cmd)
+{
+ binding *nb;
+
+ nb = realloc(*b, (size_t)(*n + 1) * sizeof(*nb));
+ if (nb == NULL)
+ return 0;
+ *b = nb;
+ nb[*n].mod = mod;
+ nb[*n].code = code;
+ if (!x_strdup(&nb[*n].cmd, cmd))
+ return 0;
+ (*n)++;
+ return 1;
+}
+
+static int
+mod_from_token(const char *tok, unsigned int *mod)
+{
+ if (!strcmp(tok, "Shift"))
+ *mod |= ShiftMask;
+ else if (!strcmp(tok, "Control") || !strcmp(tok, "Ctrl"))
+ *mod |= ControlMask;
+ else if (!strcmp(tok, "Alt") || !strcmp(tok, "Mod1"))
+ *mod |= Mod1Mask;
+ else if (!strcmp(tok, "Mod2"))
+ *mod |= Mod2Mask;
+ else if (!strcmp(tok, "Mod3"))
+ *mod |= Mod3Mask;
+ else if (!strcmp(tok, "Super") || !strcmp(tok, "Mod4"))
+ *mod |= Mod4Mask;
+ else if (!strcmp(tok, "Mod5"))
+ *mod |= Mod5Mask;
+ else
+ return 0;
+ return 1;
+}
+
+static int
+parse_combo(Display *dpy, char *combo, unsigned int *mod, KeyCode *code)
+{
+ char *p, *tok, *key;
+ KeySym ks;
+
+ *mod = 0;
+ key = NULL;
+ for (tok = strtok(combo, "+"); tok != NULL; tok = strtok(NULL, "+")) {
+ tok = trim(tok);
+ if (*tok == '\0')
+ return 0;
+ if (mod_from_token(tok, mod))
+ continue;
+ key = tok;
+ }
+ if (key == NULL)
+ return 0;
+ for (p = key; *p != '\0'; p++)
+ if ((unsigned char)*p == ' ' || (unsigned char)*p == '\t')
+ return 0;
+ ks = XStringToKeysym(key);
+ if (ks == NoSymbol)
+ return 0;
+ *code = XKeysymToKeycode(dpy, ks);
+ return *code != 0;
+}
+
+static void
+find_numlock_mask(Display *dpy)
+{
+ XModifierKeymap *map;
+ KeyCode nl;
+ int i, j;
+
+ nl = XKeysymToKeycode(dpy, XK_Num_Lock);
+ numlock_mask = 0;
+ map = XGetModifierMapping(dpy);
+ if (map == NULL)
+ return;
+ for (i = 0; i < 8; i++) {
+ for (j = 0; j < map->max_keypermod; j++) {
+ if (map->modifiermap[i * map->max_keypermod + j] == nl)
+ numlock_mask = (unsigned int)(1U << i);
+ }
+ }
+ XFreeModifiermap(map);
+}
+
+static void
+grab(Display *dpy, Window root, binding *b)
+{
+ unsigned int masks[4];
+ int i;
+
+ masks[0] = b->mod;
+ masks[1] = b->mod | LockMask;
+ masks[2] = b->mod | numlock_mask;
+ masks[3] = b->mod | numlock_mask | LockMask;
+ for (i = 0; i < 4; i++)
+ XGrabKey(dpy, b->code, masks[i], root, True, GrabModeAsync,
+ GrabModeAsync);
+}
+
+static unsigned int
+clean_mask(unsigned int m)
+{
+ return m & ~(LockMask | numlock_mask);
+}
+
+static void
+run(const char *cmd)
+{
+ if (fork() == 0) {
+ execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
+ _exit(127);
+ }
+}
+
+static int
+load_bindings(Display *dpy, const char *path, binding **out, int *out_n)
+{
+ FILE *f;
+ binding *b;
+ int n;
+ char line[MAX_LINE];
+ int line_no;
+
+ f = fopen(path, "r");
+ if (f == NULL)
+ return 0;
+ b = NULL;
+ n = 0;
+ line_no = 0;
+ while (fgets(line, sizeof(line), f) != NULL) {
+ char *p, *combo, *cmd, *sep;
+ unsigned int mod;
+ KeyCode code;
+
+ line_no++;
+ p = trim(line);
+ if (*p == '\0' || *p == '#')
+ continue;
+ sep = p;
+ while (*sep != '\0' && !isspace((unsigned char)*sep))
+ sep++;
+ if (*sep == '\0') {
+ fprintf(stderr, "hkd: %s:%d missing command\n", path, line_no);
+ continue;
+ }
+ *sep++ = '\0';
+ combo = trim(p);
+ cmd = trim(sep);
+ if (*cmd == '\0') {
+ fprintf(stderr, "hkd: %s:%d missing command\n", path, line_no);
+ continue;
+ }
+ if (!parse_combo(dpy, combo, &mod, &code)) {
+ fprintf(stderr, "hkd: %s:%d bad combo '%s'\n", path, line_no,
+ combo);
+ continue;
+ }
+ if (!add_binding(&b, &n, mod, code, cmd)) {
+ fclose(f);
+ return 0;
+ }
+ }
+ fclose(f);
+ *out = b;
+ *out_n = n;
+ return 1;
+}
+
+int
+main(int argc, char **argv)
+{
+ Display *dpy;
+ Window root;
+ binding *b;
+ XEvent ev;
+ int i, n;
+ char path[MAX_LINE];
+ const char *cfg;
+
+ signal(SIGCHLD, SIG_IGN);
+ dpy = XOpenDisplay(NULL);
+ if (dpy == NULL) {
+ fprintf(stderr, "hkd: cannot open display\n");
+ return 1;
+ }
+ root = DefaultRootWindow(dpy);
+ if (argc > 1) {
+ cfg = argv[1];
+ } else {
+ const char *home;
+
+ home = getenv("HOME");
+ if (home == NULL)
+ home = ".";
+ snprintf(path, sizeof(path), "%s/.hkdrc", home);
+ cfg = path;
+ }
+ if (!load_bindings(dpy, cfg, &b, &n)) {
+ fprintf(stderr, "hkd: cannot load %s\n", cfg);
+ XCloseDisplay(dpy);
+ return 1;
+ }
+ if (n == 0) {
+ fprintf(stderr, "hkd: no bindings in %s\n", cfg);
+ XCloseDisplay(dpy);
+ return 1;
+ }
+ find_numlock_mask(dpy);
+ for (i = 0; i < n; i++)
+ grab(dpy, root, &b[i]);
+ XSync(dpy, False);
+
+ for (;;) {
+ XNextEvent(dpy, &ev);
+ if (ev.type != KeyPress)
+ continue;
+ for (i = 0; i < n; i++) {
+ if (ev.xkey.keycode == b[i].code &&
+ clean_mask(ev.xkey.state) == b[i].mod)
+ run(b[i].cmd);
+ }
+ }
+}
diff --git a/hkdrc.example b/hkdrc.example
new file mode 100644
index 0000000..5df5873
--- /dev/null
+++ b/hkdrc.example
@@ -0,0 +1,20 @@
+Alt+Return st
+Super+d dmenu_run
+Alt+1 wmc 1
+Alt+2 wmc 2
+Alt+3 wmc 3
+Alt+4 wmc 4
+Alt+5 wmc 5
+Alt+6 wmc 6
+Alt+7 wmc 7
+Alt+8 wmc 8
+Alt+9 wmc 9
+Alt+Shift+1 wmc move 1
+Alt+Shift+2 wmc move 2
+Alt+Shift+3 wmc move 3
+Alt+Shift+4 wmc move 4
+Alt+Shift+5 wmc move 5
+Alt+Shift+6 wmc move 6
+Alt+Shift+7 wmc move 7
+Alt+Shift+8 wmc move 8
+Alt+Shift+9 wmc move 9