diff options
Diffstat (limited to 'XMonad')
-rw-r--r-- | XMonad/Actions/CycleWindows.hs | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/XMonad/Actions/CycleWindows.hs b/XMonad/Actions/CycleWindows.hs new file mode 100644 index 0000000..14cda8d --- /dev/null +++ b/XMonad/Actions/CycleWindows.hs @@ -0,0 +1,222 @@ +-------------------------------------------------------------------------------- +-- | +-- Module : XMonad.Actions.CycleWindows +-- Copyright : (c) Wirt Wolff <wirtwolff@gmail.com> +-- License : BSD3-style (see LICENSE) +-- +-- Maintainer : Wirt Wolff <wirtwolff@gmail.com> +-- Stability : unstable +-- Portability : unportable +-- +-- Provides bindings to cycle windows up or down on the current workspace +-- stack while maintaining focus in place. Bindings are available to: +-- +-- * Cycle nearby or nth windows into the focused frame +-- +-- * Cycle a window halfway around the stack +-- +-- * Cycle windows through the focused position. +-- +-- * Cycle unfocused windows. +-- +-- These bindings are especially useful with layouts that hide some of +-- the windows in the stack, such as Full, "XMonad.Layout.TwoPane" or +-- "XMonad.Layout.Mosaic" with three or four panes. See also +-- "XMonad.Actions.RotSlaves" for related actions. +----------------------------------------------------------------------------- +module XMonad.Actions.CycleWindows ( + -- * Usage + -- $usage + + -- * Cycling nearby or nth window into current frame + -- $cycle + cycleRecentWindows, + cycleStacks', + -- * Cycling half the stack to get rid of a boring window + -- $opposite + rotOpposite', rotOpposite, + -- * Cycling windows through the current frame + -- $focused + rotFocused', rotFocusedUp, rotFocusedDown, shiftToFocus', + -- * Cycling windows through other frames + -- $unfocused + rotUnfocused', rotUnfocusedUp, rotUnfocusedDown, + -- * Updating the mouse pointer + -- $pointer + + -- * Generic list rotations + -- $generic + rotUp, rotDown +) where + +import XMonad +import qualified XMonad.StackSet as W +import XMonad.Actions.RotSlaves + +-- $usage +-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: +-- +-- > import XMonad.Actions.CycleWindows +-- > -- config +-- > -- other key bindings with x here your config +-- > +-- > -- make sure mod matches keysym +-- > , ((mod4Mask, xK_s), cycleRecentWindows [xK_Super_L] xK_s xK_w) +-- > , ((modMask x, xK_z), rotOpposite) +-- > , ((modMask x , xK_i), rotUnfocusedUp) +-- > , ((modMask x , xK_u), rotUnfocusedDown) +-- > , ((modMask x .|. controlMask, xK_i), rotFocusedUp) +-- > , ((modMask x .|. controlMask, xK_u), rotFocusedDown) +-- +-- Also, if you use focus follows mouse, you will want to read the section +-- on updating the mouse pointer below. For detailed instructions on +-- editing your key bindings, see "XMonad.Doc.Extending#Editing_key_bindings". +{- $pointer +With FocusFollowsMouse == True, the focus is updated after binding +actions, possibly focusing a window you didn't intend to focus. Most +people using TwoPane probably already have a logHook causing the mouse +to follow focus. (See "XMonad.Actions.UpdatePointer", or "XMonad.Actions.Warp") + +If you want this built into the key binding instead, use the appropriate +action from one of those modules to also have your bindings move the pointer +to the point of your choice on the current window: + +> import XMonad.Actions.UpdatePointer -- or Actions.Warp + +and either + +> -- modify the window rotation bindings +> , ((modMask x .|. controlMask, xK_i ), rotFocusedUp +> >> updatePointer (Relative 1 1)) +> , ((modMask x .|. controlMask, xK_u ), rotFocusedDown +> >> updatePointer (Relative 1 1)) +> +> -- or add to xmonad's logHook +> , logHook = dynamicLogWithPP xmobarPP +> >> updatePointer Nearest -- or your preference + +-} + +-- $cycle +-- Cycle windows into focus from below or above the focused pane by pressing +-- a key while one or more modifier keys is held down. The window order isn't +-- changed until a modifier is released, leaving the previously focused window +-- just below the new one, (or above if the window just above is chosen.) For +-- best results use the same modifier + key combination as the one used to invoke +-- the \"bring from below\" action. Also, once cycling, pressing a number key n +-- will focus the nth window, with 0 being the one originally focused. +cycleRecentWindows :: [KeySym] -- ^ A list of modifier keys used when invoking this action. + -- As soon as one of them is released, the final switch is made. + -> KeySym -- ^ Key used to shift windows from below the current choice into the current frame. + -> KeySym -- ^ Key used to shift windows from above the current choice into the current frame. + -- If it's the same as the first key, it is effectively ignored. + -> X () +cycleRecentWindows = cycleStacks' stacks where + stacks s = map (shiftToFocus' `flip` s) (wins s) + wins (W.Stack t l r) = t : r ++ reverse l + + +-- | Cycle through a /finite/ list of window stacks with repeated presses +-- of a key while a modifier key is held down. For best results use the same +-- mod key + key combination as the one used to invoke the \"bring from below\" +-- action. You could use cycleStacks' with a different stack permutations +-- function to, for example, cycle from one below to one above to two below, +-- etc. instead of in order. You are responsible for having it generate a +-- finite list, though, or xmonad may hang seeking its length. +cycleStacks' :: (W.Stack Window -> [W.Stack Window]) -- ^ A function to a finite list of permutations of a given stack. + -> [KeySym] -- ^ A list of modifier keys used to invoke 'cycleStacks''. + -- As soon as any is released, we're no longer cycling on the [Stack Window] + -> KeySym -- ^ Key used to select a \"next\" stack. + -> KeySym -- ^ Key used to select a \"previous\" stack. + -> X () +cycleStacks' filteredPerms mods keyNext keyPrev = do + XConf {theRoot = root, display = d} <- ask + stacks <- gets $ maybe [] filteredPerms . W.stack . W.workspace . W.current . windowset + + let evt = allocaXEvent $ + \p -> do maskEvent d (keyPressMask .|. keyReleaseMask) p + KeyEvent {ev_event_type = t, ev_keycode = c} <- getEvent p + s <- keycodeToKeysym d c 0 + return (t, s) + choose n (t, s) + | t == keyPress && s == keyNext = io evt >>= choose (n+1) + | t == keyPress && s == keyPrev = io evt >>= choose (n-1) + | t == keyPress && s `elem` [xK_0..xK_9] = io evt >>= choose (numKeyToN s) + | t == keyRelease && s `elem` mods = return () + | otherwise = doStack n >> io evt >>= choose n + doStack n = windows . W.modify' . const $ stacks `cycref` n + + io $ grabKeyboard d root False grabModeAsync grabModeAsync currentTime + io evt >>= choose 1 + io $ ungrabKeyboard d currentTime + where cycref l i = l !! (i `mod` length l) -- modify' ensures l is never [], but must also be finite + numKeyToN = subtract 48 . read . show + +-- | Given a stack element and a stack, shift or insert the element (window) +-- at the currently focused position. +shiftToFocus' :: (Eq a, Show a, Read a) => a -> W.Stack a -> W.Stack a +shiftToFocus' w s@(W.Stack _ ls _) = W.Stack w (reverse revls') rs' + where (revls', rs') = splitAt (length ls) . filter (/= w) $ W.integrate s + + +-- $opposite +-- Shifts the focused window as far as possible from the current focus, +-- i.e. halfway around the stack. Windows above the focus up to the \"opposite\" +-- position remain in place, while those above the insertion shift toward +-- the current focus. This is useful for people who use lots of windows in Full, +-- TwoPane, etc., to get rid of boring windows while cycling and swapping +-- near the focus. +rotOpposite :: X() +rotOpposite = windows $ W.modify' rotOpposite' + +-- | The opposite rotation on a Stack. +rotOpposite' :: W.Stack a -> W.Stack a +rotOpposite' (W.Stack t l r) = W.Stack t' l' r' + where rrvl = r ++ reverse l + part = (length rrvl + 1) `div` 2 + (l',t':r') = (\(f,s) -> (f, reverse s)) . splitAt (length l) $ + reverse (take part rrvl ++ t : drop part rrvl) + + +-- $focused +-- Rotate windows through the focused frame, excluding the \"next\" window. +-- With, e.g. TwoPane, this allows cycling windows through either the +-- master or slave pane, without changing the other frame. When the master +-- is focused, the window below is skipped, when a non-master window is +-- focused, the master is skipped. +rotFocusedUp :: X () +rotFocusedUp = windows . W.modify' $ rotFocused' rotUp +rotFocusedDown :: X () +rotFocusedDown = windows . W.modify' $ rotFocused' rotDown + +-- | The focused rotation on a stack. +rotFocused' :: ([a] -> [a]) -> W.Stack a -> W.Stack a +rotFocused' _ s@(W.Stack _ [] []) = s +rotFocused' f (W.Stack t [] (r:rs)) = W.Stack t' [] (r:rs') -- Master has focus + where (t':rs') = f (t:rs) +rotFocused' f s@(W.Stack _ _ _) = rotSlaves' f s -- otherwise + + +-- $unfocused +-- Rotate windows through the unfocused frames. This is similar to +-- rotSlaves, from "XMonad.Actions.RotSlaves", but excludes the current +-- frame rather than master. +rotUnfocusedUp :: X () +rotUnfocusedUp = windows . W.modify' $ rotUnfocused' rotUp +rotUnfocusedDown :: X () +rotUnfocusedDown = windows . W.modify' $ rotUnfocused' rotDown + +-- | The unfocused rotation on a stack. +rotUnfocused' :: ([a] -> [a]) -> W.Stack a -> W.Stack a +rotUnfocused' _ s@(W.Stack _ [] []) = s +rotUnfocused' f s@(W.Stack _ [] _ ) = rotSlaves' f s -- Master has focus +rotUnfocused' f (W.Stack t ls rs) = W.Stack t (reverse revls') rs' -- otherwise + where (master:revls) = reverse ls + (revls',rs') = splitAt (length ls) (f $ master:revls ++ rs) + +-- $generic +-- Generic list rotations +rotUp :: [a] -> [a] +rotUp l = tail l ++ [head l] +rotDown :: [a] -> [a] +rotDown l = last l : init l |