----------------------------------------------------------------------------- -- | -- Module : XMonad.Actions.Plane -- Copyright : (c) Marco TĂșlio Gontijo e Silva , -- Leonardo Serra -- License : BSD3-style (see LICENSE) -- -- Maintainer : Marco TĂșlio Gontijo e Silva -- Stability : unstable -- Portability : unportable -- -- This module has functions to navigate through workspaces in a bidimensional -- manner. It allows the organization of workspaces in lines, and provides -- functions to move and shift windows in all four directions (left, up, right -- and down) possible in a surface. -- -- This functionality was inspired by GNOME (finite) and KDE (infinite) -- keybindings for workspace navigation, and by "XMonad.Actions.CycleWS" for -- the idea of applying this approach to XMonad. ----------------------------------------------------------------------------- module XMonad.Actions.Plane ( -- * Usage -- $usage -- * Data types Direction (..) , Limits (..) , Lines (..) -- * Key bindings , planeKeys -- * Navigating through workspaces , planeShift , planeMove ) where import Control.Monad import Data.List import Data.Map hiding (split) import Data.Maybe import XMonad import XMonad.StackSet hiding (workspaces) import XMonad.Util.Run -- $usage -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file: -- -- > import XMonad.Actions.Plane -- > -- > main = xmonad defaultConfig {keys = myKeys} -- > -- > myKeys conf = union (keys defaultConfig conf) $ myNewKeys conf -- > -- > myNewkeys (XConfig {modMask = modm}) = planeKeys modm (Lines 3) Finite -- -- For detailed instructions on editing your key bindings, see -- "XMonad.Doc.Extending#Editing_key_bindings". -- | Direction to go in the plane. data Direction = ToLeft | ToUp | ToRight | ToDown deriving Enum -- | Defines the behaviour when you're trying to move out of the limits. data Limits = Finite -- ^ Ignore the function call, and keep in the same workspace. | Circular -- ^ Get on the other side, like in the Snake game. | Linear -- ^ The plan comes as a row, so it goes to the next or prev if -- the workspaces were numbered. deriving Eq -- | The number of lines in which the workspaces will be arranged. It's -- possible to use a number of lines that is not a divisor of the number of -- workspaces, but the results are better when using a divisor. If it's not a -- divisor, the last line will have the remaining workspaces. data Lines = GConf -- ^ Use @gconftool-2@ to find out the number of lines. | Lines Int -- ^ Specify the number of lines explicitly. -- | This is the way most people would like to use this module. It attaches the -- 'KeyMask' passed as a parameter with 'xK_Left', 'xK_Up', 'xK_Right' and -- 'xK_Down', associating it with 'planeMove' to the corresponding 'Direction'. -- It also associates these bindings with 'shiftMask' to 'planeShift'. planeKeys :: KeyMask -> Lines -> Limits -> Map (KeyMask, KeySym) (X ()) planeKeys modm ln limits = fromList $ [ ((keyMask, keySym), function ln limits direction) | (keySym, direction) <- zip [xK_Left .. xK_Down] $ enumFrom ToLeft , (keyMask, function) <- [(modm, planeMove), (shiftMask .|. modm, planeShift)] ] -- | Shift a window to the next workspace in 'Direction'. Note that this will -- also move to the next workspace. It's a good idea to use the same 'Lines' -- and 'Limits' for all the bindings. planeShift :: Lines -> Limits -> Direction -> X () planeShift = plane shift' shift' :: (Eq s, Eq i, Ord a) => i -> StackSet i l a s sd -> StackSet i l a s sd shift' area = greedyView area . shift area -- | Move to the next workspace in 'Direction'. planeMove :: Lines -> Limits -> Direction -> X () planeMove = plane greedyView plane :: (WorkspaceId -> WindowSet -> WindowSet) -> Lines -> Limits -> Direction -> X () plane function numberLines_ limits direction = do state <- get xconf <- ask numberLines <- liftIO $ case numberLines_ of Lines numberLines__ -> return numberLines__ GConf -> do numberLines__ <- runProcessWithInput gconftool parameters "" case reads numberLines__ of [(numberRead, _)] -> return numberRead _ -> do trace $ "XMonad.Actions.Plane: Could not parse the output of " ++ gconftool ++ unwords parameters ++ ": " ++ numberLines__ ++ "; assuming 1." return 1 let notBorder :: Bool notBorder = (replicate 2 (circular_ < currentWS) ++ replicate 2 (circular_ > currentWS)) !! fromEnum direction circular_ :: Int circular_ = circular currentWS circular :: Int -> Int circular = [ onLine pred , onColumn pred , onLine succ , onColumn succ ] !! fromEnum direction linear :: Int -> Int linear = [ onLine pred . onColumn pred , onColumn pred . onLine pred , onLine succ . onColumn succ , onColumn succ . onLine succ ] !! fromEnum direction onLine :: (Int -> Int) -> Int -> Int onLine f currentWS_ | line < areasLine = mod_ columns | otherwise = mod_ areasColumn where line, column :: Int (line, column) = split currentWS_ mod_ :: Int -> Int mod_ columns_ = compose line $ mod (f column) columns_ onColumn :: (Int -> Int) -> Int -> Int onColumn f currentWS_ | column < areasColumn || areasColumn == 0 = mod_ numberLines | otherwise = mod_ $ pred numberLines where line, column :: Int (line, column) = split currentWS_ mod_ :: Int -> Int mod_ lines_ = compose (mod (f line) lines_) column compose :: Int -> Int -> Int compose line column = line * columns + column split :: Int -> (Int, Int) split currentWS_ = (operation div, operation mod) where operation :: (Int -> Int -> Int) -> Int operation f = f currentWS_ columns areasLine :: Int areasLine = div areas columns areasColumn :: Int areasColumn = mod areas columns columns :: Int columns = if mod areas numberLines == 0 then preColumns else preColumns + 1 currentWS :: Int currentWS = fromJust mCurrentWS preColumns :: Int preColumns = div areas numberLines mCurrentWS :: Maybe Int mCurrentWS = elemIndex (currentTag $ windowset state) areaNames areas :: Int areas = length areaNames run :: (Int -> Int) -> X () run f = windows $ function $ areaNames !! f currentWS areaNames :: [String] areaNames = workspaces $ config xconf when (isJust mCurrentWS) $ case limits of Finite -> when notBorder $ run circular Circular -> run circular Linear -> if notBorder then run circular else run linear gconftool :: String gconftool = "gconftool-2" parameters :: [String] parameters = ["--get", "/apps/panel/applets/workspace_switcher_screen0/prefs/num_rows"]