aboutsummaryrefslogtreecommitdiffstats
path: root/XMonad/Actions/CycleWS.hs
blob: 6cce784ed1c51553b033e8c8939e2ab5c1ef8450 (plain) (blame)
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
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
-----------------------------------------------------------------------------
-- |
-- Module      :  XMonad.Actions.CycleWS
-- Copyright   :  (c) Joachim Breitner <mail@joachim-breitner.de>,
--                    Nelson Elhage <nelhage@mit.edu> (`toggleWS' function)
-- License     :  BSD3-style (see LICENSE)
--
-- Maintainer  :  Joachim Breitner <mail@joachim-breitner.de>
-- Stability   :  unstable
-- Portability :  unportable
--
-- Provides bindings to cycle forward or backward through the list of
-- workspaces, to move windows between workspaces, and to cycle
-- between screens.  More general combinators provide ways to cycle
-- through workspaces in various orders, to only cycle through some
-- subset of workspaces, and to cycle by more than one workspace at a
-- time.
--
-- Note that this module now subsumes the functionality of the former
-- @XMonad.Actions.RotView@.  Former users of @rotView@ can simply replace
-- @rotView True@ with @moveTo Next NonEmptyWS@, and so on.
--
-- If you want to exactly replicate the action of @rotView@ (cycling
-- through workspace in order lexicographically by tag, instead of in
-- the order specified in the config), it can be implemented as:
--
-- > rotView b  = do t <- findWorkspace getSortByTag (bToDir b) NonEmptyWS 1
-- >                 windows . greedyView $ t
-- >   where bToDir True  = Next
-- >         bToDir False = Prev
--
-----------------------------------------------------------------------------

module XMonad.Actions.CycleWS (
                                -- * Usage
                                -- $usage

                                -- * Moving between workspaces
                                -- $moving

                                nextWS
                              , prevWS
                              , shiftToNext
                              , shiftToPrev

                                -- * Toggling the previous workspace
                                -- $toggling
                              , toggleWS
                              , toggleOrView

                                -- * Moving between screens (xinerama)

                              , nextScreen
                              , prevScreen
                              , shiftNextScreen
                              , shiftPrevScreen
                              , swapNextScreen
                              , swapPrevScreen

                                -- * Moving between workspaces, take two!
                                -- $taketwo

                              , WSDirection(..)
                              , WSType(..)

                              , shiftTo
                              , moveTo

                                -- * The mother-combinator

                              , findWorkspace
                              , toggleOrDoSkip
                              , skipTags

                             ) where

import Control.Monad ( unless )
import Data.List ( findIndex )
import Data.Maybe ( isNothing, isJust )

import XMonad hiding (workspaces)
import XMonad.StackSet hiding (filter)
import XMonad.Util.WorkspaceCompare

-- $usage
-- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@ file:
--
-- > import XMonad.Actions.CycleWS
-- >
-- > -- a basic CycleWS setup
-- >
-- >   , ((modMask x,               xK_Down),  nextWS)
-- >   , ((modMask x,               xK_Up),    prevWS)
-- >   , ((modMask x .|. shiftMask, xK_Down),  shiftToNext)
-- >   , ((modMask x .|. shiftMask, xK_Up),    shiftToPrev)
-- >   , ((modMask x,               xK_Right), nextScreen)
-- >   , ((modMask x,               xK_Left),  prevScreen)
-- >   , ((modMask x .|. shiftMask, xK_Right), shiftNextScreen)
-- >   , ((modMask x .|. shiftMask, xK_Left),  shiftPrevScreen)
-- >   , ((modMask x,               xK_z),     toggleWS)
--
-- If you want to follow the moved window, you can use both actions:
--
-- >   , ((modMask x .|. shiftMask, xK_Down), shiftToNext >> nextWS)
-- >   , ((modMask x .|. shiftMask, xK_Up),   shiftToPrev >> prevWS)
--
-- You can also get fancier with 'moveTo', 'shiftTo', and 'findWorkspace'.
-- For example:
--
-- >   , ((modMask x     , xK_f), moveTo Next EmptyWS)  -- find a free workspace
-- >   , ((modMask x .|. controlMask, xK_Right),        -- a crazy keybinding!
-- >         do t <- findWorkspace getSortByXineramaRule Next NonEmptyWS 2
-- >            windows . view $ t                                         )
--
-- For detailed instructions on editing your key bindings, see
-- "XMonad.Doc.Extending#Editing_key_bindings".

{- $moving

The following commands for moving the view and windows between
workspaces are somewhat inflexible, but are very simple and probably
Do The Right Thing for most users.

All of the commands in this section cycle through workspaces in the
order in which they are given in your config.

-}

-- | Switch to the next workspace.
nextWS :: X ()
nextWS = switchWorkspace 1

-- | Switch to the previous workspace.
prevWS :: X ()
prevWS = switchWorkspace (-1)

-- | Move the focused window to the next workspace.
shiftToNext :: X ()
shiftToNext = shiftBy 1

-- | Move the focused window to the previous workspace.
shiftToPrev :: X ()
shiftToPrev = shiftBy (-1)

-- $toggling

-- | Toggle to the workspace displayed previously.
toggleWS :: X ()
toggleWS = do
    hs <- gets (hidden . windowset)
    unless (null hs) (windows . view . tag $ head hs)

-- | 'XMonad.StackSet.greedyView' a workspace, or if already there, view
-- the previously displayed workspace ala weechat. Change @greedyView@ to
-- @toggleOrView@ in your workspace bindings as in the 'XMonad.StackSet.view'
-- faq at <http://haskell.org/haskellwiki/Xmonad/Frequently_asked_questions>.
-- For more flexibility see 'toggleOrDoSkip'.
toggleOrView :: WorkspaceId -> X ()
toggleOrView = toggleOrDoSkip [] greedyView

-- | Allows ignoring listed workspace tags (such as scratchpad's \"NSP\") while
-- finding the previously displayed workspace, or choice of different actions,
-- like view, shift, etc.  For example:
--
-- > import qualified XMonad.StackSet as W
-- > import XMonad.Actions.CycleWS
-- >
-- > -- toggleOrView for people who prefer view to greedyView
-- > toggleOrView' = toggleOrDoSkip [] W.view
-- >
-- > -- toggleOrView ignoring scratchpad and named scratchpad workspace
-- > toggleOrViewNoSP = toggleOrDoSkip ["NSP"] W.greedyView
toggleOrDoSkip :: [WorkspaceId] -> (WorkspaceId -> WindowSet -> WindowSet)
                                  -> WorkspaceId -> X ()
toggleOrDoSkip skips f toWS = do
    ws <- gets windowset
    let hs' = hidden ws `skipTags` skips
    if toWS == (tag . workspace $ current ws)
        then unless (null hs') (windows . f . tag $ head hs')
        else windows (f toWS)

-- | List difference ('\\') for workspaces and tags. Removes workspaces
-- matching listed tags from the given workspace list.
skipTags :: (Eq i) => [Workspace i l a] -> [i] -> [Workspace i l a]
skipTags wss ids = filter ((`notElem` ids) . tag) wss

switchWorkspace :: Int -> X ()
switchWorkspace d = wsBy d >>= windows . greedyView

shiftBy :: Int -> X ()
shiftBy d = wsBy d >>= windows . shift

wsBy :: Int -> X (WorkspaceId)
wsBy = findWorkspace getSortByIndex Next AnyWS

{- $taketwo

A few more general commands are also provided, which allow cycling
through subsets of workspaces.

For example,

>   moveTo Next EmptyWS

will move to the first available workspace with no windows, and

>   shiftTo Prev (WSIs $ return (('p' `elem`) . tag))

will move the focused window backwards to the first workspace containing
the letter 'p' in its name. =)

-}

-- | Direction to cycle through the sort order.
data WSDirection = Next | Prev

-- | What type of workspaces should be included in the cycle?
data WSType = EmptyWS     -- ^ cycle through empty workspaces
            | NonEmptyWS  -- ^ cycle through non-empty workspaces
            | HiddenWS    -- ^ cycle through non-visible workspaces
            | HiddenNonEmptyWS  -- ^ cycle through non-empty non-visible workspaces
            | AnyWS       -- ^ cycle through all workspaces
            | WSIs (X (WindowSpace -> Bool))
                          -- ^ cycle through workspaces satisfying
                          --   an arbitrary predicate

-- | Convert a WSType value to a predicate on workspaces.
wsTypeToPred :: WSType -> X (WindowSpace -> Bool)
wsTypeToPred EmptyWS    = return (isNothing . stack)
wsTypeToPred NonEmptyWS = return (isJust . stack)
wsTypeToPred HiddenWS   = do hs <- gets (map tag . hidden . windowset)
                             return (\w -> tag w `elem` hs)
wsTypeToPred HiddenNonEmptyWS  = do ne <- wsTypeToPred NonEmptyWS
                                    hi <- wsTypeToPred HiddenWS
                                    return (\w -> hi w && ne w)
wsTypeToPred AnyWS      = return (const True)
wsTypeToPred (WSIs p)   = p

-- | View the next workspace in the given direction that satisfies
--   the given condition.
moveTo :: WSDirection -> WSType -> X ()
moveTo dir t = findWorkspace getSortByIndex dir t 1 >>= windows . greedyView

-- | Move the currently focused window to the next workspace in the
--   given direction that satisfies the given condition.
shiftTo :: WSDirection -> WSType -> X ()
shiftTo dir t = findWorkspace getSortByIndex dir t 1 >>= windows . shift

-- | Given a function @s@ to sort workspaces, a direction @dir@, a
--   predicate @p@ on workspaces, and an integer @n@, find the tag of
--   the workspace which is @n@ away from the current workspace in
--   direction @dir@ (wrapping around if necessary), among those
--   workspaces, sorted by @s@, which satisfy @p@.
--
--   For some useful workspace sorting functions, see
--   "XMonad.Util.WorkspaceCompare".
--
--   For ideas of what to do with a workspace tag once obtained, note
--   that 'moveTo' and 'shiftTo' are implemented by applying @(>>=
--   (windows . greedyView))@ and @(>>= (windows . shift))@, respectively,
--   to the output of 'findWorkspace'.
findWorkspace :: X WorkspaceSort -> WSDirection -> WSType -> Int -> X WorkspaceId
findWorkspace s dir t n = findWorkspaceGen s (wsTypeToPred t) (maybeNegate dir n)
  where
    maybeNegate Next d = d
    maybeNegate Prev d = (-d)

findWorkspaceGen :: X WorkspaceSort -> X (WindowSpace -> Bool) -> Int -> X WorkspaceId
findWorkspaceGen _ _ 0 = gets (currentTag . windowset)
findWorkspaceGen sortX wsPredX d = do
    wsPred <- wsPredX
    sort   <- sortX
    ws     <- gets windowset
    let cur     = workspace (current ws)
        sorted  = sort (workspaces ws)
        pivoted = let (a,b) = span ((/= (tag cur)) . tag) sorted in b ++ a
        ws'     = filter wsPred pivoted
        mCurIx  = findWsIndex cur ws'
        d'      = if d > 0 then d - 1 else d
        next    = if null ws'
                      then cur
                      else case mCurIx of
                            Nothing -> ws' !! (d' `mod` length ws')
                            Just ix -> ws' !! ((ix + d) `mod` length ws')
    return $ tag next

findWsIndex :: WindowSpace -> [WindowSpace] -> Maybe Int
findWsIndex ws wss = findIndex ((== tag ws) . tag) wss

-- | View next screen
nextScreen :: X ()
nextScreen = switchScreen 1

-- | View prev screen
prevScreen :: X ()
prevScreen = switchScreen (-1)

switchScreen :: Int -> X ()
switchScreen d = do s <- screenBy d
                    mws <- screenWorkspace s
                    case mws of
                         Nothing -> return ()
                         Just ws -> windows (view ws)

screenBy :: Int -> X (ScreenId)
screenBy d = do ws <- gets windowset
                --let ss = sortBy screen (screens ws)
                let now = screen (current ws)
                return $ (now + fromIntegral d) `mod` fromIntegral (length (screens ws))

-- | Swap current screen with next screen
swapNextScreen :: X ()
swapNextScreen = swapScreen 1

-- | Swap current screen with previous screen
swapPrevScreen :: X ()
swapPrevScreen = swapScreen (-1)

swapScreen :: Int -> X ()
swapScreen d = do s <- screenBy d
                  mws <- screenWorkspace s
                  case mws of
                    Nothing -> return ()
                    Just ws -> windows (greedyView ws)

-- | Move focused window to workspace on next screen
shiftNextScreen :: X ()
shiftNextScreen = shiftScreenBy 1

-- | Move focused window to workspace on prev screen
shiftPrevScreen :: X ()
shiftPrevScreen = shiftScreenBy (-1)

shiftScreenBy :: Int -> X ()
shiftScreenBy d = do s <- screenBy d
                     mws <- screenWorkspace s
                     case mws of
                         Nothing -> return ()
                         Just ws -> windows (shift ws)