#include #include #include #include #include #include #include #include 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); } } }