/* * Matchbox Window Manager - A lightweight window manager not for the * desktop. * * Authored By Matthew Allum * * Copyright (c) 2002, 2004 OpenedHand Ltd - http://o-hand.com * * SPDX-License-Identifier: GPL-2.0-or-later * */ #include "structs.h" #include "keys.h" #ifndef NO_KBD static Bool keys_keysym_needs_shift(Wm *w, KeySym keysym) { int col; /* XXX double check this is correct / easiest way */ int keycode; KeySym k; int min_kc, max_kc; XDisplayKeycodes(w->dpy, &min_kc, &max_kc); for (keycode = min_kc; keycode <= max_kc; keycode++) { for (col = 0; (k = XKeycodeToKeysym (w->dpy, keycode, col)) != NoSymbol; col++) { if (k == keysym && col == 1) return True; if (k == keysym) break; } } return False; } static KeySym keys_keysym_get_shifted (Wm *w, KeySym keysym) { int min_kc, max_kc, keycode; KeySym k; XDisplayKeycodes (w->dpy, &min_kc, &max_kc); for (keycode = min_kc; keycode <= max_kc; keycode++) if ((k = XKeycodeToKeysym (w->dpy, keycode, 0)) == keysym) return XKeycodeToKeysym (w->dpy, keycode, 1); return NoSymbol; } Bool keys_add_entry(Wm *w, char *keystr, int action, int idata, char *sdata) { char *p = keystr; char *q; int i = 0, mask = 0, index = 0; char *keydef = NULL; Bool want_shift = False; KeySym ks; MBConfigKbdEntry *entry; MBConfigKbd *kb = w->config->kb; struct { char *def; int mask; } lookup[] = { { "ctrl", ControlMask }, { "alt", kb->AltMask }, { "meta", kb->MetaMask }, { "super", kb->SuperMask }, { "hyper", kb->HyperMask }, { "mod1", Mod1Mask }, { "mod2", Mod2Mask }, { "mod3", Mod3Mask }, { "mod4", Mod4Mask }, { "mod5", Mod5Mask }, { NULL, 0 } }; dbg("%s() called, keystr: %s action: %i sdata %s\n", __func__, keystr, action, sdata); while (*p != '\0') { Bool found = False; if (*p == '<') /* XXX check for esacped < */ { q = ++p; while (*q != '\0' && *q != '>') q++; if (*q == '\0') return False; /* Parse error */ i = 0; while (lookup[i].def != NULL && !found) { if (!strncasecmp(p, lookup[i].def, q-p)) { if (lookup[i].mask) { mask |= lookup[i].mask; found = True; } else { fprintf(stderr, "matchbox: keyboard does not appear to have a <%s> key.\n", lookup[i].def); } } i++; } if (found) { p = q; } else { if (!strncasecmp(p, "shift", 5)) { want_shift = True; p = q; } else return False; } } else if (!isspace(*p)) { keydef = p; break; } p++; } if (!keydef) return False; if ((ks = XStringToKeysym(keydef)) == (KeySym)NULL) { if (islower(keydef[0])) /* Try again, changing case */ keydef[0] = toupper(keydef[0]); else keydef[0] = tolower(keydef[0]); if ((ks = XStringToKeysym(keydef)) == (KeySym)NULL) { fprintf(stderr, "matchbox: Cant find keysym for %s\n", keydef); dbg("%s() can find keysym %s\n", __func__, keydef); return False; } } if (keys_keysym_needs_shift(w, ks)) { mask |= ShiftMask; index = 1; } else if (want_shift) { KeySym shifted; /* Change the keysym for case of 'lowerchar' where we * actually want to grab the shifted version of the keysym. */ if ((shifted = keys_keysym_get_shifted (w, ks)) != NoSymbol) { ks = shifted; index = 1; } /* Set the mask even if no shifted version - up key for * example. */ mask |= ShiftMask; } dbg("%s() keydefinition is %s, want_shift is %i\n", __func__, keydef, mask & ShiftMask); /* If we grab keycode 0, we end up grabbing the entire keyboard :\ */ if (XKeysymToKeycode(w->dpy, ks) == 0 && mask == 0) { fprintf(stderr, "matchbox: Cant find a keycode for keysym %li\n", ks); return False; } if (w->config->kb->entrys == NULL) { w->config->kb->entrys = malloc(sizeof(MBConfigKbdEntry)); entry = w->config->kb->entrys; } else { entry = w->config->kb->entrys; while (entry->next_entry != NULL) entry = entry->next_entry; entry->next_entry = malloc(sizeof(MBConfigKbdEntry)); entry = entry->next_entry; } entry->next_entry = NULL; entry->action = action; entry->ModifierMask = mask; entry->index = index; entry->key = ks; entry->idata = idata; entry->sdata = (( sdata == NULL ) ? NULL : strdup(sdata)); dbg("added new key entry mask: %i\n", entry->ModifierMask); return True; } Bool keys_load_config(Wm *w) { #ifdef USE_GCONF int i = 0; char *key_def_str = NULL; char gconf_key_cmd_keys[256] = { 0 }; char gconf_key_cmd[256] = { 0 }; #define KEY_CMD_ENTRYS_N 12 struct { char *key; int action; } lookup[] = { { "/apps/matchbox/global_keybindings/cycle_windows", KEY_ACTN_NEXT_CLIENT }, { "/apps/matchbox/global_keybindings/cycle_windows_backward", KEY_ACTN_PREV_CLIENT }, { "/apps/matchbox/window_keybindings/close", KEY_ACTN_CLOSE_CLIENT }, { "/apps/matchbox/global_keybindings/show_desktop", KEY_ACTN_TOGGLE_DESKTOP }, { "/apps/matchbox/window_keybindings/activate_window_menu", KEY_ACTN_TASK_MENU_ACTIVATE }, { "/apps/matchbox/window_keybindings/toggle_fullscreen", KEY_ACTN_FULLSCREEN }, { "/apps/matchbox/window_keybindings/toggle_titlebar", KEY_ACTN_HIDE_TITLEBAR }, { NULL, 0 } }; while (lookup[i].key != NULL) { key_def_str = gconf_client_get_string(w->gconf_client, lookup[i].key, NULL); if (key_def_str && strcasecmp(key_def_str, "disabled")) keys_add_entry(w, key_def_str, lookup[i].action, 0, NULL); i++; } /* Add any command entrys */ for (i=0; igconf_client, gconf_key_cmd, NULL); cmd_keys = gconf_client_get_string(w->gconf_client, gconf_key_cmd_keys, NULL); if (cmd_value && cmd_keys && strcasecmp(cmd_value, "disabled") && strcasecmp(cmd_keys, "disabled") ) { #ifdef USE_LIBSN if (*cmd_value == '!') { action = KEY_ACTN_EXEC_SN; cmd_value++; } else if (*cmd_value == '$') { action = KEY_ACTN_EXEC_SINGLE; cmd_value++; } else #else if ((*(cmd_value+1) == '!') || (*(cmd_value+1) == '$')) cmd_value++; #endif action = KEY_ACTN_EXEC; keys_add_entry(w, cmd_keys, action, 0, cmd_value); } } #else /* No Gconf, Load the shortcuts from a file */ FILE *fp; char data[256]; char *key = NULL, *val = NULL, *str = NULL; int i = 0; struct stat stat_info; char *conf_path = NULL; struct { char *str; int act; } lookup[] = { { "next", KEY_ACTN_NEXT_CLIENT }, { "prev", KEY_ACTN_PREV_CLIENT }, { "close", KEY_ACTN_CLOSE_CLIENT }, { "desktop", KEY_ACTN_TOGGLE_DESKTOP }, { "taskmenu", KEY_ACTN_TASK_MENU_ACTIVATE }, { "fullscreen", KEY_ACTN_FULLSCREEN }, { "hidetitle", KEY_ACTN_HIDE_TITLEBAR }, { NULL, 0 } }; if (w->config->kbd_conf_file != NULL) conf_path = w->config->kbd_conf_file; if (conf_path == NULL && getenv("HOME")) { conf_path = malloc(sizeof(char)*(strlen(getenv("HOME"))+25)); sprintf(conf_path, "%s/.matchbox/kbdconfig", getenv("HOME")); if (stat(conf_path, &stat_info) == -1) { free(conf_path); conf_path = NULL; } } if (conf_path == NULL) conf_path = strdup(CONFDIR "/kbdconfig"); if (!(fp = fopen(conf_path, "r"))) { free(conf_path); return False; } dbg("%s() parsing keyboard config: %s\n", __func__, conf_path); free(conf_path); while(fgets(data,256,fp) != NULL) { if (data[0] == '#') continue; str = strdup(data); if ( (val = strchr(str, '=')) != NULL) { *val++ = '\0'; key = str; if (*val != '\0') { int action = 0; if (val[strlen(val)-1] == '\n') val[strlen(val)-1] = '\0'; if (*val == '!') { /* It some kind of exec */ val++; #ifdef USE_LIBSN if (*val == '!') { action = KEY_ACTN_EXEC_SN; val++; } else if (*val == '$') { action = KEY_ACTN_EXEC_SINGLE; val++; } else #else if ((*(val+1) == '!') || (*(val+1) == '$')) val++; #endif action = KEY_ACTN_EXEC; } else { i = 0; while (lookup[i].str != NULL) { if (!strncasecmp(lookup[i].str, val, strlen(lookup[i].str))) { action = lookup[i].act; break; } i++; } } if (!(action && keys_add_entry(w, key, action, 0, val))) fprintf(stderr, "matchbox: ignoring key shortcut %s\n", data); } } free(str); } fclose(fp); #endif return 1; } void keys_get_modifiers(Wm *w) { int mod_idx, mod_key, col, kpm; XModifierKeymap *mod_map = XGetModifierMapping(w->dpy); MBConfigKbd *kbd = w->config->kb; kbd->MetaMask = 0; kbd->HyperMask = 0; kbd->SuperMask = 0; kbd->AltMask = 0; kbd->ModeMask = 0; kbd->NumLockMask = 0; kbd->ScrollLockMask = 0; kpm = mod_map->max_keypermod; for (mod_idx = 0; mod_idx < 8; mod_idx++) for (mod_key = 0; mod_key < kpm; mod_key++) { KeySym last_sym = 0; for (col = 0; col < 4; col += 2) { KeyCode code = mod_map->modifiermap[mod_idx * kpm + mod_key]; KeySym sym = (code ? XKeycodeToKeysym(w->dpy, code, col) : 0); if (sym == last_sym) continue; last_sym = sym; switch (sym) { case XK_Mode_switch: /* XXX store_modifier("Mode_switch", mode_bit); */ break; case XK_Meta_L: case XK_Meta_R: kbd->MetaMask |= (1 << mod_idx); break; case XK_Super_L: case XK_Super_R: kbd->SuperMask |= (1 << mod_idx); break; case XK_Hyper_L: case XK_Hyper_R: kbd->HyperMask |= (1 << mod_idx); break; case XK_Alt_L: case XK_Alt_R: kbd->AltMask |= (1 << mod_idx); break; case XK_Num_Lock: kbd->NumLockMask |= (1 << mod_idx); break; case XK_Scroll_Lock: kbd->ScrollLockMask |= (1 << mod_idx); break; } } } /* XXX check this. assume alt <=> meta if only either set */ if (!kbd->AltMask) kbd->AltMask = kbd->MetaMask; if (!kbd->MetaMask) kbd->MetaMask = kbd->AltMask; kbd->lock_mask = kbd->ScrollLockMask | kbd->NumLockMask | LockMask; if (mod_map) XFreeModifiermap(mod_map); } void keys_grab(Wm *w, Bool ungrab) { MBConfigKbdEntry *entry = w->config->kb->entrys; while (entry != NULL) { int ignored_mask = 0; /* Needed to grab all ignored combo's too */ while (ignored_mask <= (int) w->config->kb->lock_mask) { if (ignored_mask & ~(w->config->kb->lock_mask)) { ++ignored_mask; continue; } if (ungrab) { dbg("keys, ungrabbing %i , %i\n", XKeysymToKeycode(w->dpy, entry->key), entry->ModifierMask); XUngrabKey(w->dpy, XKeysymToKeycode(w->dpy, entry->key), entry->ModifierMask | ignored_mask, w->root); } else { int result; misc_trap_xerrors(); dbg("keys, grabbing keycode: %i , mask: %i\n", XKeysymToKeycode(w->dpy, entry->key), entry->ModifierMask | ignored_mask); XGrabKey(w->dpy, XKeysymToKeycode(w->dpy, entry->key), entry->ModifierMask | ignored_mask, w->root, True, GrabModeAsync, GrabModeAsync); result = misc_untrap_xerrors(); if (result != Success) { if (result == BadAccess) fprintf(stderr, "matchbox: Some other program is already using the key %s with modifiers %x as a binding\n", (XKeysymToString(entry->key)) ? XKeysymToString (entry->key) : "unknown", entry->ModifierMask | ignored_mask ); else fprintf(stderr, "matchbox: Unable to grab the key %s with modifiers %x as a binding\n", (XKeysymToString(entry->key)) ? XKeysymToString (entry->key) : "unknown", entry->ModifierMask | ignored_mask ); } } ++ignored_mask; } entry = entry->next_entry; } } static void keys_entries_free(Wm *w) { MBConfigKbdEntry *cur = w->config->kb->entrys, *entry = NULL; while (cur != NULL) { entry = cur; if (entry->sdata) free(entry->sdata); cur = entry->next_entry; free(entry); } w->config->kb->entrys = NULL; } static void keys_load_and_grab(Wm *w) { keys_get_modifiers(w); if (!keys_load_config(w)) { fprintf(stderr, "matchbox: failed to load keyboard config\n"); return; } keys_grab(w, False); } void keys_reinit(Wm *w) { keys_grab(w, True); keys_entries_free(w); keys_load_and_grab(w); } void keys_init(Wm *w) { w->config->kb = malloc(sizeof(MBConfigKbd)); w->config->kb->entrys = NULL; keys_load_and_grab(w); } #endif