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 <stdio.h>
21 #include <string.h>
22 #include <unistd.h>
24 #include <X11/XKBlib.h>
25 #include <X11/Xutil.h>
27 #ifndef __OpenBSD__
28 # define pledge(a, b) 0
29 #endif
31 extern FILE *yyin;
32 char *fname;
34 extern int yylineno;
35 int yyparse(void);
37 struct group *config;
38 int goterror = 0;
40 Display *d;
42 int ignored_modifiers[] = {
43 0, /* no modifiers */
44 LockMask, /* caps lock */
45 Mod2Mask, /* num lock */
46 Mod3Mask, /* scroll lock */
47 Mod5Mask, /* ? */
48 };
50 #define IGNORE_MODIFIERS (LockMask | Mod2Mask | Mod3Mask | Mod5Mask)
52 void
53 yyerror(const char *e)
54 {
55 goterror = 1;
56 fprintf(stderr, "%s:%d %s\n", fname, yylineno, e);
57 }
59 char *
60 find_config()
61 {
62 char *home, *xdg, *t;
63 int free_xdg = 0;
65 home = getenv("HOME");
66 xdg = getenv("XDG_CONFIG_HOME");
68 if (home == NULL && xdg == NULL)
69 return NULL;
71 /* file in home directory >>>>>> XDG shit */
72 if (home != NULL) {
73 if (asprintf(&t, "%s/.star-platinum.conf", home) == -1)
74 return NULL;
75 if (access(t, R_OK) == 0)
76 return t;
78 free(t);
79 /* continue to search */
80 }
82 /* sanitize XDG_CONFIG_HOME */
83 if (xdg == NULL) {
84 free_xdg = 1;
85 if (asprintf(&xdg, "%s/.config", home) == -1)
86 return NULL;
87 }
89 if (asprintf(&t, "%s/star-platinum.conf", xdg) == -1)
90 goto err;
92 if (access(t, R_OK) == 0)
93 return t;
94 free(t);
96 err:
97 if (free_xdg)
98 free(xdg);
100 return NULL;
103 int
104 main(int argc, char **argv)
106 int status = 0, dump_config = 0, conftest = 0, ch;
107 struct group *g;
108 struct rule *r;
109 XEvent e;
110 Window root;
112 fname = NULL;
113 while ((ch = getopt(argc, argv, "c:dn")) != -1) {
114 switch (ch) {
115 case 'c':
116 free(fname);
117 if ((fname = strdup(optarg)) == NULL)
118 err(1, "strdup");
119 break;
121 case 'd':
122 dump_config = 1;
123 break;
125 case 'n':
126 conftest = 1;
127 break;
129 default:
130 fprintf(stderr, "USAGE: %s [-dn] [-c conf]\n", *argv);
131 return 1;
135 if (fname == NULL) {
136 if ((fname = find_config()) == NULL)
137 errx(1, "can't find a configuration file");
140 if ((yyin = fopen(fname, "r")) == NULL)
141 err(1, "cannot open %s", fname);
142 yyparse();
143 fclose(yyin);
145 free(fname);
146 fname = NULL;
148 if (goterror)
149 return 1;
151 if (dump_config)
152 printgroup(config);
154 if (conftest) {
155 recfree_group(config);
156 return 0;
159 if ((d = XOpenDisplay(NULL)) == NULL) {
160 recfree_group(config);
161 return 1;
164 XSetErrorHandler(&error_handler);
166 root = DefaultRootWindow(d);
168 /* grab all the keys */
169 for (g = config; g != NULL; g = g->next)
170 for (r = g->rules; r != NULL; r = r->next)
171 grabkey(r->key);
173 XSelectInput(d, root, KeyPressMask);
174 XFlush(d);
176 pledge("stdio", "");
178 while (1) {
179 XNextEvent(d, &e);
180 switch (e.type) {
181 case KeyRelease:
182 case KeyPress:
183 process_event(config, (XKeyEvent*)&e);
184 break;
186 default:
187 printf("Unknown event %d\n", e.type);
188 break;
192 XCloseDisplay(d);
193 recfree_group(config);
194 return status;
198 /* xlib */
200 int
201 error_handler(Display *d, XErrorEvent *e)
203 fprintf(stderr, "Xlib error %d\n", e->type);
204 return 1;
207 /* TODO: it should grab ALL POSSIBLE COMBINATIONS of `ignored_modifiers`! */
208 void
209 grabkey(struct key k)
211 static size_t len = sizeof(ignored_modifiers)/sizeof(int);
212 size_t i;
213 Window root;
215 root = DefaultRootWindow(d);
217 /* printf("Grabbing "); printkey(k); printf("\n"); */
218 for (i = 0; i < len; ++i) {
219 XGrabKey(d, XKeysymToKeycode(d, k.key),
220 k.modifier | ignored_modifiers[i],
221 root, False, GrabModeAsync, GrabModeAsync);
225 KeySym
226 keycode_to_keysym(unsigned int kc)
228 /* group 0 (?). shift level is 0 because we don't want it*/
229 return XkbKeycodeToKeysym(d, kc, 0, 0);
232 Window
233 focused_window()
235 Window w;
236 int revert_to;
238 /* one can use (at least) three way to obtain the current
239 * focused window using xlib:
241 * - XQueryTree : you traverse tre tree until you find the
242 * window
244 * - looking at _NET_ACTIVE_WINDOW in the root window, but
245 * depedns on the window manager to set it
247 * - using XGetInputFocus
249 * I don't know the pro/cons of these, but XGetInputFocus
250 * seems the easiest.
251 */
252 XGetInputFocus(d, &w, &revert_to);
253 return w;
256 void
257 send_fake(Window w, struct key k, int pressed)
259 XKeyEvent e;
261 e.type = pressed ? KeyPress : KeyRelease;
263 e.display = d;
264 e.window = w;
265 e.root = DefaultRootWindow(d);
266 e.subwindow = None;
267 e.time = CurrentTime;
269 /* TODO: fix these */
270 e.x = 1;
271 e.y = 1;
272 e.x_root = 1;
273 e.y_root = 1;
275 e.same_screen = True;
276 e.keycode = XKeysymToKeycode(d, k.key);
277 e.state = k.modifier;
279 XSendEvent(d, w, True, KeyPressMask, (XEvent*)&e);
280 XFlush(d);
283 int
284 window_match_class(Window w, const char *class)
286 XClassHint ch;
287 int matched;
289 if (!XGetClassHint(d, w, &ch)) {
290 fprintf(stderr, "XGetClassHint failed\n");
291 return 0;
294 matched = !strcmp(ch.res_class, class);
296 XFree(ch.res_name);
297 XFree(ch.res_class);
299 return matched;
303 /* action */
305 void
306 do_action(struct action a, Window focused, int pressed)
308 switch (a.type) {
309 case AFAKE:
310 send_fake(focused, a.send_key, pressed);
311 break;
313 case ASPECIAL:
314 switch (a.special) {
315 case ATOGGLE:
316 case AACTIVATE:
317 case ADEACTIVATE:
318 printf("TODO\n");
319 break;
321 case AIGNORE:
322 break;
324 default:
325 /* unreachable */
326 abort();
328 break;
330 default:
331 /* unreachable */
332 abort();
337 /* match */
339 struct match *
340 new_match(int prop, char *s)
342 struct match *m;
344 if ((m = calloc(1, sizeof(*m))) == NULL)
345 err(1, "calloc");
346 m->prop = prop;
347 m->str = s;
348 return m;
351 void
352 recfree_match(struct match *m)
354 struct match *mt;
356 if (m == NULL)
357 return;
359 mt = m->next;
360 free(m->str);
361 free(m);
362 recfree_match(mt);
365 int
366 match_window(struct match *m, Window w)
368 switch (m->prop) {
369 case MCLASS:
370 return window_match_class(w, m->str);
371 break;
373 default:
374 /* unreachable */
375 abort();
380 /* rule */
382 struct rule *
383 new_rule(struct key k, struct action a)
385 struct rule *r;
387 if ((r = calloc(1, sizeof(*r))) == NULL)
388 err(1, "calloc");
389 memcpy(&r->key, &k, sizeof(k));
390 memcpy(&r->action, &a, sizeof(a));
391 return r;
394 void
395 recfree_rule(struct rule *r)
397 struct rule *rt;
399 if (r == NULL)
400 return;
402 rt = r->next;
403 free(r);
404 recfree_rule(rt);
407 int
408 rule_matched(struct rule *r, struct key k)
410 unsigned int m;
412 m = k.modifier;
413 m &= ~IGNORE_MODIFIERS; /* clear ignored modifiers */
415 return r->key.modifier == m
416 && r->key.key == k.key;
420 /* group */
422 struct group *
423 new_group(struct match *matches, struct rule *rules)
425 struct group *g;
427 if ((g = calloc(1, sizeof(*g))) == NULL)
428 err(1, "calloc");
429 g->matches = matches;
430 g->rules = rules;
431 return g;
434 void
435 recfree_group(struct group *g)
437 struct group *gt;
439 if (g == NULL)
440 return;
442 gt = g->next;
443 recfree_match(g->matches);
444 recfree_rule(g->rules);
445 free(g);
446 recfree_group(gt);
449 void
450 process_event(struct group *g, XKeyEvent *e)
452 Window focused;
453 struct rule *r;
454 struct key pressed = {
455 .modifier = e->state,
456 .key = keycode_to_keysym(e->keycode),
457 };
459 focused = focused_window();
461 for (; g != NULL; g = g->next) {
462 if (!group_match(g, focused))
463 continue;
465 for (r = g->rules; r != NULL; r = r->next) {
466 if (rule_matched(r, pressed)) {
467 do_action(r->action, focused, e->type == KeyPress);
468 return;
473 send_fake(focused, pressed, e->type == KeyPress);
476 int
477 group_match(struct group *g, Window w)
479 struct match *m;
481 for (m = g->matches; m != NULL; m = m->next)
482 if (match_window(m, w))
483 return 1;
484 return 0;
488 /* debug/dump stuff */
490 void
491 printkey(struct key k)
493 if (k.modifier & ControlMask)
494 printf("C-");
495 if (k.modifier & ShiftMask)
496 printf("S-");
497 if (k.modifier & Mod1Mask)
498 printf("M-");
499 if (k.modifier & Mod4Mask)
500 printf("s-");
502 printf("%s", XKeysymToString(k.key));
505 void
506 printaction(struct action a)
508 if (a.type == ASPECIAL) {
509 switch (a.special) {
510 case ATOGGLE: printf("toggle"); break;
511 case AACTIVATE: printf("activate"); break;
512 case ADEACTIVATE: printf("deactivate"); break;
513 case AIGNORE: printf("ignore"); break;
515 } else {
516 printf("send key ");
517 printkey(a.send_key);
521 void
522 printmatch(struct match *m)
524 if (m == NULL) {
525 printf("(null)");
526 return;
528 printf("match ");
529 switch (m->prop) {
530 case MCLASS:
531 printf("class");
532 break;
533 default:
534 abort();
536 printf(" %s", m->str);
538 if (m->next == NULL)
539 printf("\n");
540 else {
541 printf(" ; ");
542 printmatch(m->next);
546 void
547 printrule(struct rule *r)
549 if (r == NULL) {
550 printf("(null)");
551 return;
553 printf("on ");
554 printkey(r->key);
555 printf(" do ");
556 printaction(r->action);
557 printf("\n");
559 if (r->next != NULL)
560 printrule(r->next);
563 void
564 printgroup(struct group *g)
566 if (g == NULL) {
567 printf("(null)");
568 return;
570 printmatch(g->matches);
571 printf("\n");
572 printrule(g->rules);
573 printf("\n\n");
575 if (g->next != NULL)
576 printgroup(g->next);