diff --git a/src/Makefile.am b/src/Makefile.am index ff3c30ccc..eedecffcf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -51,6 +51,7 @@ mutter_SOURCES= \ include/meta-shadow-factory.h \ include/meta-window-actor.h \ include/compositor-mutter.h \ + core/above-tab-keycode.c \ core/constraints.c \ core/constraints.h \ core/core.c \ diff --git a/src/core/above-tab-keycode.c b/src/core/above-tab-keycode.c new file mode 100644 index 000000000..56966ab0f --- /dev/null +++ b/src/core/above-tab-keycode.c @@ -0,0 +1,241 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Find the keycode for the key above the tab key */ +/* + * Copyright 2010 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/* The standard cycle-windows keybinding should be the key above the + * tab key. This will have a different keysym on different keyboards - + * it's the ` (grave) key on US keyboards but something else on many + * other national layouts. So we need to figure out the keycode for + * this key without reference to key symbol. + * + * The "correct" way to do this is to get the XKB geometry from the + * X server, find the Tab key, find the key above the Tab key in the + * same section and use the keycode for that key. This is what I + * implemented here, but unfortunately, fetching the geometry is rather + * slow (It could take 20ms or more.) + * + * If you looking for a way to optimize Mutter startup performance: + * On all Linux systems using evdev the key above TAB will have + * keycode 49. (KEY_GRAVE=41 + the 8 code point offset between + * evdev keysyms and X keysyms.) So a configure option + * --with-above-tab-keycode=49 could be added that bypassed this + * code. It wouldn't work right for displaying Mutter remotely + * to a non-Linux X server, but that is pretty rare. + */ + +#include + +#include + +#include "display-private.h" + +#include + +#ifdef HAVE_XKB +#include +#include + +static guint +compute_above_tab_keycode (Display *xdisplay) +{ + XkbDescPtr keyboard; + XkbGeometryPtr geometry; + int i, j, k; + int tab_keycode; + char *tab_name; + XkbSectionPtr tab_section; + XkbBoundsRec tab_bounds; + XkbKeyPtr best_key = NULL; + guint best_keycode = (guint)-1; + int best_x_dist = G_MAXINT; + int best_y_dist = G_MAXINT; + + /* We need only the Names and the Geometry, but asking for these results + * in the Keyboard information retrieval failing for unknown reasons. + * (Testing with xorg-1.9.1.) So we ask for a part that we don't need + * as well. + */ + keyboard = XkbGetKeyboard (xdisplay, + XkbGBN_ClientSymbolsMask | XkbGBN_KeyNamesMask | XkbGBN_GeometryMask, + XkbUseCoreKbd); + + geometry = keyboard->geom; + + /* There could potentially be multiple keys with the Tab keysym on the keyboard; + * but XKeysymToKeycode() returns us the one that the alt-Tab binding will + * use which is good enough + */ + tab_keycode = XKeysymToKeycode (xdisplay, XK_Tab); + if (tab_keycode == 0 || tab_keycode < keyboard->min_key_code || tab_keycode > keyboard->max_key_code) + goto out; + + /* The keyboard geometry is stored by key "name" rather than keycode. + * (Key names are 4-character strings like like TAB or AE01.) We use the + * 'names' part of the keyboard description to map keycode to key name. + * + * XKB has a "key aliases" feature where a single keyboard key can have + * multiple names (with separate sets of aliases in the 'names' part and + * in the 'geometry' part), but I don't really understand it or how it is used, + * so I'm ignoring it here. + */ + + tab_name = keyboard->names->keys[tab_keycode].name; /* Not NULL terminated! */ + + /* First, iterate through the keyboard geometry to find the tab key; the keyboard + * geometry has a three-level heirarchy of section > row > key + */ + for (i = 0; i < geometry->num_sections; i++) + { + XkbSectionPtr section = &geometry->sections[i]; + for (j = 0; j < section->num_rows; j++) + { + int x = 0; + int y = 0; + + XkbRowPtr row = §ion->rows[j]; + for (k = 0; k < row->num_keys; k++) + { + XkbKeyPtr key = &row->keys[k]; + XkbShapePtr shape = XkbKeyShape (geometry, key); + + if (row->vertical) + y += key->gap; + else + x += key->gap; + + if (strncmp (key->name.name, tab_name, XkbKeyNameLength) == 0) + { + tab_section = section; + tab_bounds = shape->bounds; + tab_bounds.x1 += row->left + x; + tab_bounds.x2 += row->left + x; + tab_bounds.y1 += row->top + y; + tab_bounds.y2 += row->top + y; + + goto found_tab; + } + + if (row->vertical) + y += (shape->bounds.y2 - shape->bounds.y1); + else + x += (shape->bounds.x2 - shape->bounds.x1); + } + } + } + + /* No tab key found */ + goto out; + + found_tab: + + /* Now find the key that: + * - Is in the same section as the Tab key + * - Has a horizontal center in the Tab key's horizonal bounds + * - Is above the Tab key at a distance closer than any other key + * - In case of ties, has its horizontal center as close as possible + * to the Tab key's horizontal center + */ + for (j = 0; j < tab_section->num_rows; j++) + { + int x = 0; + int y = 0; + + XkbRowPtr row = &tab_section->rows[j]; + for (k = 0; k < row->num_keys; k++) + { + XkbKeyPtr key = &row->keys[k]; + XkbShapePtr shape = XkbKeyShape(geometry, key); + XkbBoundsRec bounds = shape->bounds; + int x_center; + int x_dist, y_dist; + + if (row->vertical) + y += key->gap; + else + x += key->gap; + + bounds.x1 += row->left + x; + bounds.x2 += row->left + x; + bounds.y1 += row->top + y; + bounds.y2 += row->top + y; + + y_dist = tab_bounds.y1 - bounds.y2; + if (y_dist < 0) + continue; + + x_center = (bounds.x1 + bounds.x2) / 2; + if (x_center < tab_bounds.x1 || x_center > tab_bounds.x2) + continue; + + x_dist = ABS (x_center - (tab_bounds.x1 + tab_bounds.x2) / 2); + + if (y_dist < best_y_dist || + (y_dist == best_y_dist && x_dist < best_x_dist)) + { + best_key = key; + best_x_dist = x_dist; + best_y_dist = y_dist; + } + + if (row->vertical) + y += (shape->bounds.y2 - shape->bounds.y1); + else + x += (shape->bounds.x2 - shape->bounds.x1); + } + } + + if (best_key == NULL) + goto out; + + /* Now we need to resolve the name of the best key back to a keycode */ + for (i = keyboard->min_key_code; i < keyboard->max_key_code; i++) + { + if (strncmp (best_key->name.name, keyboard->names->keys[i].name, XkbKeyNameLength) == 0) + { + best_keycode = i; + break; + } + } + + out: + XkbFreeKeyboard (keyboard, 0, True); + + return best_keycode; +} +#else /* !HAVE_XKB */ +static guint +compute_above_tab_keycode (Display *xdisplay) +{ + return XKeysymToKeycode (xdisplay, XK_grave); +} +#endif /* HAVE_XKB */ + +guint +meta_display_get_above_tab_keycode (MetaDisplay *display) +{ + if (display->above_tab_keycode == 0) /* not yet computed */ + display->above_tab_keycode = compute_above_tab_keycode (display->xdisplay); + + if (display->above_tab_keycode == (guint)-1) /* failed to compute */ + return 0; + else + return display->above_tab_keycode; +} diff --git a/src/core/display-private.h b/src/core/display-private.h index b7a79cb20..faa803ead 100644 --- a/src/core/display-private.h +++ b/src/core/display-private.h @@ -221,6 +221,7 @@ struct _MetaDisplay KeySym *keymap; int keysyms_per_keycode; XModifierKeymap *modmap; + unsigned int above_tab_keycode; unsigned int ignored_modifier_mask; unsigned int num_lock_mask; unsigned int scroll_lock_mask; @@ -433,4 +434,7 @@ void meta_display_remove_autoraise_callback (MetaDisplay *display); void meta_display_overlay_key_activate (MetaDisplay *display); +/* In above-tab-keycode.c */ +guint meta_display_get_above_tab_keycode (MetaDisplay *display); + #endif diff --git a/src/core/keybindings.c b/src/core/keybindings.c index 6d93e9733..b280233a2 100644 --- a/src/core/keybindings.c +++ b/src/core/keybindings.c @@ -117,6 +117,10 @@ reload_keymap (MetaDisplay *display) if (display->keymap) meta_XFree (display->keymap); + /* This is expensive to compute, so we'll lazily load if and when we first + * need it */ + display->above_tab_keycode = 0; + display->keymap = XGetKeyboardMapping (display->xdisplay, display->min_keycode, display->max_keycode - @@ -228,6 +232,16 @@ reload_modmap (MetaDisplay *display) display->meta_mask); } +static guint +keysym_to_keycode (MetaDisplay *display, + guint keysym) +{ + if (keysym == META_KEY_ABOVE_TAB) + return meta_display_get_above_tab_keycode (display); + else + return XKeysymToKeycode (display->xdisplay, keysym); +} + static void reload_keycodes (MetaDisplay *display) { @@ -236,8 +250,8 @@ reload_keycodes (MetaDisplay *display) if (display->overlay_key_combo.keysym != 0) { - display->overlay_key_combo.keycode = XKeysymToKeycode ( - display->xdisplay, display->overlay_key_combo.keysym); + display->overlay_key_combo.keycode = + keysym_to_keycode (display, display->overlay_key_combo.keysym); } if (display->key_bindings) @@ -249,8 +263,8 @@ reload_keycodes (MetaDisplay *display) { if (display->key_bindings[i].keysym != 0) { - display->key_bindings[i].keycode = XKeysymToKeycode ( - display->xdisplay, display->key_bindings[i].keysym); + display->key_bindings[i].keycode = + keysym_to_keycode (display, display->key_bindings[i].keysym); } ++i; diff --git a/src/include/all-keybindings.h b/src/include/all-keybindings.h index f435c6e45..643a123bc 100644 --- a/src/include/all-keybindings.h +++ b/src/include/all-keybindings.h @@ -167,7 +167,7 @@ keybind (switch_panels_backward, handle_switch, META_TAB_LIST_DOCKS, "using a popup window")) keybind (cycle_group, handle_cycle, META_TAB_LIST_GROUP, - BINDING_REVERSES, "grave", + BINDING_REVERSES, "Above_Tab", _("Move between windows of an application immediately")) keybind (cycle_group_backward, handle_cycle, META_TAB_LIST_GROUP, REVERSES_AND_REVERSED, NULL, diff --git a/src/include/ui.h b/src/include/ui.h index ecf84701d..064ca3afd 100644 --- a/src/include/ui.h +++ b/src/include/ui.h @@ -157,6 +157,12 @@ void meta_ui_set_current_theme (const char *name, gboolean force_reload); gboolean meta_ui_have_a_theme (void); +/* Not a real key symbol but means "key above the tab key"; this is + * used as the default keybinding for cycle_group. + * 0x2xxxxxxx is a range not used by GDK or X. the remaining digits are + * randomly chosen */ +#define META_KEY_ABOVE_TAB 0x2f7259c9 + gboolean meta_ui_parse_accelerator (const char *accel, unsigned int *keysym, unsigned int *keycode, diff --git a/src/ui/ui.c b/src/ui/ui.c index 4f42822e9..34d0d5ca8 100644 --- a/src/ui/ui.c +++ b/src/ui/ui.c @@ -761,14 +761,50 @@ meta_ui_accelerator_parse (const char *accel, guint *keycode, GdkModifierType *keymask) { + const char *above_tab; + if (accel[0] == '0' && accel[1] == 'x') { *keysym = 0; *keycode = (guint) strtoul (accel, NULL, 16); *keymask = 0; + + return; } - else - gtk_accelerator_parse (accel, keysym, keymask); + + /* The key name 'Above_Tab' is special - it's not an actual keysym name, + * but rather refers to the key above the tab key. In order to use + * the GDK parsing for modifiers in combination with it, we substitute + * it with 'Tab' temporarily before calling gtk_accelerator_parse(). + */ +#define is_word_character(c) (g_ascii_isalnum(c) || ((c) == '_')) +#define ABOVE_TAB "Above_Tab" +#define ABOVE_TAB_LEN 9 + + above_tab = strstr (accel, ABOVE_TAB); + if (above_tab && + (above_tab == accel || !is_word_character (above_tab[-1])) && + !is_word_character (above_tab[ABOVE_TAB_LEN])) + { + char *before = g_strndup (accel, above_tab - accel); + char *after = g_strdup (above_tab + ABOVE_TAB_LEN); + char *replaced = g_strconcat (before, "Tab", after, NULL); + + gtk_accelerator_parse (replaced, NULL, keymask); + + g_free (before); + g_free (after); + g_free (replaced); + + *keysym = META_KEY_ABOVE_TAB; + return; + } + +#undef is_word_character +#undef ABOVE_TAB +#undef ABOVE_TAB_LEN + + gtk_accelerator_parse (accel, keysym, keymask); } gboolean