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 struct group *g;
123 struct rule *r;
124 XEvent e;
125 Window root;
127 signal(SIGCHLD, SIG_IGN); /* don't allow zombies */
129 fname = NULL;
130 while ((ch = getopt(argc, argv, "c:dn")) != -1) {
131 switch (ch) {
132 case 'c':
133 free(fname);
134 if ((fname = strdup(optarg)) == NULL)
135 err(1, "strdup");
136 break;
138 case 'd':
139 dump_config = 1;
140 break;
142 case 'n':
143 conftest = 1;
144 break;
146 default:
147 fprintf(stderr, "USAGE: %s [-dn] [-c conf]\n", *argv);
148 return 1;
152 if (fname == NULL) {
153 if ((fname = find_config()) == NULL)
154 errx(1, "can't find a configuration file");
157 if ((yyin = fopen(fname, "r")) == NULL)
158 err(1, "cannot open %s", fname);
159 yyparse();
160 fclose(yyin);
162 free(fname);
163 fname = NULL;
165 if (goterror)
166 return 1;
168 if (dump_config)
169 printgroup(config);
171 if (conftest) {
172 recfree_group(config);
173 return 0;
176 if ((d = XOpenDisplay(NULL)) == NULL) {
177 recfree_group(config);
178 return 1;
181 XSetErrorHandler(&error_handler);
183 root = DefaultRootWindow(d);
185 /* grab all the keys */
186 for (g = config; g != NULL; g = g->next)
187 for (r = g->rules; r != NULL; r = r->next)
188 grabkey(r->key, root);
190 XSelectInput(d, root, KeyPressMask);
191 XFlush(d);
193 pledge("stdio proc exec", NULL);
195 while (1) {
196 XNextEvent(d, &e);
197 switch (e.type) {
198 case KeyRelease:
199 case KeyPress:
200 process_event(config, (XKeyEvent*)&e);
201 break;
203 default:
204 printf("Unknown event %d\n", e.type);
205 break;
209 XCloseDisplay(d);
210 recfree_group(config);
211 return status;
215 /* xlib */
217 int
218 error_handler(Display *d, XErrorEvent *e)
220 fprintf(stderr, "Xlib error %d\n", e->type);
221 return 1;
224 /* TODO: it should grab ALL POSSIBLE COMBINATIONS of `ignored_modifiers`! */
225 void
226 grabkey(struct key k, Window w)
228 static size_t len = sizeof(ignored_modifiers)/sizeof(int);
229 size_t i;
231 /* printf("Grabbing "); printkey(k); printf("\n"); */
232 for (i = 0; i < len; ++i) {
233 XGrabKey(d, XKeysymToKeycode(d, k.key),
234 k.modifier | ignored_modifiers[i],
235 w, False, GrabModeAsync, GrabModeAsync);
239 KeySym
240 keycode_to_keysym(unsigned int kc)
242 /* group 0 (?). shift level is 0 because we don't want it*/
243 return XkbKeycodeToKeysym(d, kc, 0, 0);
246 Window
247 focused_window()
249 Window w;
250 int revert_to;
252 /* one can use (at least) three way to obtain the current
253 * focused window using xlib:
255 * - XQueryTree : you traverse tre tree until you find the
256 * window
258 * - looking at _NET_ACTIVE_WINDOW in the root window, but
259 * depedns on the window manager to set it
261 * - using XGetInputFocus
263 * I don't know the pro/cons of these, but XGetInputFocus
264 * seems the easiest.
265 */
266 XGetInputFocus(d, &w, &revert_to);
267 return w;
270 void
271 send_fake(Window w, struct key k, XKeyEvent *original)
273 XKeyEvent e;
275 /*
276 * this needs to be hijacked. original->window is the root
277 * window (since we grabbed the key there) and we want to
278 * deliver the key to another window.
279 */
280 e.window = w;
282 /* this is the fake key */
283 e.keycode = XKeysymToKeycode(d, k.key);
284 e.state = k.modifier;
286 /* the rest is just copying fields from the original event */
287 e.type = original->type;
288 e.display = original->display;
289 e.root = original->root;
290 e.subwindow = original->subwindow;
291 e.time = original->time;
292 e.same_screen = original->same_screen;
293 e.x = original->x;
294 e.y = original->y;
295 e.x_root = original->x_root;
296 e.y_root = original->y_root;
298 XSendEvent(d, w, True, KeyPressMask, (XEvent*)&e);
299 XFlush(d);
302 int
303 window_match_class(Window w, const char *class)
305 XClassHint ch;
306 int matched;
308 if (!XGetClassHint(d, w, &ch)) {
309 fprintf(stderr, "XGetClassHint failed\n");
310 return 0;
313 matched = !strcmp(ch.res_class, class);
315 XFree(ch.res_name);
316 XFree(ch.res_class);
318 return matched;
322 /* action */
324 void
325 do_action(struct action a, Window focused, XKeyEvent *original)
327 switch (a.type) {
328 case AFAKE:
329 send_fake(focused, a.send_key, original);
330 break;
332 case ASPECIAL:
333 switch (a.special) {
334 case ATOGGLE:
335 case AACTIVATE:
336 case ADEACTIVATE:
337 printf("TODO\n");
338 break;
340 case AIGNORE:
341 break;
343 default:
344 /* unreachable */
345 abort();
347 break;
349 case AEXEC: {
350 pid_t p;
351 const char *sh;
353 /* exec only on key press */
354 if (original->type == KeyRelease)
355 break;
357 switch (p = fork()) {
358 case -1:
359 err(1, "fork");
361 case 0:
362 if ((sh = getenv("SHELL")) == NULL)
363 sh = "/bin/sh";
364 printf("before exec'ing %s -c '%s'\n", sh, a.str);
365 execlp(sh, sh, "-c", a.str, NULL);
366 err(1, "execlp");
369 break;
372 default:
373 /* unreachable */
374 abort();
378 void
379 free_action(struct action a)
381 if (a.type == AEXEC)
382 free(a.str);
386 /* match */
388 struct match *
389 new_match(int prop, char *s)
391 struct match *m;
393 if ((m = calloc(1, sizeof(*m))) == NULL)
394 err(1, "calloc");
395 m->prop = prop;
396 m->str = s;
397 return m;
400 void
401 recfree_match(struct match *m)
403 struct match *mt;
405 if (m == NULL)
406 return;
408 mt = m->next;
409 free(m->str);
410 free(m);
411 recfree_match(mt);
414 int
415 match_window(struct match *m, Window w)
417 switch (m->prop) {
418 case MANY:
419 return 1;
421 case MCLASS:
422 return window_match_class(w, m->str);
423 break;
425 default:
426 /* unreachable */
427 abort();
432 /* rule */
434 struct rule *
435 new_rule(struct key k, struct action a)
437 struct rule *r;
439 if ((r = calloc(1, sizeof(*r))) == NULL)
440 err(1, "calloc");
441 memcpy(&r->key, &k, sizeof(k));
442 memcpy(&r->action, &a, sizeof(a));
443 return r;
446 void
447 recfree_rule(struct rule *r)
449 struct rule *rt;
451 if (r == NULL)
452 return;
454 rt = r->next;
455 free(r);
456 recfree_rule(rt);
459 int
460 rule_matched(struct rule *r, struct key k)
462 unsigned int m;
464 m = k.modifier;
465 m &= ~IGNORE_MODIFIERS; /* clear ignored modifiers */
467 return r->key.modifier == m
468 && r->key.key == k.key;
472 /* group */
474 struct group *
475 new_group(struct match *matches, struct rule *rules)
477 struct group *g;
479 if ((g = calloc(1, sizeof(*g))) == NULL)
480 err(1, "calloc");
481 g->matches = matches;
482 g->rules = rules;
483 return g;
486 void
487 recfree_group(struct group *g)
489 struct group *gt;
491 if (g == NULL)
492 return;
494 gt = g->next;
495 recfree_match(g->matches);
496 recfree_rule(g->rules);
497 free(g);
498 recfree_group(gt);
501 void
502 process_event(struct group *g, XKeyEvent *e)
504 Window focused;
505 struct rule *r;
506 struct key pressed = {
507 .modifier = e->state,
508 .key = keycode_to_keysym(e->keycode),
509 };
511 focused = focused_window();
513 for (; g != NULL; g = g->next) {
514 if (!group_match(g, focused))
515 continue;
517 for (r = g->rules; r != NULL; r = r->next) {
518 if (rule_matched(r, pressed)) {
519 do_action(r->action, focused, e);
520 return;
525 send_fake(focused, pressed, e);
528 int
529 group_match(struct group *g, Window w)
531 struct match *m;
533 for (m = g->matches; m != NULL; m = m->next)
534 if (match_window(m, w))
535 return 1;
536 return 0;
540 /* debug/dump stuff */
542 void
543 printkey(struct key k)
545 if (k.modifier & ControlMask)
546 printf("C-");
547 if (k.modifier & ShiftMask)
548 printf("S-");
549 if (k.modifier & Mod1Mask)
550 printf("M-");
551 if (k.modifier & Mod4Mask)
552 printf("s-");
554 printf("%s", XKeysymToString(k.key));
557 void
558 printaction(struct action a)
560 switch (a.type) {
561 case ASPECIAL:
562 switch (a.special) {
563 case ATOGGLE: printf("toggle"); break;
564 case AACTIVATE: printf("activate"); break;
565 case ADEACTIVATE: printf("deactivate"); break;
566 case AIGNORE: printf("ignore"); break;
567 default: abort(); /* unreachable */
569 break;
571 case AFAKE:
572 printf("send key ");
573 printkey(a.send_key);
574 break;
576 case AEXEC:
577 printf("exec %s", a.str);
578 break;
580 default:
581 /* unreachable */
582 abort();
586 void
587 printmatch(struct match *m)
589 if (m == NULL) {
590 printf("(null)");
591 return;
593 printf("match ");
594 switch (m->prop) {
595 case MANY:
596 printf("all");
597 break;
598 case MCLASS:
599 printf("class %s", m->str);
600 break;
601 default:
602 /* unreachable */
603 abort();
606 if (m->next == NULL)
607 printf("\n");
608 else {
609 printf(" ; ");
610 printmatch(m->next);
614 void
615 printrule(struct rule *r)
617 if (r == NULL) {
618 printf("(null)");
619 return;
621 printf("on ");
622 printkey(r->key);
623 printf(" do ");
624 printaction(r->action);
625 printf("\n");
627 if (r->next != NULL)
628 printrule(r->next);
631 void
632 printgroup(struct group *g)
634 if (g == NULL) {
635 printf("(null)");
636 return;
638 printmatch(g->matches);
639 printf("\n");
640 printrule(g->rules);
641 printf("\n\n");
643 if (g->next != NULL)
644 printgroup(g->next);