progman.exe^H^H^H^H
at master 233 lines 6.2 kB view raw
1/* 2 * Copyright (c) 2020 joshua stein <jcs@jcs.org> 3 * 4 * Permission is hereby granted, free of charge, to any person obtaining a copy 5 * of this software and associated documentation files (the "Software"), to 6 * deal in the Software without restriction, including without limitation the 7 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 * sell copies of the Software, and to permit persons to whom the Software is 9 * furnished to do so, subject to the following conditions: 10 * 11 * The above copyright notice and this permission notice shall be included in 12 * all copies or substantial portions of the Software. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 * AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 18 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 19 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 */ 21 22#include <ctype.h> 23#include <err.h> 24#include <errno.h> 25#include <limits.h> 26#include <stdlib.h> 27#include <strings.h> 28#include "progman.h" 29 30action_t *key_actions = NULL; 31int nkey_actions = 0; 32static int cycle_key = 0; 33 34action_t * 35bind_key(int type, char *key, char *action) 36{ 37 char *tkey, *sep; 38 KeySym k = 0; 39 action_t *taction; 40 int x, mod = 0, iaction = -1, button = 0, overwrite, aidx; 41 42 tkey = strdup(key); 43 44 /* key can be "shift+alt+f1" or "Super+Space" or just "ampersand" */ 45 while ((sep = strchr(tkey, '+'))) { 46 *sep = '\0'; 47 if (strcasecmp(tkey, "shift") == 0) 48 mod |= ShiftMask; 49 else if (strcasecmp(tkey, "control") == 0 || 50 strcasecmp(tkey, "ctrl") == 0 || 51 strcasecmp(tkey, "ctl") == 0) 52 mod |= ControlMask; 53 else if (strcasecmp(tkey, "alt") == 0 || 54 strcasecmp(tkey, "meta") == 0 || 55 strcasecmp(tkey, "mod1") == 0) 56 mod |= Mod1Mask; 57 else if (strcasecmp(tkey, "mod2") == 0) 58 mod |= Mod2Mask; 59 else if (strcasecmp(tkey, "mod3") == 0) 60 mod |= Mod3Mask; 61 else if (strcasecmp(tkey, "super") == 0 || 62 strcasecmp(tkey, "win") == 0 || 63 strcasecmp(tkey, "mod4") == 0) 64 mod |= Mod4Mask; 65 else { 66 warnx("failed parsing modifier \"%s\" in \"%s\", " 67 "skipping", tkey, key); 68 return NULL; 69 } 70 71 tkey = sep + 1; 72 } 73 74 /* modifiers have been parsed, only the key or button should remain */ 75 if (strncasecmp(tkey, "mouse", 5) == 0 && 76 tkey[5] > '0' && tkey[5] <= '9') 77 button = tkey[5] - '0'; 78 else if (tkey[0] != '\0') { 79 if (tkey[1] == '\0') { 80 /* 81 * Assume a single-character key is meant to be used as 82 * its lower-case key, e.g., "Win+T" is mod4+t, not 83 * mod4+T, and if the user wanted it a capital t, they 84 * would specify it as "Win+Shift+T" 85 */ 86 tkey[0] = tolower(tkey[0]); 87 } 88 89 k = XStringToKeysym(tkey); 90 if (k == 0) { 91 warnx("failed parsing key \"%s\", skipping\n", tkey); 92 return NULL; 93 } 94 } 95 96 /* action can be e.g., "cycle" or "exec xterm -g 80x50" */ 97 taction = parse_action(key, action); 98 if (taction == NULL) 99 return NULL; 100 101 /* if we're overriding an existing config, replace it in key_actions */ 102 overwrite = 0; 103 for (x = 0; x < nkey_actions; x++) { 104 if (key_actions[x].type == type && 105 key_actions[x].key == k && 106 key_actions[x].mod == mod && 107 key_actions[x].button == button) { 108 overwrite = 1; 109 aidx = x; 110 break; 111 } 112 } 113 114 if (!overwrite) { 115 key_actions = realloc(key_actions, 116 (nkey_actions + 1) * sizeof(action_t)); 117 if (key_actions == NULL) 118 err(1, "realloc"); 119 120 aidx = nkey_actions; 121 } 122 123 if (taction->action == ACTION_NONE) { 124 key_actions[aidx].key = -1; 125 key_actions[aidx].mod = -1; 126 key_actions[aidx].button = 0; 127 } else { 128 key_actions[aidx].key = k; 129 key_actions[aidx].mod = mod; 130 key_actions[aidx].button = button; 131 } 132 key_actions[aidx].type = type; 133 key_actions[aidx].action = taction->action; 134 key_actions[aidx].iarg = taction->iarg; 135 if (overwrite && key_actions[aidx].sarg) 136 free(key_actions[aidx].sarg); 137 key_actions[aidx].sarg = taction->sarg; 138 139 /* retain taction->sarg */ 140 free(taction); 141 142 if (!overwrite) 143 nkey_actions++; 144 145#ifdef DEBUG 146 if (key_actions[aidx].action == ACTION_NONE) 147 printf("%s(%s): unbinding key %ld/button %d with " 148 "mod mask 0x%x\n", __func__, key, k, button, mod); 149 else 150 printf("%s(%s): binding key %ld/button %d with mod mask 0x%x " 151 "to action \"%s\"\n", __func__, key, k, button, mod, 152 action); 153#endif 154 155 if (type == BINDING_TYPE_KEYBOARD) { 156 if (overwrite && iaction == ACTION_NONE) 157 XUngrabKey(dpy, XKeysymToKeycode(dpy, k), mod, root); 158 else if (!overwrite) 159 XGrabKey(dpy, XKeysymToKeycode(dpy, k), mod, root, 160 False, GrabModeAsync, GrabModeAsync); 161 } 162 163 return &key_actions[aidx]; 164} 165 166void 167handle_key_event(XKeyEvent *e) 168{ 169#ifdef DEBUG 170 char buf[64]; 171#endif 172 KeySym kc; 173 action_t *action = NULL; 174 int i; 175 176 kc = XLookupKeysym(e, 0); 177 178#ifdef DEBUG 179 snprintf(buf, sizeof(buf), "%c:%ld", e->type == KeyRelease ? 'U' : 'D', 180 kc); 181 dump_name(focused, __func__, buf, NULL); 182#endif 183 184 if (cycle_key && kc != cycle_key && e->type == KeyRelease) { 185 /* 186 * If any key other than the non-modifier(s) of our cycle 187 * binding was released, consider the cycle over. 188 */ 189 cycle_key = 0; 190 XUngrabKeyboard(dpy, CurrentTime); 191 XAllowEvents(dpy, ReplayKeyboard, e->time); 192 XFlush(dpy); 193 194 if (cycle_head) { 195 cycle_head = NULL; 196 if (focused && focused->state & STATE_ICONIFIED) 197 uniconify_client(focused); 198 } 199 200 return; 201 } 202 203 for (i = 0; i < nkey_actions; i++) { 204 if (key_actions[i].type == BINDING_TYPE_KEYBOARD && 205 key_actions[i].key == kc && 206 key_actions[i].mod == e->state) { 207 action = &key_actions[i]; 208 break; 209 } 210 } 211 212 if (!action) 213 return; 214 215 if (e->type != KeyPress) 216 return; 217 218 switch (key_actions[i].action) { 219 case ACTION_CYCLE: 220 case ACTION_REVERSE_CYCLE: 221 /* 222 * Keep watching input until the modifier is released, but the 223 * keycode will be the modifier key 224 */ 225 XGrabKeyboard(dpy, root, False, GrabModeAsync, GrabModeAsync, 226 e->time); 227 cycle_key = key_actions[i].key; 228 take_action(&key_actions[i]); 229 break; 230 default: 231 take_action(&key_actions[i]); 232 } 233}