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 <stdarg.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <unistd.h>
25 #include <X11/XKBlib.h>
26 #include <X11/Xutil.h>
28 #ifndef __OpenBSD__
29 # define pledge(a, b) 0
30 #endif
32 extern FILE *yyin;
33 char *fname;
35 extern int yylineno;
36 int yyparse(void);
38 struct group *config;
39 int goterror = 0;
41 Display *d;
43 int ignored_modifiers[] = {
44 0, /* no modifiers */
45 LockMask, /* caps lock */
46 Mod2Mask, /* num lock */
47 Mod3Mask, /* scroll lock */
48 Mod5Mask, /* ? */
49 };
51 #define IGNORE_MODIFIERS (LockMask | Mod2Mask | Mod3Mask | Mod5Mask)
53 __attribute__((__format__ (__printf__, 1, 2)))
54 void
55 yyerror(const char *fmt, ...)
56 {
57 va_list ap;
58 size_t l;
60 va_start(ap, fmt);
62 goterror = 1;
63 fprintf(stderr, "%s:%d ", fname, yylineno);
64 vfprintf(stderr, fmt, ap);
66 l = strlen(fmt);
67 if (l > 0 && fmt[l-1] != '\n')
68 fprintf(stderr, "\n");
70 va_end(ap);
71 }
73 char *
74 find_config()
75 {
76 char *home, *xdg, *t;
77 int free_xdg = 0;
79 home = getenv("HOME");
80 xdg = getenv("XDG_CONFIG_HOME");
82 if (home == NULL && xdg == NULL)
83 return NULL;
85 /* file in home directory >>>>>> XDG shit */
86 if (home != NULL) {
87 if (asprintf(&t, "%s/.star-platinum.conf", home) == -1)
88 return NULL;
89 if (access(t, R_OK) == 0)
90 return t;
92 free(t);
93 /* continue to search */
94 }
96 /* sanitize XDG_CONFIG_HOME */
97 if (xdg == NULL) {
98 free_xdg = 1;
99 if (asprintf(&xdg, "%s/.config", home) == -1)
100 return NULL;
103 if (asprintf(&t, "%s/star-platinum.conf", xdg) == -1)
104 goto err;
106 if (access(t, R_OK) == 0)
107 return t;
108 free(t);
110 err:
111 if (free_xdg)
112 free(xdg);
114 return NULL;
117 int
118 main(int argc, char **argv)
120 int status = 0, dump_config = 0, conftest = 0, ch;
121 struct group *g;
122 struct rule *r;
123 XEvent e;
124 Window root;
126 fname = NULL;
127 while ((ch = getopt(argc, argv, "c:dn")) != -1) {
128 switch (ch) {
129 case 'c':
130 free(fname);
131 if ((fname = strdup(optarg)) == NULL)
132 err(1, "strdup");
133 break;
135 case 'd':
136 dump_config = 1;
137 break;
139 case 'n':
140 conftest = 1;
141 break;
143 default:
144 fprintf(stderr, "USAGE: %s [-dn] [-c conf]\n", *argv);
145 return 1;
149 if (fname == NULL) {
150 if ((fname = find_config()) == NULL)
151 errx(1, "can't find a configuration file");
154 if ((yyin = fopen(fname, "r")) == NULL)
155 err(1, "cannot open %s", fname);
156 yyparse();
157 fclose(yyin);
159 free(fname);
160 fname = NULL;
162 if (goterror)
163 return 1;
165 if (dump_config)
166 printgroup(config);
168 if (conftest) {
169 recfree_group(config);
170 return 0;
173 if ((d = XOpenDisplay(NULL)) == NULL) {
174 recfree_group(config);
175 return 1;
178 XSetErrorHandler(&error_handler);
180 root = DefaultRootWindow(d);
182 /* grab all the keys */
183 for (g = config; g != NULL; g = g->next)
184 for (r = g->rules; r != NULL; r = r->next)
185 grabkey(r->key);
187 XSelectInput(d, root, KeyPressMask);
188 XFlush(d);
190 pledge("stdio", "");
192 while (1) {
193 XNextEvent(d, &e);
194 switch (e.type) {
195 case KeyRelease:
196 case KeyPress:
197 process_event(config, (XKeyEvent*)&e);
198 break;
200 default:
201 printf("Unknown event %d\n", e.type);
202 break;
206 XCloseDisplay(d);
207 recfree_group(config);
208 return status;
212 /* xlib */
214 int
215 error_handler(Display *d, XErrorEvent *e)
217 fprintf(stderr, "Xlib error %d\n", e->type);
218 return 1;
221 /* TODO: it should grab ALL POSSIBLE COMBINATIONS of `ignored_modifiers`! */
222 void
223 grabkey(struct key k)
225 static size_t len = sizeof(ignored_modifiers)/sizeof(int);
226 size_t i;
227 Window root;
229 root = DefaultRootWindow(d);
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 root, 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, int pressed)
273 XKeyEvent e;
275 e.type = pressed ? KeyPress : KeyRelease;
277 e.display = d;
278 e.window = w;
279 e.root = DefaultRootWindow(d);
280 e.subwindow = None;
281 e.time = CurrentTime;
283 /* TODO: fix these */
284 e.x = 1;
285 e.y = 1;
286 e.x_root = 1;
287 e.y_root = 1;
289 e.same_screen = True;
290 e.keycode = XKeysymToKeycode(d, k.key);
291 e.state = k.modifier;
293 XSendEvent(d, w, True, KeyPressMask, (XEvent*)&e);
294 XFlush(d);
297 int
298 window_match_class(Window w, const char *class)
300 XClassHint ch;
301 int matched;
303 if (!XGetClassHint(d, w, &ch)) {
304 fprintf(stderr, "XGetClassHint failed\n");
305 return 0;
308 matched = !strcmp(ch.res_class, class);
310 XFree(ch.res_name);
311 XFree(ch.res_class);
313 return matched;
317 /* action */
319 void
320 do_action(struct action a, Window focused, int pressed)
322 switch (a.type) {
323 case AFAKE:
324 send_fake(focused, a.send_key, pressed);
325 break;
327 case ASPECIAL:
328 switch (a.special) {
329 case ATOGGLE:
330 case AACTIVATE:
331 case ADEACTIVATE:
332 printf("TODO\n");
333 break;
335 case AIGNORE:
336 break;
338 default:
339 /* unreachable */
340 abort();
342 break;
344 default:
345 /* unreachable */
346 abort();
351 /* match */
353 struct match *
354 new_match(int prop, char *s)
356 struct match *m;
358 if ((m = calloc(1, sizeof(*m))) == NULL)
359 err(1, "calloc");
360 m->prop = prop;
361 m->str = s;
362 return m;
365 void
366 recfree_match(struct match *m)
368 struct match *mt;
370 if (m == NULL)
371 return;
373 mt = m->next;
374 free(m->str);
375 free(m);
376 recfree_match(mt);
379 int
380 match_window(struct match *m, Window w)
382 switch (m->prop) {
383 case MANY:
384 return 1;
386 case MCLASS:
387 return window_match_class(w, m->str);
388 break;
390 default:
391 /* unreachable */
392 abort();
397 /* rule */
399 struct rule *
400 new_rule(struct key k, struct action a)
402 struct rule *r;
404 if ((r = calloc(1, sizeof(*r))) == NULL)
405 err(1, "calloc");
406 memcpy(&r->key, &k, sizeof(k));
407 memcpy(&r->action, &a, sizeof(a));
408 return r;
411 void
412 recfree_rule(struct rule *r)
414 struct rule *rt;
416 if (r == NULL)
417 return;
419 rt = r->next;
420 free(r);
421 recfree_rule(rt);
424 int
425 rule_matched(struct rule *r, struct key k)
427 unsigned int m;
429 m = k.modifier;
430 m &= ~IGNORE_MODIFIERS; /* clear ignored modifiers */
432 return r->key.modifier == m
433 && r->key.key == k.key;
437 /* group */
439 struct group *
440 new_group(struct match *matches, struct rule *rules)
442 struct group *g;
444 if ((g = calloc(1, sizeof(*g))) == NULL)
445 err(1, "calloc");
446 g->matches = matches;
447 g->rules = rules;
448 return g;
451 void
452 recfree_group(struct group *g)
454 struct group *gt;
456 if (g == NULL)
457 return;
459 gt = g->next;
460 recfree_match(g->matches);
461 recfree_rule(g->rules);
462 free(g);
463 recfree_group(gt);
466 void
467 process_event(struct group *g, XKeyEvent *e)
469 Window focused;
470 struct rule *r;
471 struct key pressed = {
472 .modifier = e->state,
473 .key = keycode_to_keysym(e->keycode),
474 };
476 focused = focused_window();
478 for (; g != NULL; g = g->next) {
479 if (!group_match(g, focused))
480 continue;
482 for (r = g->rules; r != NULL; r = r->next) {
483 if (rule_matched(r, pressed)) {
484 do_action(r->action, focused, e->type == KeyPress);
485 return;
490 send_fake(focused, pressed, e->type == KeyPress);
493 int
494 group_match(struct group *g, Window w)
496 struct match *m;
498 for (m = g->matches; m != NULL; m = m->next)
499 if (match_window(m, w))
500 return 1;
501 return 0;
505 /* debug/dump stuff */
507 void
508 printkey(struct key k)
510 if (k.modifier & ControlMask)
511 printf("C-");
512 if (k.modifier & ShiftMask)
513 printf("S-");
514 if (k.modifier & Mod1Mask)
515 printf("M-");
516 if (k.modifier & Mod4Mask)
517 printf("s-");
519 printf("%s", XKeysymToString(k.key));
522 void
523 printaction(struct action a)
525 if (a.type == ASPECIAL) {
526 switch (a.special) {
527 case ATOGGLE: printf("toggle"); break;
528 case AACTIVATE: printf("activate"); break;
529 case ADEACTIVATE: printf("deactivate"); break;
530 case AIGNORE: printf("ignore"); break;
532 } else {
533 printf("send key ");
534 printkey(a.send_key);
538 void
539 printmatch(struct match *m)
541 if (m == NULL) {
542 printf("(null)");
543 return;
545 printf("match ");
546 switch (m->prop) {
547 case MCLASS:
548 printf("class");
549 break;
550 default:
551 abort();
553 printf(" %s", m->str);
555 if (m->next == NULL)
556 printf("\n");
557 else {
558 printf(" ; ");
559 printmatch(m->next);
563 void
564 printrule(struct rule *r)
566 if (r == NULL) {
567 printf("(null)");
568 return;
570 printf("on ");
571 printkey(r->key);
572 printf(" do ");
573 printaction(r->action);
574 printf("\n");
576 if (r->next != NULL)
577 printrule(r->next);
580 void
581 printgroup(struct group *g)
583 if (g == NULL) {
584 printf("(null)");
585 return;
587 printmatch(g->matches);
588 printf("\n");
589 printrule(g->rules);
590 printf("\n\n");
592 if (g->next != NULL)
593 printgroup(g->next);