/* Metacity Keybindings */ /* * Copyright (C) 2001 Havoc Pennington * * 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. */ #include "keybindings.h" #include "workspace.h" #include "errors.h" #include "ui.h" #include "frame.h" #include "place.h" #include /* Plainly we'll want some more configurable keybinding system * eventually. */ typedef void (* MetaKeyHandler) (MetaDisplay *display, MetaWindow *window, XEvent *event, gpointer data); static void handle_activate_workspace (MetaDisplay *display, MetaWindow *window, XEvent *event, gpointer data); static void handle_activate_menu (MetaDisplay *display, MetaWindow *window, XEvent *event, gpointer data); static void handle_tab_forward (MetaDisplay *display, MetaWindow *window, XEvent *event, gpointer data); static void handle_tab_backward (MetaDisplay *display, MetaWindow *window, XEvent *event, gpointer data); static void handle_focus_previous (MetaDisplay *display, MetaWindow *window, XEvent *event, gpointer data); typedef struct _MetaKeyBinding MetaKeyBinding; struct _MetaKeyBinding { KeySym keysym; gulong mask; int event_type; MetaKeyHandler handler; gpointer data; int keycode; }; #define INTERESTING_MODIFIERS (ShiftMask | ControlMask | Mod1Mask) static MetaKeyBinding screen_bindings[] = { { XK_F1, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (0), 0 }, { XK_F2, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (1), 0 }, { XK_F3, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (2), 0 }, { XK_F4, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (3), 0 }, { XK_F5, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (4), 0 }, { XK_F6, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (5), 0 }, { XK_1, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (0), 0 }, { XK_2, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (1), 0 }, { XK_3, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (2), 0 }, { XK_4, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (3), 0 }, { XK_5, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (4), 0 }, { XK_6, Mod1Mask, KeyPress, handle_activate_workspace, GINT_TO_POINTER (5), 0 }, { XK_Tab, Mod1Mask, KeyPress, handle_tab_forward, NULL, 0 }, { XK_ISO_Left_Tab, ShiftMask | Mod1Mask, KeyPress, handle_tab_backward, NULL, 0 }, { XK_Tab, ShiftMask | Mod1Mask, KeyPress, handle_tab_backward, NULL, 0 }, { XK_Escape, Mod1Mask, KeyPress, handle_focus_previous, NULL, 0 }, { None, 0, 0, NULL, NULL, 0 } }; static MetaKeyBinding window_bindings[] = { { XK_space, Mod1Mask, KeyPress, handle_activate_menu, NULL, 0 }, { XK_Tab, Mod1Mask, KeyPress, handle_tab_forward, NULL, 0 }, { XK_ISO_Left_Tab, ShiftMask | Mod1Mask, KeyPress, handle_tab_backward, NULL, 0 }, { XK_Tab, ShiftMask | Mod1Mask, KeyPress, handle_tab_backward, NULL, 0 }, { XK_Escape, Mod1Mask, KeyPress, handle_focus_previous, NULL, 0 }, { None, 0, 0, NULL, NULL, 0 } }; static void init_bindings (MetaDisplay *display, MetaKeyBinding *bindings) { int i; i = 0; while (bindings[i].keysym != None) { bindings[i].keycode = XKeysymToKeycode (display->xdisplay, bindings[i].keysym); ++i; } } void meta_display_init_keys (MetaDisplay *display) { init_bindings (display, screen_bindings); init_bindings (display, window_bindings); } static void grab_keys (MetaKeyBinding *bindings, MetaDisplay *display, Window xwindow) { int i; i = 0; while (bindings[i].keysym != None) { if (bindings[i].keycode != 0) { int result; meta_error_trap_push (display); XGrabKey (display->xdisplay, bindings[i].keycode, bindings[i].mask, xwindow, True, GrabModeAsync, GrabModeAsync); result = meta_error_trap_pop (display); if (result != Success) { const char *name; name = XKeysymToString (bindings[i].keysym); if (name == NULL) name = "(unknown)"; if (result == BadAccess) meta_warning (_("Some other program is already using the key %s as a binding\n"), name); } } ++i; } } static void ungrab_keys (MetaKeyBinding *bindings, MetaDisplay *display, Window xwindow) { int i; i = 0; while (bindings[i].keysym != None) { if (bindings[i].keycode != 0) { meta_error_trap_push (display); XUngrabKey (display->xdisplay, bindings[i].keycode, bindings[i].mask, xwindow); meta_error_trap_pop (display); } ++i; } } void meta_screen_grab_keys (MetaScreen *screen) { grab_keys (screen_bindings, screen->display, screen->xroot); } void meta_screen_ungrab_keys (MetaScreen *screen) { ungrab_keys (screen_bindings, screen->display, screen->xroot); } void meta_window_grab_keys (MetaWindow *window) { if (window->all_keys_grabbed) return; if (window->keys_grabbed) { if (window->frame && !window->grab_on_frame) ungrab_keys (window_bindings, window->display, window->xwindow); else if (window->frame == NULL && window->grab_on_frame) ; /* continue to regrab on client window */ else return; /* already all good */ } /* no keybindings for Emacs ;-) */ if (window->res_class && g_strcasecmp (window->res_class, "Emacs") == 0) return; grab_keys (window_bindings, window->display, window->frame ? window->frame->xwindow : window->xwindow); window->keys_grabbed = TRUE; window->grab_on_frame = window->frame != NULL; } void meta_window_ungrab_keys (MetaWindow *window) { if (window->keys_grabbed) { if (window->grab_on_frame && window->frame != NULL) ungrab_keys (window_bindings, window->display, window->frame->xwindow); else if (!window->grab_on_frame) ungrab_keys (window_bindings, window->display, window->xwindow); } } gboolean meta_window_grab_all_keys (MetaWindow *window) { int result; Window grabwindow; if (window->all_keys_grabbed) return FALSE; if (window->keys_grabbed) meta_window_ungrab_keys (window); /* Make sure the window is focused, otherwise the grab * won't do a lot of good. */ meta_window_focus (window, CurrentTime); grabwindow = window->frame ? window->frame->xwindow : window->xwindow; meta_error_trap_push (window->display); XGrabKey (window->display->xdisplay, AnyKey, AnyModifier, grabwindow, True, GrabModeAsync, GrabModeAsync); result = meta_error_trap_pop (window->display); if (result != Success) { meta_verbose ("Global key grab failed for window %s\n", window->desc); return FALSE; } else { window->keys_grabbed = FALSE; window->all_keys_grabbed = TRUE; window->grab_on_frame = window->frame != NULL; return TRUE; } } void meta_window_ungrab_all_keys (MetaWindow *window) { if (window->all_keys_grabbed) { Window grabwindow; grabwindow = (window->frame && window->grab_on_frame) ? window->frame->xwindow : window->xwindow; meta_error_trap_push (window->display); XUngrabKey (window->display->xdisplay, AnyKey, AnyModifier, grabwindow); meta_error_trap_pop (window->display); window->grab_on_frame = FALSE; window->all_keys_grabbed = FALSE; window->keys_grabbed = FALSE; /* Re-establish our standard bindings */ meta_window_grab_keys (window); } } static gboolean is_modifier (MetaDisplay *display, unsigned int keycode) { int i; int map_size; XModifierKeymap *mod_keymap; gboolean retval = FALSE; /* FIXME this is ass-slow, cache the modmap */ mod_keymap = XGetModifierMapping (display->xdisplay); map_size = 8 * mod_keymap->max_keypermod; i = 0; while (i < map_size) { if (keycode == mod_keymap->modifiermap[i]) { retval = TRUE; break; } ++i; } XFreeModifiermap (mod_keymap); return retval; } static void process_event (MetaKeyBinding *bindings, MetaDisplay *display, MetaWindow *window, XEvent *event, KeySym keysym) { int i; i = 0; while (bindings[i].keysym != None) { if (bindings[i].keysym == keysym && ((event->xkey.state & INTERESTING_MODIFIERS) == bindings[i].mask) && bindings[i].event_type == event->type) { (* bindings[i].handler) (display, window, event, bindings[i].data); break; } ++i; } } void meta_display_process_key_event (MetaDisplay *display, MetaWindow *window, XEvent *event) { KeySym keysym; gboolean handled; g_return_if_fail (window != NULL); keysym = XKeycodeToKeysym (display->xdisplay, event->xkey.keycode, 0); meta_verbose ("Processing key %s event, keysym: %s state: 0x%x window: %s\n", event->type == KeyPress ? "press" : "release", XKeysymToString (keysym), event->xkey.state, window->desc); if (!window->all_keys_grabbed) { /* Do the normal keybindings */ process_event (screen_bindings, display, window, event, keysym); process_event (window_bindings, display, window, event, keysym); return; } /* If we get here we have a global grab, because * we're in some special keyboard mode such as window move * mode. */ if (display->grab_op == META_GRAB_OP_NONE) return; /* don't end grabs on modifier key presses */ if (is_modifier (display, event->xkey.keycode)) return; handled = FALSE; if (display->grab_op == META_GRAB_OP_KEYBOARD_MOVING && display->grab_window == window) { int x, y; int incr; gboolean smart_snap; int edge; if (event->type == KeyRelease) return; /* don't care about releases */ if (window == NULL) meta_bug ("NULL window while META_GRAB_OP_MOVING\n"); meta_window_get_position (window, &x, &y); smart_snap = (event->xkey.state & ShiftMask) != 0; #define SMALL_INCREMENT 1 #define NORMAL_INCREMENT 10 if (smart_snap) incr = 0; else if (event->xkey.state & ControlMask) incr = SMALL_INCREMENT; else incr = NORMAL_INCREMENT; /* When moving by increments, we still snap to edges if the move * to the edge is smaller than the increment. This is because * Shift + arrow to snap is sort of a hidden feature. This way * people using just arrows shouldn't get too frustrated. */ switch (keysym) { case XK_Up: case XK_KP_Up: edge = meta_window_find_next_horizontal_edge (window, FALSE); y -= incr; if (smart_snap || ((edge > y) && ABS (edge - y) < incr)) y = edge; handled = TRUE; break; case XK_Down: case XK_KP_Down: edge = meta_window_find_next_horizontal_edge (window, TRUE); y += incr; if (smart_snap || ((edge < y) && ABS (edge - y) < incr)) y = edge; handled = TRUE; break; case XK_Left: case XK_KP_Left: edge = meta_window_find_next_vertical_edge (window, FALSE); x -= incr; if (smart_snap || ((edge > x) && ABS (edge - x) < incr)) x = edge; handled = TRUE; break; case XK_Right: case XK_KP_Right: edge = meta_window_find_next_vertical_edge (window, TRUE); x += incr; if (smart_snap || ((edge < x) && ABS (edge - x) < incr)) x = edge; handled = TRUE; break; case XK_Escape: /* End move and restore to original position */ meta_window_move_resize (display->grab_window, display->grab_initial_window_pos.x, display->grab_initial_window_pos.y, display->grab_initial_window_pos.width, display->grab_initial_window_pos.height); break; default: break; } if (handled) meta_window_move (window, x, y); } /* end grab if a key that isn't used gets pressed */ if (!handled) { meta_verbose ("Ending grab op %d on key press event sym %s\n", display->grab_op, XKeysymToString (keysym)); meta_display_end_grab_op (display, event->xkey.time); } } static void handle_activate_workspace (MetaDisplay *display, MetaWindow *event_window, XEvent *event, gpointer data) { int which; MetaWorkspace *workspace; which = GPOINTER_TO_INT (data); workspace = meta_display_get_workspace_by_index (display, which); if (workspace) { Window move_frame; MetaWindow *move_window; move_window = NULL; move_frame = meta_ui_get_moving_frame (workspace->screen->ui); if (move_frame != None) { move_window = meta_display_lookup_x_window (display, move_frame); if (move_window == NULL || move_window->frame == NULL) meta_bug ("No move_frame window 0x%lx!\n", move_frame); if (move_window->on_all_workspaces) move_window = NULL; /* don't move it after all */ /* We put the window on the new workspace, flip spaces, * then remove from old workspace, so the window * never gets unmapped and we maintain the button grab * on it. */ if (move_window) { if (!meta_workspace_contains_window (workspace, move_window)) meta_workspace_add_window (workspace, move_window); } } meta_workspace_activate (workspace); if (move_window) { /* Lamely rely on prepend */ g_assert (move_window->workspaces->data == workspace); while (move_window->workspaces->next) /* while list size > 1 */ meta_workspace_remove_window (move_window->workspaces->next->data, move_window); } } else { /* We could offer to create it I suppose */ } } static void handle_activate_menu (MetaDisplay *display, MetaWindow *event_window, XEvent *event, gpointer data) { if (display->focus_window) { int x, y; meta_window_get_position (display->focus_window, &x, &y); meta_window_show_menu (display->focus_window, x, y, 0, event->xkey.time); } } static void handle_tab_forward (MetaDisplay *display, MetaWindow *event_window, XEvent *event, gpointer data) { MetaWindow *window; meta_verbose ("Tab forward\n"); window = NULL; if (display->focus_window != NULL) { window = meta_stack_get_tab_next (display->focus_window->screen->stack, display->focus_window, FALSE); } if (window == NULL) { MetaScreen *screen; screen = meta_display_screen_for_root (display, event->xkey.root); /* We get the screen because event_window may be NULL, * in which case we can't use event_window->screen */ if (screen) { window = meta_stack_get_tab_next (screen->stack, event_window, FALSE); } } if (window) { meta_window_raise (window); meta_window_focus (window, event->xkey.time); } } static void handle_tab_backward (MetaDisplay *display, MetaWindow *event_window, XEvent *event, gpointer data) { MetaWindow *window; meta_verbose ("Tab backward\n"); window = NULL; if (display->focus_window != NULL) { window = meta_stack_get_tab_next (display->focus_window->screen->stack, display->focus_window, TRUE); } if (window == NULL) { MetaScreen *screen; screen = meta_display_screen_for_root (display, event->xkey.root); /* We get the screen because event_window may be NULL, * in which case we can't use event_window->screen */ if (screen) { window = meta_stack_get_tab_next (screen->stack, event_window, TRUE); } } if (window) { meta_window_raise (window); meta_window_focus (window, event->xkey.time); } } static void handle_focus_previous (MetaDisplay *display, MetaWindow *event_window, XEvent *event, gpointer data) { MetaWindow *window; meta_verbose ("Focus previous window\n"); window = display->prev_focus_window; if (window == NULL) { /* Pick first window in tab order */ MetaScreen *screen; screen = meta_display_screen_for_root (display, event->xkey.root); /* We get the screen because event_window may be NULL, * in which case we can't use event_window->screen */ if (screen) { window = meta_stack_get_tab_next (screen->stack, event_window, TRUE); } } if (window) { meta_window_raise (window); meta_window_focus (window, event->xkey.time); } }