progman.exe^H^H^H^H
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}