diff options
Diffstat (limited to 'sway/input/keyboard.c')
-rw-r--r-- | sway/input/keyboard.c | 504 |
1 files changed, 504 insertions, 0 deletions
diff --git a/sway/input/keyboard.c b/sway/input/keyboard.c new file mode 100644 index 00000000..dbb0c359 --- /dev/null +++ b/sway/input/keyboard.c | |||
@@ -0,0 +1,504 @@ | |||
1 | #include <assert.h> | ||
2 | #include <wlr/backend/multi.h> | ||
3 | #include <wlr/backend/session.h> | ||
4 | #include "sway/input/seat.h" | ||
5 | #include "sway/input/keyboard.h" | ||
6 | #include "sway/input/input-manager.h" | ||
7 | #include "sway/commands.h" | ||
8 | #include "log.h" | ||
9 | |||
10 | static bool keysym_is_modifier(xkb_keysym_t keysym) { | ||
11 | switch (keysym) { | ||
12 | case XKB_KEY_Shift_L: case XKB_KEY_Shift_R: | ||
13 | case XKB_KEY_Control_L: case XKB_KEY_Control_R: | ||
14 | case XKB_KEY_Caps_Lock: | ||
15 | case XKB_KEY_Shift_Lock: | ||
16 | case XKB_KEY_Meta_L: case XKB_KEY_Meta_R: | ||
17 | case XKB_KEY_Alt_L: case XKB_KEY_Alt_R: | ||
18 | case XKB_KEY_Super_L: case XKB_KEY_Super_R: | ||
19 | case XKB_KEY_Hyper_L: case XKB_KEY_Hyper_R: | ||
20 | return true; | ||
21 | default: | ||
22 | return false; | ||
23 | } | ||
24 | } | ||
25 | |||
26 | static size_t pressed_keysyms_length(xkb_keysym_t *pressed_keysyms) { | ||
27 | size_t n = 0; | ||
28 | for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) { | ||
29 | if (pressed_keysyms[i] != XKB_KEY_NoSymbol) { | ||
30 | ++n; | ||
31 | } | ||
32 | } | ||
33 | return n; | ||
34 | } | ||
35 | |||
36 | static ssize_t pressed_keysyms_index(xkb_keysym_t *pressed_keysyms, | ||
37 | xkb_keysym_t keysym) { | ||
38 | for (size_t i = 0; i < SWAY_KEYBOARD_PRESSED_KEYSYMS_CAP; ++i) { | ||
39 | if (pressed_keysyms[i] == keysym) { | ||
40 | return i; | ||
41 | } | ||
42 | } | ||
43 | return -1; | ||
44 | } | ||
45 | |||
46 | static void pressed_keysyms_add(xkb_keysym_t *pressed_keysyms, | ||
47 | xkb_keysym_t keysym) { | ||
48 | ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym); | ||
49 | if (i < 0) { | ||
50 | i = pressed_keysyms_index(pressed_keysyms, XKB_KEY_NoSymbol); | ||
51 | if (i >= 0) { | ||
52 | pressed_keysyms[i] = keysym; | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | |||
57 | static void pressed_keysyms_remove(xkb_keysym_t *pressed_keysyms, | ||
58 | xkb_keysym_t keysym) { | ||
59 | ssize_t i = pressed_keysyms_index(pressed_keysyms, keysym); | ||
60 | if (i >= 0) { | ||
61 | pressed_keysyms[i] = XKB_KEY_NoSymbol; | ||
62 | } | ||
63 | } | ||
64 | |||
65 | static void pressed_keysyms_update(xkb_keysym_t *pressed_keysyms, | ||
66 | const xkb_keysym_t *keysyms, size_t keysyms_len, | ||
67 | enum wlr_key_state state) { | ||
68 | for (size_t i = 0; i < keysyms_len; ++i) { | ||
69 | if (keysym_is_modifier(keysyms[i])) { | ||
70 | continue; | ||
71 | } | ||
72 | if (state == WLR_KEY_PRESSED) { | ||
73 | pressed_keysyms_add(pressed_keysyms, keysyms[i]); | ||
74 | } else { // WLR_KEY_RELEASED | ||
75 | pressed_keysyms_remove(pressed_keysyms, keysyms[i]); | ||
76 | } | ||
77 | } | ||
78 | } | ||
79 | |||
80 | static bool binding_matches_key_state(struct sway_binding *binding, | ||
81 | enum wlr_key_state key_state) { | ||
82 | if (key_state == WLR_KEY_PRESSED && !binding->release) { | ||
83 | return true; | ||
84 | } | ||
85 | if (key_state == WLR_KEY_RELEASED && binding->release) { | ||
86 | return true; | ||
87 | } | ||
88 | |||
89 | return false; | ||
90 | } | ||
91 | |||
92 | static void keyboard_execute_command(struct sway_keyboard *keyboard, | ||
93 | struct sway_binding *binding) { | ||
94 | wlr_log(L_DEBUG, "running command for binding: %s", | ||
95 | binding->command); | ||
96 | config_clear_handler_context(config); | ||
97 | config->handler_context.seat = keyboard->seat_device->sway_seat; | ||
98 | struct cmd_results *results = execute_command(binding->command, NULL); | ||
99 | if (results->status != CMD_SUCCESS) { | ||
100 | wlr_log(L_DEBUG, "could not run command for binding: %s (%s)", | ||
101 | binding->command, results->error); | ||
102 | } | ||
103 | free_cmd_results(results); | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * Execute a built-in, hardcoded compositor binding. These are triggered from a | ||
108 | * single keysym. | ||
109 | * | ||
110 | * Returns true if the keysym was handled by a binding and false if the event | ||
111 | * should be propagated to clients. | ||
112 | */ | ||
113 | static bool keyboard_execute_compositor_binding(struct sway_keyboard *keyboard, | ||
114 | xkb_keysym_t *pressed_keysyms, uint32_t modifiers, size_t keysyms_len) { | ||
115 | for (size_t i = 0; i < keysyms_len; ++i) { | ||
116 | xkb_keysym_t keysym = pressed_keysyms[i]; | ||
117 | if (keysym >= XKB_KEY_XF86Switch_VT_1 && | ||
118 | keysym <= XKB_KEY_XF86Switch_VT_12) { | ||
119 | if (wlr_backend_is_multi(server.backend)) { | ||
120 | struct wlr_session *session = | ||
121 | wlr_multi_get_session(server.backend); | ||
122 | if (session) { | ||
123 | unsigned vt = keysym - XKB_KEY_XF86Switch_VT_1 + 1; | ||
124 | wlr_session_change_vt(session, vt); | ||
125 | } | ||
126 | } | ||
127 | return true; | ||
128 | } | ||
129 | } | ||
130 | |||
131 | return false; | ||
132 | } | ||
133 | |||
134 | /** | ||
135 | * Execute keyboard bindings bound with `bindysm` for the given keyboard state. | ||
136 | * | ||
137 | * Returns true if the keysym was handled by a binding and false if the event | ||
138 | * should be propagated to clients. | ||
139 | */ | ||
140 | static bool keyboard_execute_bindsym(struct sway_keyboard *keyboard, | ||
141 | xkb_keysym_t *pressed_keysyms, uint32_t modifiers, | ||
142 | enum wlr_key_state key_state) { | ||
143 | // configured bindings | ||
144 | int n = pressed_keysyms_length(pressed_keysyms); | ||
145 | list_t *keysym_bindings = config->current_mode->keysym_bindings; | ||
146 | for (int i = 0; i < keysym_bindings->length; ++i) { | ||
147 | struct sway_binding *binding = keysym_bindings->items[i]; | ||
148 | if (!binding_matches_key_state(binding, key_state) || | ||
149 | modifiers ^ binding->modifiers || | ||
150 | n != binding->keys->length) { | ||
151 | continue; | ||
152 | } | ||
153 | |||
154 | bool match = true; | ||
155 | for (int j = 0; j < binding->keys->length; ++j) { | ||
156 | match = | ||
157 | pressed_keysyms_index(pressed_keysyms, | ||
158 | *(int*)binding->keys->items[j]) >= 0; | ||
159 | |||
160 | if (!match) { | ||
161 | break; | ||
162 | } | ||
163 | } | ||
164 | |||
165 | if (match) { | ||
166 | keyboard_execute_command(keyboard, binding); | ||
167 | return true; | ||
168 | } | ||
169 | } | ||
170 | |||
171 | return false; | ||
172 | } | ||
173 | |||
174 | static bool binding_matches_keycodes(struct wlr_keyboard *keyboard, | ||
175 | struct sway_binding *binding, struct wlr_event_keyboard_key *event) { | ||
176 | assert(binding->bindcode); | ||
177 | |||
178 | uint32_t keycode = event->keycode + 8; | ||
179 | |||
180 | if (!binding_matches_key_state(binding, event->state)) { | ||
181 | return false; | ||
182 | } | ||
183 | |||
184 | uint32_t modifiers = wlr_keyboard_get_modifiers(keyboard); | ||
185 | if (modifiers ^ binding->modifiers) { | ||
186 | return false; | ||
187 | } | ||
188 | |||
189 | // on release, the released key must be in the binding | ||
190 | if (event->state == WLR_KEY_RELEASED) { | ||
191 | bool found = false; | ||
192 | for (int i = 0; i < binding->keys->length; ++i) { | ||
193 | uint32_t binding_keycode = *(uint32_t*)binding->keys->items[i] + 8; | ||
194 | if (binding_keycode == keycode) { | ||
195 | found = true; | ||
196 | break; | ||
197 | } | ||
198 | } | ||
199 | if (!found) { | ||
200 | return false; | ||
201 | } | ||
202 | } | ||
203 | |||
204 | // every keycode in the binding must be present in the pressed keys on the | ||
205 | // keyboard | ||
206 | for (int i = 0; i < binding->keys->length; ++i) { | ||
207 | uint32_t binding_keycode = *(uint32_t*)binding->keys->items[i] + 8; | ||
208 | if (event->state == WLR_KEY_RELEASED && keycode == binding_keycode) { | ||
209 | continue; | ||
210 | } | ||
211 | |||
212 | bool found = false; | ||
213 | for (size_t j = 0; j < keyboard->num_keycodes; ++j) { | ||
214 | xkb_keycode_t keycode = keyboard->keycodes[j] + 8; | ||
215 | if (keycode == binding_keycode) { | ||
216 | found = true; | ||
217 | break; | ||
218 | } | ||
219 | } | ||
220 | |||
221 | if (!found) { | ||
222 | return false; | ||
223 | } | ||
224 | } | ||
225 | |||
226 | // every keycode pressed on the keyboard must be present within the binding | ||
227 | // keys (unless it is a modifier) | ||
228 | for (size_t i = 0; i < keyboard->num_keycodes; ++i) { | ||
229 | xkb_keycode_t keycode = keyboard->keycodes[i] + 8; | ||
230 | bool found = false; | ||
231 | for (int j = 0; j < binding->keys->length; ++j) { | ||
232 | uint32_t binding_keycode = *(uint32_t*)binding->keys->items[j] + 8; | ||
233 | if (binding_keycode == keycode) { | ||
234 | found = true; | ||
235 | break; | ||
236 | } | ||
237 | } | ||
238 | |||
239 | if (!found) { | ||
240 | if (!binding->modifiers) { | ||
241 | return false; | ||
242 | } | ||
243 | |||
244 | // check if it is a modifier, which we know matched from the check | ||
245 | // above | ||
246 | const xkb_keysym_t *keysyms; | ||
247 | int num_keysyms = | ||
248 | xkb_state_key_get_syms(keyboard->xkb_state, | ||
249 | keycode, &keysyms); | ||
250 | if (num_keysyms != 1 || !keysym_is_modifier(keysyms[0])) { | ||
251 | return false; | ||
252 | } | ||
253 | } | ||
254 | } | ||
255 | |||
256 | return true; | ||
257 | } | ||
258 | |||
259 | /** | ||
260 | * Execute keyboard bindings bound with `bindcode` for the given keyboard state. | ||
261 | * | ||
262 | * Returns true if the keysym was handled by a binding and false if the event | ||
263 | * should be propagated to clients. | ||
264 | */ | ||
265 | static bool keyboard_execute_bindcode(struct sway_keyboard *keyboard, | ||
266 | struct wlr_event_keyboard_key *event) { | ||
267 | struct wlr_keyboard *wlr_keyboard = | ||
268 | keyboard->seat_device->input_device->wlr_device->keyboard; | ||
269 | list_t *keycode_bindings = config->current_mode->keycode_bindings; | ||
270 | for (int i = 0; i < keycode_bindings->length; ++i) { | ||
271 | struct sway_binding *binding = keycode_bindings->items[i]; | ||
272 | if (binding_matches_keycodes(wlr_keyboard, binding, event)) { | ||
273 | keyboard_execute_command(keyboard, binding); | ||
274 | return true; | ||
275 | } | ||
276 | } | ||
277 | |||
278 | return false; | ||
279 | } | ||
280 | |||
281 | /** | ||
282 | * Get keysyms and modifiers from the keyboard as xkb sees them. | ||
283 | * | ||
284 | * This uses the xkb keysyms translation based on pressed modifiers and clears | ||
285 | * the consumed modifiers from the list of modifiers passed to keybind | ||
286 | * detection. | ||
287 | * | ||
288 | * On US layout, pressing Alt+Shift+2 will trigger Alt+@. | ||
289 | */ | ||
290 | static size_t keyboard_keysyms_translated(struct sway_keyboard *keyboard, | ||
291 | xkb_keycode_t keycode, const xkb_keysym_t **keysyms, | ||
292 | uint32_t *modifiers) { | ||
293 | struct wlr_input_device *device = | ||
294 | keyboard->seat_device->input_device->wlr_device; | ||
295 | *modifiers = wlr_keyboard_get_modifiers(device->keyboard); | ||
296 | xkb_mod_mask_t consumed = xkb_state_key_get_consumed_mods2( | ||
297 | device->keyboard->xkb_state, keycode, XKB_CONSUMED_MODE_XKB); | ||
298 | *modifiers = *modifiers & ~consumed; | ||
299 | |||
300 | return xkb_state_key_get_syms(device->keyboard->xkb_state, | ||
301 | keycode, keysyms); | ||
302 | } | ||
303 | |||
304 | /** | ||
305 | * Get keysyms and modifiers from the keyboard as if modifiers didn't change | ||
306 | * keysyms. | ||
307 | * | ||
308 | * This avoids the xkb keysym translation based on modifiers considered pressed | ||
309 | * in the state. | ||
310 | * | ||
311 | * This will trigger keybinds such as Alt+Shift+2. | ||
312 | */ | ||
313 | static size_t keyboard_keysyms_raw(struct sway_keyboard *keyboard, | ||
314 | xkb_keycode_t keycode, const xkb_keysym_t **keysyms, | ||
315 | uint32_t *modifiers) { | ||
316 | struct wlr_input_device *device = | ||
317 | keyboard->seat_device->input_device->wlr_device; | ||
318 | *modifiers = wlr_keyboard_get_modifiers(device->keyboard); | ||
319 | |||
320 | xkb_layout_index_t layout_index = xkb_state_key_get_layout( | ||
321 | device->keyboard->xkb_state, keycode); | ||
322 | return xkb_keymap_key_get_syms_by_level(device->keyboard->keymap, | ||
323 | keycode, layout_index, 0, keysyms); | ||
324 | } | ||
325 | |||
326 | static void handle_keyboard_key(struct wl_listener *listener, void *data) { | ||
327 | struct sway_keyboard *keyboard = | ||
328 | wl_container_of(listener, keyboard, keyboard_key); | ||
329 | struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat; | ||
330 | struct wlr_input_device *wlr_device = | ||
331 | keyboard->seat_device->input_device->wlr_device; | ||
332 | struct wlr_event_keyboard_key *event = data; | ||
333 | |||
334 | xkb_keycode_t keycode = event->keycode + 8; | ||
335 | bool handled = false; | ||
336 | |||
337 | // handle keycodes | ||
338 | handled = keyboard_execute_bindcode(keyboard, event); | ||
339 | |||
340 | // handle translated keysyms | ||
341 | if (!handled && event->state == WLR_KEY_RELEASED) { | ||
342 | handled = keyboard_execute_bindsym(keyboard, | ||
343 | keyboard->pressed_keysyms_translated, | ||
344 | keyboard->modifiers_translated, | ||
345 | event->state); | ||
346 | } | ||
347 | const xkb_keysym_t *translated_keysyms; | ||
348 | size_t translated_keysyms_len = | ||
349 | keyboard_keysyms_translated(keyboard, keycode, &translated_keysyms, | ||
350 | &keyboard->modifiers_translated); | ||
351 | pressed_keysyms_update(keyboard->pressed_keysyms_translated, | ||
352 | translated_keysyms, translated_keysyms_len, event->state); | ||
353 | if (!handled && event->state == WLR_KEY_PRESSED) { | ||
354 | handled = keyboard_execute_bindsym(keyboard, | ||
355 | keyboard->pressed_keysyms_translated, | ||
356 | keyboard->modifiers_translated, | ||
357 | event->state); | ||
358 | } | ||
359 | |||
360 | // Handle raw keysyms | ||
361 | if (!handled && event->state == WLR_KEY_RELEASED) { | ||
362 | handled = keyboard_execute_bindsym(keyboard, | ||
363 | keyboard->pressed_keysyms_raw, keyboard->modifiers_raw, | ||
364 | event->state); | ||
365 | } | ||
366 | const xkb_keysym_t *raw_keysyms; | ||
367 | size_t raw_keysyms_len = | ||
368 | keyboard_keysyms_raw(keyboard, keycode, &raw_keysyms, &keyboard->modifiers_raw); | ||
369 | pressed_keysyms_update(keyboard->pressed_keysyms_raw, raw_keysyms, | ||
370 | raw_keysyms_len, event->state); | ||
371 | if (!handled && event->state == WLR_KEY_PRESSED) { | ||
372 | handled = keyboard_execute_bindsym(keyboard, | ||
373 | keyboard->pressed_keysyms_raw, keyboard->modifiers_raw, | ||
374 | event->state); | ||
375 | } | ||
376 | |||
377 | // Compositor bindings | ||
378 | if (!handled && event->state == WLR_KEY_PRESSED) { | ||
379 | handled = | ||
380 | keyboard_execute_compositor_binding(keyboard, | ||
381 | keyboard->pressed_keysyms_translated, | ||
382 | keyboard->modifiers_translated, | ||
383 | translated_keysyms_len); | ||
384 | } | ||
385 | if (!handled && event->state == WLR_KEY_PRESSED) { | ||
386 | handled = | ||
387 | keyboard_execute_compositor_binding(keyboard, | ||
388 | keyboard->pressed_keysyms_raw, keyboard->modifiers_raw, | ||
389 | raw_keysyms_len); | ||
390 | } | ||
391 | |||
392 | if (!handled || event->state == WLR_KEY_RELEASED) { | ||
393 | wlr_seat_set_keyboard(wlr_seat, wlr_device); | ||
394 | wlr_seat_keyboard_notify_key(wlr_seat, event->time_msec, | ||
395 | event->keycode, event->state); | ||
396 | } | ||
397 | } | ||
398 | |||
399 | static void handle_keyboard_modifiers(struct wl_listener *listener, | ||
400 | void *data) { | ||
401 | struct sway_keyboard *keyboard = | ||
402 | wl_container_of(listener, keyboard, keyboard_modifiers); | ||
403 | struct wlr_seat *wlr_seat = keyboard->seat_device->sway_seat->wlr_seat; | ||
404 | struct wlr_input_device *wlr_device = | ||
405 | keyboard->seat_device->input_device->wlr_device; | ||
406 | wlr_seat_set_keyboard(wlr_seat, wlr_device); | ||
407 | wlr_seat_keyboard_notify_modifiers(wlr_seat, &wlr_device->keyboard->modifiers); | ||
408 | } | ||
409 | |||
410 | struct sway_keyboard *sway_keyboard_create(struct sway_seat *seat, | ||
411 | struct sway_seat_device *device) { | ||
412 | struct sway_keyboard *keyboard = | ||
413 | calloc(1, sizeof(struct sway_keyboard)); | ||
414 | if (!sway_assert(keyboard, "could not allocate sway keyboard")) { | ||
415 | return NULL; | ||
416 | } | ||
417 | |||
418 | keyboard->seat_device = device; | ||
419 | device->keyboard = keyboard; | ||
420 | |||
421 | wl_list_init(&keyboard->keyboard_key.link); | ||
422 | wl_list_init(&keyboard->keyboard_modifiers.link); | ||
423 | |||
424 | return keyboard; | ||
425 | } | ||
426 | |||
427 | void sway_keyboard_configure(struct sway_keyboard *keyboard) { | ||
428 | struct xkb_rule_names rules; | ||
429 | memset(&rules, 0, sizeof(rules)); | ||
430 | struct input_config *input_config = | ||
431 | input_device_get_config(keyboard->seat_device->input_device); | ||
432 | struct wlr_input_device *wlr_device = | ||
433 | keyboard->seat_device->input_device->wlr_device; | ||
434 | |||
435 | if (input_config && input_config->xkb_layout) { | ||
436 | rules.layout = input_config->xkb_layout; | ||
437 | } else { | ||
438 | rules.layout = getenv("XKB_DEFAULT_LAYOUT"); | ||
439 | } | ||
440 | if (input_config && input_config->xkb_model) { | ||
441 | rules.model = input_config->xkb_model; | ||
442 | } else { | ||
443 | rules.model = getenv("XKB_DEFAULT_MODEL"); | ||
444 | } | ||
445 | |||
446 | if (input_config && input_config->xkb_options) { | ||
447 | rules.options = input_config->xkb_options; | ||
448 | } else { | ||
449 | rules.options = getenv("XKB_DEFAULT_OPTIONS"); | ||
450 | } | ||
451 | |||
452 | if (input_config && input_config->xkb_rules) { | ||
453 | rules.rules = input_config->xkb_rules; | ||
454 | } else { | ||
455 | rules.rules = getenv("XKB_DEFAULT_RULES"); | ||
456 | } | ||
457 | |||
458 | if (input_config && input_config->xkb_variant) { | ||
459 | rules.variant = input_config->xkb_variant; | ||
460 | } else { | ||
461 | rules.variant = getenv("XKB_DEFAULT_VARIANT"); | ||
462 | } | ||
463 | |||
464 | struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); | ||
465 | if (!sway_assert(context, "cannot create XKB context")) { | ||
466 | return; | ||
467 | } | ||
468 | |||
469 | struct xkb_keymap *keymap = | ||
470 | xkb_keymap_new_from_names(context, &rules, XKB_KEYMAP_COMPILE_NO_FLAGS); | ||
471 | |||
472 | if (!keymap) { | ||
473 | wlr_log(L_DEBUG, "cannot configure keyboard: keymap does not exist"); | ||
474 | xkb_context_unref(context); | ||
475 | return; | ||
476 | } | ||
477 | |||
478 | xkb_keymap_unref(keyboard->keymap); | ||
479 | keyboard->keymap = keymap; | ||
480 | wlr_keyboard_set_keymap(wlr_device->keyboard, keyboard->keymap); | ||
481 | |||
482 | wlr_keyboard_set_repeat_info(wlr_device->keyboard, 25, 600); | ||
483 | xkb_context_unref(context); | ||
484 | struct wlr_seat *seat = keyboard->seat_device->sway_seat->wlr_seat; | ||
485 | wlr_seat_set_keyboard(seat, wlr_device); | ||
486 | |||
487 | wl_list_remove(&keyboard->keyboard_key.link); | ||
488 | wl_signal_add(&wlr_device->keyboard->events.key, &keyboard->keyboard_key); | ||
489 | keyboard->keyboard_key.notify = handle_keyboard_key; | ||
490 | |||
491 | wl_list_remove(&keyboard->keyboard_modifiers.link); | ||
492 | wl_signal_add( &wlr_device->keyboard->events.modifiers, | ||
493 | &keyboard->keyboard_modifiers); | ||
494 | keyboard->keyboard_modifiers.notify = handle_keyboard_modifiers; | ||
495 | } | ||
496 | |||
497 | void sway_keyboard_destroy(struct sway_keyboard *keyboard) { | ||
498 | if (!keyboard) { | ||
499 | return; | ||
500 | } | ||
501 | wl_list_remove(&keyboard->keyboard_key.link); | ||
502 | wl_list_remove(&keyboard->keyboard_modifiers.link); | ||
503 | free(keyboard); | ||
504 | } | ||