Blob


1 /*
2 * Copyright (c) 2020 Omar Polo <op@omarpolo.com>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include "star-platinum.h"
19 #include <err.h>
20 #include <signal.h>
21 #include <stdarg.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <unistd.h>
26 #include <X11/XKBlib.h>
27 #include <X11/Xutil.h>
29 #ifndef __OpenBSD__
30 # define pledge(a, b) 0
31 #endif
33 extern FILE *yyin;
34 char *fname;
36 extern int yylineno;
37 int yyparse(void);
39 struct group *config;
40 int goterror = 0;
42 Display *d;
44 int ignored_modifiers[] = {
45 0, /* no modifiers */
46 LockMask, /* caps lock */
47 Mod2Mask, /* num lock */
48 Mod3Mask, /* scroll lock */
49 Mod5Mask, /* ? */
50 };
52 #define IGNORE_MODIFIERS (LockMask | Mod2Mask | Mod3Mask | Mod5Mask)
54 __attribute__((__format__ (__printf__, 1, 2)))
55 void
56 yyerror(const char *fmt, ...)
57 {
58 va_list ap;
59 size_t l;
61 va_start(ap, fmt);
63 goterror = 1;
64 fprintf(stderr, "%s:%d ", fname, yylineno);
65 vfprintf(stderr, fmt, ap);
67 l = strlen(fmt);
68 if (l > 0 && fmt[l-1] != '\n')
69 fprintf(stderr, "\n");
71 va_end(ap);
72 }
74 char *
75 find_config()
76 {
77 char *home, *xdg, *t;
78 int free_xdg = 0;
80 home = getenv("HOME");
81 xdg = getenv("XDG_CONFIG_HOME");
83 if (home == NULL && xdg == NULL)
84 return NULL;
86 /* file in home directory >>>>>> XDG shit */
87 if (home != NULL) {
88 if (asprintf(&t, "%s/.star-platinum.conf", home) == -1)
89 return NULL;
90 if (access(t, R_OK) == 0)
91 return t;
93 free(t);
94 /* continue to search */
95 }
97 /* sanitize XDG_CONFIG_HOME */
98 if (xdg == NULL) {
99 free_xdg = 1;
100 if (asprintf(&xdg, "%s/.config", home) == -1)
101 return NULL;
104 if (asprintf(&t, "%s/star-platinum.conf", xdg) == -1)
105 goto err;
107 if (access(t, R_OK) == 0)
108 return t;
109 free(t);
111 err:
112 if (free_xdg)
113 free(xdg);
115 return NULL;
118 int
119 main(int argc, char **argv)
121 int status = 0, dump_config = 0, conftest = 0, ch;
122 XEvent e;
123 Window root;
125 signal(SIGCHLD, SIG_IGN); /* don't allow zombies */
127 fname = NULL;
128 while ((ch = getopt(argc, argv, "c:dn")) != -1) {
129 switch (ch) {
130 case 'c':
131 free(fname);
132 if ((fname = strdup(optarg)) == NULL)
133 err(1, "strdup");
134 break;
136 case 'd':
137 dump_config = 1;
138 break;
140 case 'n':
141 conftest = 1;
142 break;
144 default:
145 fprintf(stderr, "USAGE: %s [-dn] [-c conf]\n", *argv);
146 return 1;
150 if (fname == NULL) {
151 if ((fname = find_config()) == NULL)
152 errx(1, "can't find a configuration file");
155 if ((yyin = fopen(fname, "r")) == NULL)
156 err(1, "cannot open %s", fname);
157 yyparse();
158 fclose(yyin);
160 free(fname);
161 fname = NULL;
163 if (goterror)
164 return 1;
166 if (dump_config)
167 printgroup(config);
169 if (conftest) {
170 recfree_group(config);
171 return 0;
174 if ((d = XOpenDisplay(NULL)) == NULL) {
175 recfree_group(config);
176 return 1;
179 XSetErrorHandler(&error_handler);
181 root = DefaultRootWindow(d);
183 grabkey_matching_windows();
185 XSelectInput(d, root, SubstructureNotifyMask | KeyPressMask);
186 XFlush(d);
188 pledge("stdio proc exec", NULL);
190 while (1) {
191 XNextEvent(d, &e);
192 switch (e.type) {
193 case KeyRelease:
194 case KeyPress:
195 process_event(config, (XKeyEvent*)&e);
196 break;
198 case MapNotify: {
199 XMapEvent *ev = (XMapEvent*)&e;
200 grab_matching_keys(ev->window);
201 break;
204 case UnmapNotify:
205 case ConfigureNotify:
206 case CreateNotify:
207 case DestroyNotify:
208 case ClientMessage:
209 /* ignored */
210 break;
212 default:
213 printf("Unknown event %d\n", e.type);
214 break;
218 XCloseDisplay(d);
219 recfree_group(config);
220 return status;
224 /* xlib */
226 int
227 error_handler(Display *d, XErrorEvent *e)
229 fprintf(stderr, "Xlib error %d\n", e->type);
230 return 1;
233 /* TODO: it should grab ALL POSSIBLE COMBINATIONS of `ignored_modifiers`! */
234 void
235 grabkey(struct key k, Window w)
237 static size_t len = sizeof(ignored_modifiers)/sizeof(int);
238 size_t i;
240 /* printf("Grabbing "); printkey(k); printf("\n"); */
241 for (i = 0; i < len; ++i) {
242 XGrabKey(d, XKeysymToKeycode(d, k.key),
243 k.modifier | ignored_modifiers[i],
244 w, False, GrabModeAsync, GrabModeAsync);
248 void
249 grab_matching_keys(Window w)
251 XClassHint ch;
252 struct group *g;
253 struct rule *r;
255 if (!XGetClassHint(d, w, &ch))
256 return;
258 for (g = config; g != NULL; g = g->next) {
259 if (!group_match(g, w))
260 continue;
262 for (r = g->rules; r != NULL; r = r->next)
263 grabkey(r->key, w);
267 int
268 grabkey_matching_windows()
270 Window root, parent, *children;
271 unsigned int len, i;
273 root = DefaultRootWindow(d);
274 if (!XQueryTree(d, root, &root, &parent, &children, &len))
275 return 0;
277 for (i = 0; i < len; ++i)
278 grab_matching_keys(children[i]);
280 return 1;
283 KeySym
284 keycode_to_keysym(unsigned int kc)
286 /* group 0 (?). shift level is 0 because we don't want it*/
287 return XkbKeycodeToKeysym(d, kc, 0, 0);
290 Window
291 focused_window()
293 Window w;
294 int revert_to;
296 /* one can use (at least) three way to obtain the current
297 * focused window using xlib:
299 * - XQueryTree : you traverse tre tree until you find the
300 * window
302 * - looking at _NET_ACTIVE_WINDOW in the root window, but
303 * depedns on the window manager to set it
305 * - using XGetInputFocus
307 * I don't know the pro/cons of these, but XGetInputFocus
308 * seems the easiest.
309 */
310 XGetInputFocus(d, &w, &revert_to);
311 return w;
314 void
315 send_fake(Window w, struct key k, XKeyEvent *original)
317 XKeyEvent e;
319 /*
320 * this needs to be hijacked. original->window is the root
321 * window (since we grabbed the key there) and we want to
322 * deliver the key to another window.
323 */
324 e.window = w;
326 /* this is the fake key */
327 e.keycode = XKeysymToKeycode(d, k.key);
328 e.state = k.modifier;
330 /* the rest is just copying fields from the original event */
331 e.type = original->type;
332 e.display = original->display;
333 e.root = original->root;
334 e.subwindow = original->subwindow;
335 e.time = original->time;
336 e.same_screen = original->same_screen;
337 e.x = original->x;
338 e.y = original->y;
339 e.x_root = original->x_root;
340 e.y_root = original->y_root;
342 XSendEvent(d, w, True, KeyPressMask, (XEvent*)&e);
343 XFlush(d);
346 int
347 window_match_class(Window w, const char *class)
349 XClassHint ch;
350 int matched;
352 if (!XGetClassHint(d, w, &ch)) {
353 fprintf(stderr, "XGetClassHint failed\n");
354 return 0;
357 matched = !strcmp(ch.res_class, class);
359 XFree(ch.res_name);
360 XFree(ch.res_class);
362 return matched;
366 /* action */
368 void
369 do_action(struct action a, Window focused, XKeyEvent *original)
371 switch (a.type) {
372 case AFAKE:
373 send_fake(focused, a.send_key, original);
374 break;
376 case ASPECIAL:
377 switch (a.special) {
378 case ATOGGLE:
379 case AACTIVATE:
380 case ADEACTIVATE:
381 printf("TODO\n");
382 break;
384 case AIGNORE:
385 break;
387 default:
388 /* unreachable */
389 abort();
391 break;
393 case AEXEC: {
394 pid_t p;
395 const char *sh;
397 /* exec only on key press */
398 if (original->type == KeyRelease)
399 break;
401 switch (p = fork()) {
402 case -1:
403 err(1, "fork");
405 case 0:
406 if ((sh = getenv("SHELL")) == NULL)
407 sh = "/bin/sh";
408 printf("before exec'ing %s -c '%s'\n", sh, a.str);
409 execlp(sh, sh, "-c", a.str, NULL);
410 err(1, "execlp");
413 break;
416 default:
417 /* unreachable */
418 abort();
422 void
423 free_action(struct action a)
425 if (a.type == AEXEC)
426 free(a.str);
430 /* match */
432 struct match *
433 new_match(int prop, char *s)
435 struct match *m;
437 if ((m = calloc(1, sizeof(*m))) == NULL)
438 err(1, "calloc");
439 m->prop = prop;
440 m->str = s;
441 return m;
444 void
445 recfree_match(struct match *m)
447 struct match *mt;
449 if (m == NULL)
450 return;
452 mt = m->next;
453 free(m->str);
454 free(m);
455 recfree_match(mt);
458 int
459 match_window(struct match *m, Window w)
461 switch (m->prop) {
462 case MANY:
463 return 1;
465 case MCLASS:
466 return window_match_class(w, m->str);
467 break;
469 default:
470 /* unreachable */
471 abort();
476 /* rule */
478 struct rule *
479 new_rule(struct key k, struct action a)
481 struct rule *r;
483 if ((r = calloc(1, sizeof(*r))) == NULL)
484 err(1, "calloc");
485 memcpy(&r->key, &k, sizeof(k));
486 memcpy(&r->action, &a, sizeof(a));
487 return r;
490 void
491 recfree_rule(struct rule *r)
493 struct rule *rt;
495 if (r == NULL)
496 return;
498 rt = r->next;
499 free(r);
500 recfree_rule(rt);
503 int
504 rule_matched(struct rule *r, struct key k)
506 unsigned int m;
508 m = k.modifier;
509 m &= ~IGNORE_MODIFIERS; /* clear ignored modifiers */
511 return r->key.modifier == m
512 && r->key.key == k.key;
516 /* group */
518 struct group *
519 new_group(struct match *matches, struct rule *rules)
521 struct group *g;
523 if ((g = calloc(1, sizeof(*g))) == NULL)
524 err(1, "calloc");
525 g->matches = matches;
526 g->rules = rules;
527 return g;
530 void
531 recfree_group(struct group *g)
533 struct group *gt;
535 if (g == NULL)
536 return;
538 gt = g->next;
539 recfree_match(g->matches);
540 recfree_rule(g->rules);
541 free(g);
542 recfree_group(gt);
545 void
546 process_event(struct group *g, XKeyEvent *e)
548 Window focused;
549 struct rule *r;
550 struct key pressed = {
551 .modifier = e->state,
552 .key = keycode_to_keysym(e->keycode),
553 };
555 focused = focused_window();
557 for (; g != NULL; g = g->next) {
558 if (!group_match(g, focused))
559 continue;
561 for (r = g->rules; r != NULL; r = r->next) {
562 if (rule_matched(r, pressed)) {
563 do_action(r->action, focused, e);
564 return;
569 send_fake(focused, pressed, e);
572 int
573 group_match(struct group *g, Window w)
575 struct match *m;
577 for (m = g->matches; m != NULL; m = m->next)
578 if (match_window(m, w))
579 return 1;
580 return 0;
584 /* debug/dump stuff */
586 void
587 printkey(struct key k)
589 if (k.modifier & ControlMask)
590 printf("C-");
591 if (k.modifier & ShiftMask)
592 printf("S-");
593 if (k.modifier & Mod1Mask)
594 printf("M-");
595 if (k.modifier & Mod4Mask)
596 printf("s-");
598 printf("%s", XKeysymToString(k.key));
601 void
602 printaction(struct action a)
604 switch (a.type) {
605 case ASPECIAL:
606 switch (a.special) {
607 case ATOGGLE: printf("toggle"); break;
608 case AACTIVATE: printf("activate"); break;
609 case ADEACTIVATE: printf("deactivate"); break;
610 case AIGNORE: printf("ignore"); break;
611 default: abort(); /* unreachable */
613 break;
615 case AFAKE:
616 printf("send key ");
617 printkey(a.send_key);
618 break;
620 case AEXEC:
621 printf("exec %s", a.str);
622 break;
624 default:
625 /* unreachable */
626 abort();
630 void
631 printmatch(struct match *m)
633 if (m == NULL) {
634 printf("(null)");
635 return;
637 printf("match ");
638 switch (m->prop) {
639 case MANY:
640 printf("all");
641 break;
642 case MCLASS:
643 printf("class %s", m->str);
644 break;
645 default:
646 /* unreachable */
647 abort();
650 if (m->next == NULL)
651 printf("\n");
652 else {
653 printf(" ; ");
654 printmatch(m->next);
658 void
659 printrule(struct rule *r)
661 if (r == NULL) {
662 printf("(null)");
663 return;
665 printf("on ");
666 printkey(r->key);
667 printf(" do ");
668 printaction(r->action);
669 printf("\n");
671 if (r->next != NULL)
672 printrule(r->next);
675 void
676 printgroup(struct group *g)
678 if (g == NULL) {
679 printf("(null)");
680 return;
682 printmatch(g->matches);
683 printf("\n");
684 printrule(g->rules);
685 printf("\n\n");
687 if (g->next != NULL)
688 printgroup(g->next);