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 XSync(dpy, False);
436
437 rp_draw_string(s, s->input_window, STYLE_NORMAL,
438 defaults.bar_x_padding,
439 defaults.bar_y_padding + FONT_ASCENT(s),
440 line->prompt,
441 -1, NULL, NULL);
442
443 rp_draw_string(s, s->input_window, STYLE_NORMAL,
444 defaults.bar_x_padding + prompt_width,
445 defaults.bar_y_padding + FONT_ASCENT(s),
446 line->buffer,
447 line->length, NULL, NULL);
448
449 gcv.function = GXxor;
450 gcv.foreground = rp_glob_screen.fg_color ^ rp_glob_screen.bg_color;
451 lgc = XCreateGC(dpy, s->input_window, GCFunction | GCForeground, &gcv);
452
453 /* Draw a cheap-o cursor - MkIII */
454 XFillRectangle(dpy, s->input_window, lgc,
455 defaults.bar_x_padding + prompt_width +
456 rp_text_width(s, line->buffer, line->position, NULL),
457 defaults.bar_y_padding,
458 rp_text_width(s, &line->buffer[line->position], char_len, NULL),
459 FONT_HEIGHT(s));
460
461 XFlush(dpy);
462 XFreeGC(dpy, lgc);
463}
464
465char *
466get_input(char *prompt, int history_id, completion_fn fn)
467{
468 return get_more_input(prompt, "", history_id, BASIC, fn);
469}
470
471char *
472get_more_input(char *prompt, char *preinput, int history_id,
473 enum completion_styles style, completion_fn compl_fn)
474{
475 /* Emacs 21 uses a 513 byte string to store the keysym name. */
476 char keysym_buf[513];
477 rp_screen *s = rp_current_screen;
478 KeySym ch;
479 unsigned int modifier;
480 rp_input_line *line;
481 char *final_input;
482 edit_status status;
483 Window focus;
484 int revert, done = 0;
485
486 history_reset();
487
488 /* Create our line structure */
489 line = input_line_new(prompt, preinput, history_id, style, compl_fn);
490
491 /* Switch to the default colormap. */
492 if (current_window())
493 XUninstallColormap(dpy, current_window()->colormap);
494 XInstallColormap(dpy, s->def_cmap);
495
496 XMapWindow(dpy, s->input_window);
497 XRaiseWindow(dpy, s->input_window);
498
499 hide_bar(s, 1);
500
501 XClearWindow(dpy, s->input_window);
502 /* Switch focus to our input window to read the next key events. */
503 XGetInputFocus(dpy, &focus, &revert);
504 set_window_focus(s->input_window);
505 XSync(dpy, False);
506
507 update_input_window(s, line);
508
509 while (!done) {
510 read_key(&ch, &modifier, keysym_buf, sizeof(keysym_buf));
511 modifier = x11_mask_to_rp_mask(modifier);
512 PRINT_INPUT_DEBUG(("ch = %ld, modifier = %d, keysym_buf = %s\n",
513 ch, modifier, keysym_buf));
514 status = execute_edit_action(line, ch, modifier, keysym_buf);
515
516 switch (status) {
517 case EDIT_COMPLETE:
518 case EDIT_DELETE:
519 case EDIT_INSERT:
520 case EDIT_MOVE:
521 /*
522 * If the text changed (and we didn't just complete
523 * something) then set the virgin bit.
524 */
525 if (status != EDIT_COMPLETE)
526 line->compl->virgin = 1;
527 /* In all cases, we need to redisplay the input string. */
528 update_input_window(s, line);
529 break;
530 case EDIT_NO_OP:
531 break;
532 case EDIT_ABORT:
533 final_input = NULL;
534 done = 1;
535 break;
536 case EDIT_DONE:
537 final_input = xstrdup(line->buffer);
538 done = 1;
539 break;
540 default:
541 errx(1, "unhandled input status %d; this is a *BUG*",
542 status);
543 }
544 }
545
546 /* Clean up our line structure */
547 input_line_free(line);
548
549 /* Revert focus. */
550 set_window_focus(focus);
551
552 /* Possibly restore colormap. */
553 if (current_window()) {
554 XUninstallColormap(dpy, s->def_cmap);
555 XInstallColormap(dpy, current_window()->colormap);
556 }
557
558 /* Re-show the sticky bar if we hid it earlier */
559 if (defaults.bar_sticky)
560 hide_bar(s, 0);
561
562 XUnmapWindow(dpy, s->input_window);
563
564 /*
565 * XXX: Without this, the input window doesn't show up again if we need
566 * to prompt the user for more input, until the user types a character.
567 * Figure out what is actually causing this and remove this.
568 */
569 usleep(1);
570
571 return final_input;
572}