2 * Copyright (c) 2020 Omar Polo <op@omarpolo.com>
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.
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.
17 #include "star-platinum.h"
26 #include <X11/XKBlib.h>
27 #include <X11/Xutil.h>
30 # define pledge(a, b) 0
44 int ignored_modifiers[] = {
46 LockMask, /* caps lock */
47 Mod2Mask, /* num lock */
48 Mod3Mask, /* scroll lock */
52 #define IGNORE_MODIFIERS (LockMask | Mod2Mask | Mod3Mask | Mod5Mask)
54 __attribute__((__format__ (__printf__, 1, 2)))
56 yyerror(const char *fmt, ...)
64 fprintf(stderr, "%s:%d ", fname, yylineno);
65 vfprintf(stderr, fmt, ap);
68 if (l > 0 && fmt[l-1] != '\n')
69 fprintf(stderr, "\n");
80 home = getenv("HOME");
81 xdg = getenv("XDG_CONFIG_HOME");
83 if (home == NULL && xdg == NULL)
86 /* file in home directory >>>>>> XDG shit */
88 if (asprintf(&t, "%s/.star-platinum.conf", home) == -1)
90 if (access(t, R_OK) == 0)
94 /* continue to search */
97 /* sanitize XDG_CONFIG_HOME */
100 if (asprintf(&xdg, "%s/.config", home) == -1)
104 if (asprintf(&t, "%s/star-platinum.conf", xdg) == -1)
107 if (access(t, R_OK) == 0)
119 main(int argc, char **argv)
121 int status = 0, dump_config = 0, conftest = 0, ch;
125 signal(SIGCHLD, SIG_IGN); /* don't allow zombies */
128 while ((ch = getopt(argc, argv, "c:dn")) != -1) {
132 if ((fname = strdup(optarg)) == NULL)
145 fprintf(stderr, "USAGE: %s [-dn] [-c conf]\n", *argv);
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);
170 recfree_group(config);
174 if ((d = XOpenDisplay(NULL)) == NULL) {
175 recfree_group(config);
179 XSetErrorHandler(&error_handler);
181 root = DefaultRootWindow(d);
183 grabkey_matching_windows();
185 XSelectInput(d, root, SubstructureNotifyMask | KeyPressMask);
188 pledge("stdio proc exec", NULL);
195 process_event(config, (XKeyEvent*)&e);
199 XMapEvent *ev = (XMapEvent*)&e;
200 grab_matching_keys(ev->window);
205 case ConfigureNotify:
213 printf("Unknown event %d\n", e.type);
219 recfree_group(config);
227 error_handler(Display *d, XErrorEvent *e)
229 fprintf(stderr, "Xlib error %d\n", e->type);
233 /* TODO: it should grab ALL POSSIBLE COMBINATIONS of `ignored_modifiers`! */
235 grabkey(struct key k, Window w)
237 static size_t len = sizeof(ignored_modifiers)/sizeof(int);
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);
249 grab_matching_keys(Window w)
255 if (!XGetClassHint(d, w, &ch))
258 for (g = config; g != NULL; g = g->next) {
259 if (!group_match(g, w))
262 for (r = g->rules; r != NULL; r = r->next)
268 grabkey_matching_windows()
270 Window root, parent, *children;
273 root = DefaultRootWindow(d);
274 if (!XQueryTree(d, root, &root, &parent, &children, &len))
277 for (i = 0; i < len; ++i)
278 grab_matching_keys(children[i]);
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);
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
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
310 XGetInputFocus(d, &w, &revert_to);
315 send_fake(Window w, struct key k, XKeyEvent *original)
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.
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;
339 e.x_root = original->x_root;
340 e.y_root = original->y_root;
342 XSendEvent(d, w, True, KeyPressMask, (XEvent*)&e);
347 window_match_class(Window w, const char *class)
352 if (!XGetClassHint(d, w, &ch)) {
353 fprintf(stderr, "XGetClassHint failed\n");
357 matched = !strcmp(ch.res_class, class);
369 do_action(struct action a, Window focused, XKeyEvent *original)
373 send_fake(focused, a.send_key, original);
397 /* exec only on key press */
398 if (original->type == KeyRelease)
401 switch (p = fork()) {
406 if ((sh = getenv("SHELL")) == NULL)
408 printf("before exec'ing %s -c '%s'\n", sh, a.str);
409 execlp(sh, sh, "-c", a.str, NULL);
423 free_action(struct action a)
433 new_match(int prop, char *s)
437 if ((m = calloc(1, sizeof(*m))) == NULL)
445 recfree_match(struct match *m)
459 match_window(struct match *m, Window w)
466 return window_match_class(w, m->str);
479 new_rule(struct key k, struct action a)
483 if ((r = calloc(1, sizeof(*r))) == NULL)
485 memcpy(&r->key, &k, sizeof(k));
486 memcpy(&r->action, &a, sizeof(a));
491 recfree_rule(struct rule *r)
504 rule_matched(struct rule *r, struct key k)
509 m &= ~IGNORE_MODIFIERS; /* clear ignored modifiers */
511 return r->key.modifier == m
512 && r->key.key == k.key;
519 new_group(struct match *matches, struct rule *rules)
523 if ((g = calloc(1, sizeof(*g))) == NULL)
525 g->matches = matches;
531 recfree_group(struct group *g)
539 recfree_match(g->matches);
540 recfree_rule(g->rules);
546 process_event(struct group *g, XKeyEvent *e)
550 struct key pressed = {
551 .modifier = e->state,
552 .key = keycode_to_keysym(e->keycode),
555 focused = focused_window();
557 for (; g != NULL; g = g->next) {
558 if (!group_match(g, focused))
561 for (r = g->rules; r != NULL; r = r->next) {
562 if (rule_matched(r, pressed)) {
563 do_action(r->action, focused, e);
569 send_fake(focused, pressed, e);
573 group_match(struct group *g, Window w)
577 for (m = g->matches; m != NULL; m = m->next)
578 if (match_window(m, w))
584 /* debug/dump stuff */
587 printkey(struct key k)
589 if (k.modifier & ControlMask)
591 if (k.modifier & ShiftMask)
593 if (k.modifier & Mod1Mask)
595 if (k.modifier & Mod4Mask)
598 printf("%s", XKeysymToString(k.key));
602 printaction(struct action a)
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 */
617 printkey(a.send_key);
621 printf("exec %s", a.str);
631 printmatch(struct match *m)
643 printf("class %s", m->str);
659 printrule(struct rule *r)
668 printaction(r->action);
676 printgroup(struct group *g)
682 printmatch(g->matches);