mirror of
https://github.com/xmonad/xmonad-contrib.git
synced 2025-05-19 03:20:21 -07:00
229 lines
7.6 KiB
Haskell
229 lines
7.6 KiB
Haskell
-----------------------------------------------------------------------------
|
|
-- |
|
|
-- Module : XMonad.Actions.Plane
|
|
-- Copyright : (c) Marco Túlio Gontijo e Silva <marcot@riseup.net>,
|
|
-- Leonardo Serra <leoserra@minaslivre.org>
|
|
-- License : BSD3-style (see LICENSE)
|
|
--
|
|
-- Maintainer : Marco Túlio Gontijo e Silva <marcot@riseup.net>
|
|
-- 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"] |