1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
|
-----------------------------------------------------------------------------
-- |
-- 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"]
|