A tiling window manager
1/*
2 * Read keyboard input from the user.
3 * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts <sabetts@vcn.bc.ca>
4 *
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA.
18 */
19
20#include <stdlib.h>
21#include <stdio.h>
22#include <string.h>
23#include <unistd.h>
24#include <err.h>
25#include <X11/Xlib.h>
26#include <X11/keysym.h>
27#include <X11/Xutil.h>
28#include <X11/XKBlib.h>
29
30#include "sdorfehs.h"
31
32/*
33 * Convert an X11 modifier mask to the rp modifier mask equivalent, as best it
34 * can (the X server may not have a hyper key defined, for instance).
35 */
36unsigned int
37x11_mask_to_rp_mask(unsigned int mask)
38{
39 unsigned int result = 0;
40
41 PRINT_INPUT_DEBUG(("x11 mask = %x\n", mask));
42
43 result |= mask & ShiftMask ? RP_SHIFT_MASK : 0;
44 result |= mask & ControlMask ? RP_CONTROL_MASK : 0;
45 result |= mask & rp_modifier_info.meta_mod_mask ? RP_META_MASK : 0;
46 result |= mask & rp_modifier_info.alt_mod_mask ? RP_ALT_MASK : 0;
47 result |= mask & rp_modifier_info.hyper_mod_mask ? RP_HYPER_MASK : 0;
48 result |= mask & rp_modifier_info.super_mod_mask ? RP_SUPER_MASK : 0;
49
50 PRINT_INPUT_DEBUG(("rp mask = %x\n", mask));
51
52 return result;
53}
54
55/*
56 * Convert an rp modifier mask to the x11 modifier mask equivalent, as best it
57 * can (the X server may not have a hyper key defined, for instance).
58 */
59unsigned int
60rp_mask_to_x11_mask(unsigned int mask)
61{
62 unsigned int result = 0;
63
64 PRINT_INPUT_DEBUG(("rp mask = %x\n", mask));
65
66 result |= mask & RP_SHIFT_MASK ? ShiftMask : 0;
67 result |= mask & RP_CONTROL_MASK ? ControlMask : 0;
68 result |= mask & RP_META_MASK ? rp_modifier_info.meta_mod_mask : 0;
69 result |= mask & RP_ALT_MASK ? rp_modifier_info.alt_mod_mask : 0;
70 result |= mask & RP_HYPER_MASK ? rp_modifier_info.hyper_mod_mask : 0;
71 result |= mask & RP_SUPER_MASK ? rp_modifier_info.super_mod_mask : 0;
72
73 PRINT_INPUT_DEBUG(("x11 mask = %x\n", result));
74
75 return result;
76}
77
78/* Figure out what keysyms are attached to what modifiers */
79void
80update_modifier_map(void)
81{
82 unsigned int modmasks[] = { Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask,
83 Mod5Mask };
84 int row, col; /* The row and column in the modifier table. */
85 int found_alt_or_meta;
86 XModifierKeymap *mods;
87 int min_code, max_code;
88 int syms_per_code;
89 KeySym *syms;
90
91 rp_modifier_info.meta_mod_mask = 0;
92 rp_modifier_info.alt_mod_mask = 0;
93 rp_modifier_info.super_mod_mask = 0;
94 rp_modifier_info.hyper_mod_mask = 0;
95 rp_modifier_info.num_lock_mask = 0;
96 rp_modifier_info.scroll_lock_mask = 0;
97
98 XDisplayKeycodes(dpy, &min_code, &max_code);
99 syms = XGetKeyboardMapping(dpy,
100 min_code, max_code - min_code + 1,
101 &syms_per_code);
102 mods = XGetModifierMapping(dpy);
103
104 for (row = 3; row < 8; row++) {
105 found_alt_or_meta = 0;
106 for (col = 0; col < mods->max_keypermod; col++) {
107 KeyCode code =
108 mods->modifiermap[(row * mods->max_keypermod) + col];
109 int code_col;
110
111 PRINT_INPUT_DEBUG(("row: %d col: %d code: %d\n", row,
112 col, code));
113
114 if (code == 0)
115 continue;
116
117 /* Are any of this keycode's keysyms a meta key? */
118 for (code_col = 0; code_col < syms_per_code; code_col++) {
119 int sym = syms[((code - min_code) *
120 syms_per_code) + code_col];
121
122 switch (sym) {
123 case XK_Meta_L:
124 case XK_Meta_R:
125 found_alt_or_meta = 1;
126 rp_modifier_info.meta_mod_mask |=
127 modmasks[row - 3];
128 PRINT_INPUT_DEBUG(("Found Meta on %d\n",
129 rp_modifier_info.meta_mod_mask));
130 break;
131
132 case XK_Alt_L:
133 case XK_Alt_R:
134 found_alt_or_meta = 1;
135 rp_modifier_info.alt_mod_mask |=
136 modmasks[row - 3];
137 PRINT_INPUT_DEBUG(("Found Alt on %d\n",
138 rp_modifier_info.alt_mod_mask));
139 break;
140
141 case XK_Super_L:
142 case XK_Super_R:
143 if (!found_alt_or_meta) {
144 rp_modifier_info.super_mod_mask |=
145 modmasks[row - 3];
146 PRINT_INPUT_DEBUG(("Found "
147 "Super on %d\n",
148 rp_modifier_info.super_mod_mask));
149 }
150 code_col = syms_per_code;
151 col = mods->max_keypermod;
152 break;
153
154 case XK_Hyper_L:
155 case XK_Hyper_R:
156 if (!found_alt_or_meta) {
157 rp_modifier_info.hyper_mod_mask |=
158 modmasks[row - 3];
159 PRINT_INPUT_DEBUG(("Found "
160 "Hyper on %d\n",
161 rp_modifier_info.hyper_mod_mask));
162 }
163 code_col = syms_per_code;
164 col = mods->max_keypermod;
165
166 break;
167
168 case XK_Num_Lock:
169 rp_modifier_info.num_lock_mask |=
170 modmasks[row - 3];
171 PRINT_INPUT_DEBUG(("Found NumLock on %d\n",
172 rp_modifier_info.num_lock_mask));
173 break;
174
175 case XK_Scroll_Lock:
176 rp_modifier_info.scroll_lock_mask |=
177 modmasks[row - 3];
178 PRINT_INPUT_DEBUG(("Found ScrollLock on %d\n",
179 rp_modifier_info.scroll_lock_mask));
180 break;
181 default:
182 break;
183 }
184 }
185 }
186 }
187
188 /* Stolen from Emacs 21.0.90 - xterm.c */
189 /* If we couldn't find any meta keys, accept any alt keys as meta keys. */
190 if (!rp_modifier_info.meta_mod_mask) {
191 rp_modifier_info.meta_mod_mask = rp_modifier_info.alt_mod_mask;
192 rp_modifier_info.alt_mod_mask = 0;
193 }
194 /*
195 * If some keys are both alt and meta, make them just meta, not alt.
196 */
197 if (rp_modifier_info.alt_mod_mask & rp_modifier_info.meta_mod_mask) {
198 rp_modifier_info.alt_mod_mask &= ~rp_modifier_info.meta_mod_mask;
199 }
200 XFree((char *) syms);
201 XFreeModifiermap(mods);
202}
203
204/*
205 * we need a keycode + modifier to generate the proper keysym (such as @).
206 * Return 1 if successful, 0 otherwise. This function can fail if a keysym
207 * doesn't map to a keycode.
208 */
209static int
210keysym_to_keycode_mod(KeySym keysym, KeyCode * code, unsigned int *mod)
211{
212 KeySym lower, upper;
213
214 *mod = 0;
215 *code = XKeysymToKeycode(dpy, keysym);
216 lower = XkbKeycodeToKeysym(dpy, *code, 0, 0);
217 upper = XkbKeycodeToKeysym(dpy, *code, 0, 1);
218 /*
219 * If you need to press shift to get the keysym, add the shift mask.
220 */
221 if (upper == keysym && lower != keysym)
222 *mod = ShiftMask;
223
224 return *code != 0;
225}
226
227/*
228 * Grab the key while ignoring annoying modifier keys including caps lock, num
229 * lock, and scroll lock.
230 */
231void
232grab_key(KeySym keysym, unsigned int modifiers, Window grab_window)
233{
234 unsigned int mod_list[8];
235 int i;
236 KeyCode keycode;
237 unsigned int mod;
238
239 /* Convert to a modifier mask that X Windows will understand. */
240 modifiers = rp_mask_to_x11_mask(modifiers);
241 if (!keysym_to_keycode_mod(keysym, &keycode, &mod))
242 return;
243 PRINT_INPUT_DEBUG(("keycode_mod: %ld %d %d\n", keysym, keycode, mod));
244 modifiers |= mod;
245
246 /*
247 * Create a list of all possible combinations of ignored modifiers.
248 * Assumes there are only 3 ignored modifiers.
249 */
250 mod_list[0] = 0;
251 mod_list[1] = LockMask;
252 mod_list[2] = rp_modifier_info.num_lock_mask;
253 mod_list[3] = mod_list[1] | mod_list[2];
254 mod_list[4] = rp_modifier_info.scroll_lock_mask;
255 mod_list[5] = mod_list[1] | mod_list[4];
256 mod_list[6] = mod_list[2] | mod_list[4];
257 mod_list[7] = mod_list[1] | mod_list[2] | mod_list[4];
258
259 /* Grab every combination of ignored modifiers. */
260 for (i = 0; i < 8; i++) {
261 XGrabKey(dpy, keycode, modifiers | mod_list[i],
262 grab_window, True, GrabModeAsync, GrabModeAsync);
263 }
264}
265
266
267/* Return the name of the keysym. caller must free returned pointer */
268char *
269keysym_to_string(KeySym keysym, unsigned int modifier)
270{
271 struct sbuf *name;
272 char *tmp;
273
274 name = sbuf_new(0);
275
276 if (modifier & RP_SHIFT_MASK)
277 sbuf_concat(name, "S-");
278 if (modifier & RP_CONTROL_MASK)
279 sbuf_concat(name, "C-");
280 if (modifier & RP_META_MASK)
281 sbuf_concat(name, "M-");
282 if (modifier & RP_ALT_MASK)
283 sbuf_concat(name, "A-");
284 if (modifier & RP_HYPER_MASK)
285 sbuf_concat(name, "H-");
286 if (modifier & RP_SUPER_MASK)
287 sbuf_concat(name, "s-");
288
289 /*
290 * On solaris machines (perhaps other machines as well) this call can
291 * return NULL. In this case use the "NULL" string.
292 */
293 tmp = XKeysymToString(keysym);
294 if (tmp == NULL)
295 tmp = "NULL";
296
297 sbuf_concat(name, tmp);
298
299 return sbuf_free_struct(name);
300}
301
302/*
303 * Cooks a keycode + modifier into a keysym + modifier. This should be used
304 * anytime meaningful key information is to be extracted from a KeyPress or
305 * KeyRelease event.
306 *
307 * returns the number of bytes in keysym_name. If you are not interested in the
308 * keysym name pass in NULL for keysym_name and 0 for len.
309 */
310int
311cook_keycode(XKeyEvent *ev, KeySym *keysym, unsigned int *mod,
312 char *keysym_name, int len, int ignore_bad_mods)
313{
314 int nbytes;
315 int shift = 0;
316 KeySym lower, upper;
317
318 if (ignore_bad_mods) {
319 ev->state &= ~(LockMask
320 | rp_modifier_info.num_lock_mask
321 | rp_modifier_info.scroll_lock_mask);
322 }
323 if (len > 0)
324 len--;
325 nbytes = XLookupString(ev, keysym_name, len, keysym, NULL);
326
327 /* Null terminate the string (not all X servers do it for us). */
328 if (keysym_name) {
329 keysym_name[nbytes] = '\0';
330 }
331 /* Find out if XLookupString gobbled the shift modifier */
332 if (ev->state & ShiftMask) {
333 lower = XkbKeycodeToKeysym(dpy, ev->keycode, 0, 0);
334 upper = XkbKeycodeToKeysym(dpy, ev->keycode, 0, 1);
335 /*
336 * If the keysym isn't affected by the shift key, then keep the
337 * shift modifier.
338 */
339 if (lower == upper)
340 shift = ShiftMask;
341 }
342 *mod = ev->state;
343 *mod &= (rp_modifier_info.meta_mod_mask
344 | rp_modifier_info.alt_mod_mask
345 | rp_modifier_info.hyper_mod_mask
346 | rp_modifier_info.super_mod_mask
347 | ControlMask
348 | shift);
349
350 return nbytes;
351}
352
353/* Wait for a key and discard it. */
354void
355read_any_key(void)
356{
357 char buffer[513];
358 unsigned int mod;
359 KeySym c;
360
361 read_single_key(&c, &mod, buffer, sizeof(buffer));
362}
363
364/* The same as read_key, but handle focusing the key_window and reverting focus. */
365int
366read_single_key(KeySym *keysym, unsigned int *modifiers, char *keysym_name,
367 int len)
368{
369 Window focus;
370 int revert;
371 int nbytes;
372
373 XGetInputFocus(dpy, &focus, &revert);
374 set_window_focus(rp_current_screen->key_window);
375 nbytes = read_key(keysym, modifiers, keysym_name, len);
376 set_window_focus(focus);
377
378 return nbytes;
379}
380
381int
382read_key(KeySym *keysym, unsigned int *modifiers, char *keysym_name, int len)
383{
384 XEvent ev;
385 int nbytes;
386
387 /* Read a key from the keyboard. */
388 do {
389 XMaskEvent(dpy, KeyPressMask | KeyRelease, &ev);
390 *modifiers = ev.xkey.state;
391 nbytes = cook_keycode(&ev.xkey, keysym, modifiers, keysym_name,
392 len, 0);
393 } while (IsModifierKey(*keysym) || ev.xkey.type == KeyRelease);
394
395 return nbytes;
396}
397
398static void
399update_input_window(rp_screen *s, rp_input_line *line)
400{
401 int prompt_width, input_width, total_width;
402 int char_len = 0, height;
403 GC lgc;
404 XGCValues gcv;
405
406 prompt_width = rp_text_width(s, line->prompt, -1, NULL);
407 input_width = rp_text_width(s, line->buffer, line->length, NULL);
408 total_width = defaults.bar_x_padding * 2 + prompt_width + input_width +
409 MAX_FONT_WIDTH(defaults.font);
410 height = (FONT_HEIGHT(s) + defaults.bar_y_padding * 2);
411
412 if (isu8start(line->buffer[line->position])) {
413 do {
414 char_len++;
415 } while (isu8cont(line->buffer[line->position + char_len]));
416 } else
417 char_len = 1;
418
419 if (total_width < defaults.input_window_size + prompt_width)
420 total_width = defaults.input_window_size + prompt_width;
421
422 if (defaults.bar_sticky) {
423 XWindowAttributes attr;
424 XGetWindowAttributes(dpy, s->bar_window, &attr);
425
426 if (total_width < attr.width)
427 total_width = attr.width;
428 }
429
430 XMoveResizeWindow(dpy, s->input_window,
431 bar_x(s, total_width), bar_y(s, height), total_width,
432 (FONT_HEIGHT(s) + defaults.bar_y_padding * 2));
433
434 XClearWindow(dpy, s->input_window);
435
436 rp_draw_string(s, s->input_window, STYLE_NORMAL,
437 defaults.bar_x_padding,
438 defaults.bar_y_padding + FONT_ASCENT(s),
439 line->prompt,
440 -1, NULL, NULL);
441
442 rp_draw_string(s, s->input_window, STYLE_NORMAL,
443 defaults.bar_x_padding + prompt_width,
444 defaults.bar_y_padding + FONT_ASCENT(s),
445 line->buffer,
446 line->length, NULL, NULL);
447
448 gcv.function = GXxor;
449 gcv.foreground = rp_glob_screen.fgcolor ^ rp_glob_screen.bgcolor;
450 lgc = XCreateGC(dpy, s->input_window, GCFunction | GCForeground, &gcv);
451
452 /* Draw a cheap-o cursor - MkIII */
453 XFillRectangle(dpy, s->input_window, lgc,
454 defaults.bar_x_padding + prompt_width +
455 rp_text_width(s, line->buffer, line->position, NULL),
456 defaults.bar_y_padding,
457 rp_text_width(s, &line->buffer[line->position], char_len, NULL),
458 FONT_HEIGHT(s));
459
460 XFlush(dpy);
461 XFreeGC(dpy, lgc);
462}
463
464char *
465get_input(char *prompt, int history_id, completion_fn fn)
466{
467 return get_more_input(prompt, "", history_id, BASIC, fn);
468}
469
470char *
471get_more_input(char *prompt, char *preinput, int history_id,
472 enum completion_styles style, completion_fn compl_fn)
473{
474 /* Emacs 21 uses a 513 byte string to store the keysym name. */
475 char keysym_buf[513];
476 rp_screen *s = rp_current_screen;
477 KeySym ch;
478 unsigned int modifier;
479 rp_input_line *line;
480 char *final_input;
481 edit_status status;
482 Window focus;
483 int revert, done = 0;
484
485 history_reset();
486
487 /* Create our line structure */
488 line = input_line_new(prompt, preinput, history_id, style, compl_fn);
489
490 /* Switch to the default colormap. */
491 if (current_window())
492 XUninstallColormap(dpy, current_window()->colormap);
493 XInstallColormap(dpy, s->def_cmap);
494
495 XMapWindow(dpy, s->input_window);
496 XRaiseWindow(dpy, s->input_window);
497
498 hide_bar(s, 1);
499
500 XClearWindow(dpy, s->input_window);
501 /* Switch focus to our input window to read the next key events. */
502 XGetInputFocus(dpy, &focus, &revert);
503 set_window_focus(s->input_window);
504 XSync(dpy, False);
505
506 update_input_window(s, line);
507
508 while (!done) {
509 read_key(&ch, &modifier, keysym_buf, sizeof(keysym_buf));
510 modifier = x11_mask_to_rp_mask(modifier);
511 PRINT_INPUT_DEBUG(("ch = %ld, modifier = %d, keysym_buf = %s\n",
512 ch, modifier, keysym_buf));
513 status = execute_edit_action(line, ch, modifier, keysym_buf);
514
515 switch (status) {
516 case EDIT_COMPLETE:
517 case EDIT_DELETE:
518 case EDIT_INSERT:
519 case EDIT_MOVE:
520 /*
521 * If the text changed (and we didn't just complete
522 * something) then set the virgin bit.
523 */
524 if (status != EDIT_COMPLETE)
525 line->compl->virgin = 1;
526 /* In all cases, we need to redisplay the input string. */
527 update_input_window(s, line);
528 break;
529 case EDIT_NO_OP:
530 break;
531 case EDIT_ABORT:
532 final_input = NULL;
533 done = 1;
534 break;
535 case EDIT_DONE:
536 final_input = xstrdup(line->buffer);
537 done = 1;
538 break;
539 default:
540 errx(1, "unhandled input status %d; this is a *BUG*",
541 status);
542 }
543 }
544
545 /* Clean up our line structure */
546 input_line_free(line);
547
548 /* Revert focus. */
549 set_window_focus(focus);
550
551 /* Possibly restore colormap. */
552 if (current_window()) {
553 XUninstallColormap(dpy, s->def_cmap);
554 XInstallColormap(dpy, current_window()->colormap);
555 }
556
557 /* Re-show the sticky bar if we hid it earlier */
558 if (defaults.bar_sticky)
559 hide_bar(s, 0);
560
561 XUnmapWindow(dpy, s->input_window);
562
563 /*
564 * XXX: Without this, the input window doesn't show up again if we need
565 * to prompt the user for more input, until the user types a character.
566 * Figure out what is actually causing this and remove this.
567 */
568 usleep(1);
569
570 return final_input;
571}