A tiling window manager
at master 571 lines 16 kB view raw
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}