/*- * Copyright (c) 2001 Jordan DeLong * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the author nor the names of contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include "wm.h" /* * because X11 treats the NumLock and ScrollLock as modifiers, the user's * keybindings wont be grabbed when the locks are on without grabbing for * those also. the masks are available here. */ u_int numlock_mask, scroll_mask; /* our list of keys that we bind */ static SLIST_HEAD(, keybind) keys_list = SLIST_HEAD_INITIALIZER(keys_list); /* shutdown keys; free allocated memory and the like */ void keys_shutdown() { keybind_t *key, *next; /* free memory allocated in our list of keys */ key = SLIST_FIRST(&keys_list); while (key) { /* some keybinding types have allocated memory */ switch (key->action) { case KEY_COMMAND: free(key->dat.cmd); break; case KEY_SETVIEWPORT: free(key->dat.pt); break; } /* free the keybind_t */ next = SLIST_NEXT(key, k_list); free(key); key = next; } SLIST_INIT(&keys_list); } /* add a key binding */ keybind_t *keys_add(int keycode, u_int modifiers, int action, void* dat) { keybind_t *key; key = malloc(sizeof(keybind_t)); if (!key) return NULL; key->keycode = keycode; key->modifiers = modifiers; key->action = action; key->dat.generic = dat; SLIST_INSERT_HEAD(&keys_list, key, k_list); return key; } static __inline void gethackmods() { static int masks[8] = { ShiftMask, LockMask, ControlMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask }; XModifierKeymap *modmap; KeyCode ncode, scode; int i; /* determine modifier mappings for the below hack */ modmap = XGetModifierMapping(display); if (modmap) { numlock_mask = scroll_mask = 0; ncode = XKeysymToKeycode(display, XK_Num_Lock); scode = XKeysymToKeycode(display, XK_Scroll_Lock); for (i = 0; i < 8 * modmap->max_keypermod; i++) if (ncode && modmap->modifiermap[i] == ncode) numlock_mask = masks[i / modmap->max_keypermod]; else if (scode && modmap->modifiermap[i] == scode) scroll_mask = masks[i / modmap->max_keypermod]; XFreeModifiermap(modmap); } else numlock_mask = scroll_mask = 0; } static __inline void grabhack(Window win, int keycode, u_int modifiers) { if (numlock_mask) { XGrabKey(display, keycode, modifiers | numlock_mask, win, 1, GrabModeAsync, GrabModeAsync); XGrabKey(display, keycode, modifiers | numlock_mask | LockMask, win, 1, GrabModeAsync, GrabModeAsync); } if (scroll_mask) { XGrabKey(display, keycode, modifiers | scroll_mask, win, 1, GrabModeAsync, GrabModeAsync); XGrabKey(display, keycode, modifiers | scroll_mask | LockMask, win, 1, GrabModeAsync, GrabModeAsync); } if (numlock_mask && scroll_mask) { XGrabKey(display, keycode, modifiers | numlock_mask | scroll_mask, win, 1, GrabModeAsync, GrabModeAsync); XGrabKey(display, keycode, modifiers | numlock_mask | scroll_mask | LockMask, win, 1, GrabModeAsync, GrabModeAsync); } XGrabKey(display, keycode, modifiers | LockMask, win, 1, GrabModeAsync, GrabModeAsync); XGrabKey(display, keycode, modifiers, win, 1, GrabModeAsync, GrabModeAsync); } void buttongrabhack(Window win, int button, u_int modifiers) { if (numlock_mask) { XGrabButton(display, AnyButton, modifiers | numlock_mask, win, 1, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); XGrabButton(display, AnyButton, modifiers | numlock_mask | LockMask, win, 1, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); } if (scroll_mask) { XGrabButton(display, AnyButton, modifiers | scroll_mask, win, 1, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); XGrabButton(display, AnyButton, modifiers | scroll_mask | LockMask, win, 1, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); } if (numlock_mask && scroll_mask) { XGrabButton(display, AnyButton, modifiers | numlock_mask | scroll_mask, win, 1, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); XGrabButton(display, AnyButton, modifiers | numlock_mask | scroll_mask | LockMask, win, 1, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); } XGrabButton(display, AnyButton, modifiers | LockMask, win, 1, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); XGrabButton(display, AnyButton, modifiers, win, 1, ButtonPressMask, GrabModeSync, GrabModeAsync, None, None); } /* do grabs on the root window of screen */ void keys_grab(screen_t *screen) { keybind_t *key; gethackmods(); SLIST_FOREACH(key, &keys_list, k_list) grabhack(screen->root, key->keycode, key->modifiers); } /* move the viewport */ static void keys_moveport(screen_t *screen, int dir) { int vx, vy; switch (dir) { case MV_UP: vx = 0; vy = -1; break; case MV_DOWN: vx = 0; vy = 1; break; case MV_LEFT: vx = -1; vy = 0; break; case MV_RIGHT: vx = 1; vy = 0; break; case MV_UPRIGHT: vx = 1; vy = -1; break; case MV_DOWNRIGHT: vx = 1; vy = 1; break; case MV_DOWNLEFT: vx = -1; vy = 1; break; default: case MV_UPLEFT: vx = -1; vy = -1; break; } workspace_viewport_move(screen, screen->desktop, vx, vy); } /* * focus and raise a win, we don't use focus_setfocused because * when going across screens in the all or screen style cyclings * it would try to just set workspace->focused pointers. */ #define FOCUSRAISE(foc) do { \ focus_client((foc)); \ stacking_raise(client_focused); \ } while (0) /* forward loop through screens, check if foc to focus foc, for cycle */ #define SCREEN_FLOOP(foc, currentscr) do { \ screen = TAILQ_NEXT((currentscr), s_list); \ \ while (screen != (currentscr)) { \ if (!screen) { \ screen = TAILQ_FIRST(&screen_list); \ continue; \ } \ \ if ((foc)) { \ FOCUSRAISE((foc)); \ break; \ } \ screen = TAILQ_NEXT(screen, s_list); \ } \ } while (0) /* backward loop through screens, like floop */ #define SCREEN_BLOOP(foc, currentscr) do { \ screen = TAILQ_PREV((currentscr), screenlist, s_list); \ \ while (screen != (currentscr)) { \ if (!screen) { \ screen = TAILQ_LAST(&screen_list, screenlist); \ continue; \ } \ \ if ((foc)) { \ FOCUSRAISE((foc)); \ break; \ } \ \ screen = TAILQ_PREV(screen, screenlist, s_list); \ } \ } while (0) /* cycle screens */ static void keys_cyclescr(screen_t *keyscr, client_t *client, int cycletype) { screen_t *screen; screen_t *pointer_screen; Window pointer_root; Window dumwin; int dumint; /* * screen-based cycling doesn't require visible client windows: it * warps the pointer to new screens, and focused the focused window * on the screen if there is one. The currently 'focused' screen * is the one w/ the mouse pointer, because that is where keybinds * will take effect. So event if the focused window is on a different * screen we treat the mouse pointer screen as the focused screen. */ XQueryPointer(display, keyscr->root, &pointer_root, &dumwin, &dumint, &dumint, &dumint, &dumint, &dumint); TAILQ_FOREACH(pointer_screen, &screen_list, s_list) if (pointer_screen->root == pointer_root) break; /* * now we know which screen we're on; put it on the * screen it needs to go to. */ switch (cycletype) { case CF_FSCR: screen = TAILQ_NEXT(pointer_screen, s_list); if (!screen) screen = TAILQ_FIRST(&screen_list); /* only refocus if this isn't the screen of the focused window */ if (screen == pointer_screen) goto warp; if (screen->desktop->current_space->focused) if (!client || client->screen != screen) FOCUSRAISE(screen->desktop->current_space->focused); case CF_BSCR: default: screen = TAILQ_PREV(pointer_screen, screenlist, s_list); if (!screen) screen = TAILQ_LAST(&screen_list, screenlist); /* only refocus if this isn't the screen of the focused window */ if (screen == pointer_screen) goto warp; if (screen->desktop->current_space->focused) if (!client || client->screen != screen) FOCUSRAISE(screen->desktop->current_space->focused); } warp: /* put pointer in the middle of the screen */ XWarpPointer(display, None, screen->root, 0, 0, 1, 1, screen->width / 2, screen->height / 2); } /* * cycle input focus, slightly complicated becase we support several cycling * types here (for multiscreen), see keys.h for info on the types. */ static void keys_cyclefoc(screen_t *keyscr, client_t *client, int cycletype) { screen_t *screen; /* screen based cycling; not strictly for just focus cycling */ if (cycletype == CF_FSCR || cycletype == CF_BSCR) { keys_cyclescr(keyscr, client, cycletype); return; } /* if no window is focused, this is easier */ if (!client) { if (cycletype % 2 == 0) { if (TAILQ_FIRST(&keyscr->desktop->current_space->w_foclist)) FOCUSRAISE(TAILQ_FIRST(&keyscr->desktop->current_space->w_foclist)); else if (cycletype == CF_FALL) SCREEN_FLOOP(TAILQ_FIRST(&screen->desktop->current_space->w_foclist), keyscr); } else { if (TAILQ_LAST(&keyscr->desktop->current_space->w_foclist, foclist)) FOCUSRAISE(TAILQ_LAST(&keyscr->desktop->current_space->w_foclist, foclist)); else if (cycletype == CF_BALL) SCREEN_BLOOP(TAILQ_LAST(&screen->desktop->current_space->w_foclist, foclist), keyscr); } return; } /* if the type is div by 2, it's a forward cycling type */ if (cycletype % 2 == 0) { if (TAILQ_NEXT(client, c_focus)) { FOCUSRAISE(TAILQ_NEXT(client, c_focus)); } else { if (cycletype == CF_FALL) SCREEN_FLOOP(TAILQ_FIRST(&screen->desktop->current_space->w_foclist), client->screen); else if (TAILQ_FIRST(&client->screen->desktop->current_space->w_foclist)) FOCUSRAISE(TAILQ_FIRST(&client->screen->desktop->current_space->w_foclist)); } } else { if (TAILQ_PREV(client, foclist, c_focus)) { FOCUSRAISE(TAILQ_PREV(client, foclist, c_focus)); } else { if (cycletype == CF_BALL) SCREEN_BLOOP(TAILQ_LAST(&screen->desktop->current_space->w_foclist, foclist), client->screen); else if (TAILQ_LAST(&client->screen->desktop->current_space->w_foclist, foclist)) FOCUSRAISE(TAILQ_LAST(&client->screen->desktop->current_space->w_foclist, foclist)); } } } #undef SCREEN_BLOOP #undef SCREEN_FLOOP #undef FOCUSRAISE /* perform an action for a key */ static void keys_action(screen_t *screen, keybind_t *key) { client_t *client = client_focused; switch (key->action) { case KEY_ICONIFY: if (client) action_iconify(client); break; case KEY_ZOOM: if (client) action_zoom(client); break; case KEY_SWITCHDESK: desktop_switch(screen, key->dat.num); break; case KEY_MOVEVIEWPORT: keys_moveport(screen, key->dat.dir); break; case KEY_SETVIEWPORT: workspace_viewport_move(screen, screen->desktop, key->dat.pt->x - screen->desktop->viewx, key->dat.pt->y - screen->desktop->viewy); break; case KEY_COMMAND: action_exec(screen->num, key->dat.cmd); break; case KEY_DELETE: if (client && !client->flags.nodelete) action_sendcmesg(client->window, WM_DELETE_WINDOW, CurrentTime); break; case KEY_CYCLEFOCUS: keys_cyclefoc(screen, client, key->dat.cycletype); break; case KEY_RAISE: if (client) stacking_raise(client); break; case KEY_LOWER: if (client) stacking_lower(client); break; case KEY_DGROUPSWITCH: if (client) dgroup_switch(client, NULL); break; case KEY_STICKY: if (client) client->flags.sticky = ~client->flags.sticky; break; case KEY_ABORT: if (!fork()) abort(); break; case KEY_RESTART: restart_bin = binary_name; case KEY_EXIT: restart_flag = 1; break; } } /* handles when we get keypresses from our grabs */ void keys_press(screen_t *screen, XKeyEvent *e) { keybind_t *key; SLIST_FOREACH(key, &keys_list, k_list) if (e->keycode == key->keycode && (e->state & ~(numlock_mask | scroll_mask | LockMask)) == key->modifiers) { keys_action(screen, key); return; } }