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
|
{-# LANGUAGE MultiWayIf #-}
module Rahm.Desktop.WorkspaceWheel
( displayWorkspaceWheel,
displayWorkspaceWheelAtPoint,
displayWorkspaceWheelInCenter,
)
where
import Control.Arrow (Arrow (first, second))
import Control.Concurrent (threadDelay)
import Control.Monad (forM_)
import Control.Monad.Trans.Maybe (MaybeT (runMaybeT))
import Data.Char (toLower)
import Data.Function (fix)
import Data.List (find, sortBy)
import qualified Data.Map as Map
import Data.Maybe (fromMaybe, isNothing)
import Data.Monoid (Endo (Endo, appEndo))
import Data.Ord (comparing)
import Data.Word
import qualified Graphics.X11 as X
import Rahm.Desktop.Common (click, gotoWorkspace, pointerLocation, pointerScreen)
import Rahm.Desktop.Keys.Wml (getAndResetWindowSelection, getAndResetWindowSelectionOrCurrent)
import Rahm.Desktop.Layout.PinWindow (withWindowsUnpinned)
import Rahm.Desktop.Marking (setAlternateWindows, setAlternateWorkspace)
import Rahm.Desktop.StackSet as W
import Rahm.Desktop.Submap (ButtonOrKeyEvent (ButtonPress, KeyPress), nextButton, nextButtonOrKeyEvent)
import System.IO
import Text.Printf (printf)
import XMonad
import qualified XMonad.Operations as X
import XMonad.Util.Run (spawnPipe)
dzenSize :: (Integral a) => a
dzenSize = 100
data ScreenType = Current | Visible | Hidden deriving (Eq)
data WorkspaceWheelType = Existing | Alphabet Bool Bool | Digits
displayWorkspaceWheel :: X ()
displayWorkspaceWheel =
uncurry (displayWorkspaceWheelAtPoint workspaceWheelTypeRing)
=<< pointerLocation
data Ring a = Ring Int [a] (Maybe a) [a]
workspaceWheelTypeRing :: Ring WorkspaceWheelType
workspaceWheelTypeRing =
ringAdv $
mkRing
[ Existing,
Alphabet False False,
Alphabet False True,
Alphabet True False,
Alphabet True True,
Digits
]
mkRing :: [a] -> Ring a
mkRing lst =
let as = cycle lst
rev = cycle (reverse lst)
in Ring (length lst) rev Nothing as
ringCur :: Ring a -> Maybe a
ringCur (Ring _ _ a _) = a
ringAdv :: Ring a -> Ring a
ringAdv (Ring s p Nothing (n : ns)) = Ring s p (Just n) ns
ringAdv (Ring s p (Just a) (n : ns)) = Ring s (a : p) (Just n) ns
ringPrev :: Ring a -> Ring a
ringPrev (Ring s (p : ps) Nothing ns) = Ring s ps (Just p) ns
ringPrev (Ring s (p : ps) (Just a) ns) = Ring s ps (Just p) (a : ns)
ringFastForwardTo :: (a -> Bool) -> Ring a -> Ring a
ringFastForwardTo fn ring@(Ring s _ a _) =
case ringFastForwardTo' fn s ring of
ring'@(Ring s' p' (Just a') n') | isNothing a -> Ring s' (a' : p') Nothing n'
ring' -> ring'
where
ringFastForwardTo' _ 0 ring = ring
ringFastForwardTo' fn _ ring@(Ring _ _ (Just a) _) | fn a = ring
ringFastForwardTo' fn n ring = ringFastForwardTo' fn (n - 1) (ringAdv ring)
displayWorkspaceWheelInCenter :: X ()
displayWorkspaceWheelInCenter =
mapM_
( \(W.screenDetail -> (SD (Rectangle x y w h))) ->
displayWorkspaceWheelAtPoint
workspaceWheelTypeRing
(fromIntegral $ x + (fromIntegral w `div` 2))
(fromIntegral $ y + (fromIntegral h `div` 2))
)
=<< pointerScreen
displayWorkspaceWheelAtPoint :: Ring WorkspaceWheelType -> Int -> Int -> X ()
displayWorkspaceWheelAtPoint wheelType px py = do
click
(W.StackSet cur vis other _floating) <- gets windowset
screen <- fromMaybe cur <$> pointerScreen
allWorkspaces <- getWorkspaces (fromMaybe Existing $ ringCur wheelType)
let (SD rect) = W.screenDetail screen
rectsToWorkspaces = calculatePositions px py rect allWorkspaces
-- rectsToWorkspaces = map (second (first W.tag)) dzenRects
dzenPipes <- mapM (uncurry launchDzen) rectsToWorkspaces
after <-
( fix $ \recur ring -> do
evt <- runMaybeT nextButtonOrKeyEvent
let stop = return ()
let retry = recur ring
case evt of
Nothing -> return stop -- timeout
Just (ButtonPress _ b)
| b == button4 ->
return (displayWorkspaceWheelAtPoint (ringAdv wheelType) px py)
| b == button5 ->
return (displayWorkspaceWheelAtPoint (ringPrev wheelType) px py)
Just (ButtonPress _ b) ->
withDisplay $ \d -> do
(_, _, _, fi -> cx, fi -> cy, _, _, _) <-
io $ X.queryPointer d =<< rootWindow d 0
case find (\(r, _) -> pointWithin cx cy r) rectsToWorkspaces of
Just (_, (ws, _)) ->
if
| b == button1 -> do
gotoWorkspace ws
return (displayWorkspaceWheelAtPoint wheelType px py)
| b == button3 -> do
selection <- getAndResetWindowSelectionOrCurrent
withWindowsUnpinned selection $
windows $
appEndo $
mconcat (map (Endo . W.shiftWin ws) selection)
return (displayWorkspaceWheelAtPoint wheelType px py)
| b == 13 -> do
winSet <- gets windowset
let mWs = find ((== ws) . W.tag) (W.workspaces winSet)
forM_ mWs $ \ws -> do
let allWindows = W.integrate' (W.stack ws)
setAlternateWindows allWindows
mapM_ (`setAlternateWorkspace` W.tag ws) allWindows
mapM_ killWindow allWindows
-- Preemptively delete the windows so we show the updates
-- immediately.
windows $
appEndo $
mconcat $
map (Endo . W.delete) allWindows
X.refresh
return (displayWorkspaceWheelAtPoint wheelType px py)
| otherwise -> return stop
Nothing -> return stop
Just (KeyPress _ sym _ _) -> do
if
| sym == xK_l -> do
mapM_ deselect (ringCur ring)
let ring' = ringAdv ring
mapM_ select (ringCur ring')
recur ring'
| sym == xK_h -> do
mapM_ deselect (ringCur ring)
let ring' = ringPrev ring
mapM_ select (ringCur ring')
recur ring'
| sym == xK_space || sym == xK_Return -> do
mapM_
(\(_, (_, (ws, _))) -> gotoWorkspace ws)
(ringCur ring)
return stop
| otherwise -> return stop
)
( ringFastForwardTo (\(_, (_, (_, n))) -> n == Current) $
mkRing $
zip dzenPipes rectsToWorkspaces
)
io $ do
mapM_ hClose dzenPipes
after
where
fi :: (Integral a, Num b) => a -> b
fi = fromIntegral
toInt :: (Integral a) => a -> Int
toInt = fromIntegral
deselect :: (Handle, (Rectangle, (String, ScreenType))) -> X ()
deselect (pipe, (rect, (wsName, screenType))) = io $ hPutStrLn pipe wsName
select :: (Handle, (Rectangle, (String, ScreenType))) -> X ()
select (pipe, (rect, (wsName, screenType))) =
io $ hPutStrLn pipe $ printf "^fg(#ff0)%s" wsName
getWorkspaces :: WorkspaceWheelType -> X [(WorkspaceId, ScreenType)]
getWorkspaces Existing = do
(W.StackSet cur vis other _floating) <- gets windowset
return $
map (first W.tag) $
sortBy (comparing (W.tag . fst)) $
((W.workspace cur, Current) : map ((,Visible) . W.workspace) vis)
++ filter
(\(w, _) -> (not . null . W.integrate' . W.stack) w && W.tag w /= "*")
(map (,Hidden) other)
getWorkspaces (Alphabet isGreek b) =
getWorkspacesForAlphabet
( map (if b then id else toLower) $
if isGreek then "ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ" else "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
)
getWorkspaces Digits =
getWorkspacesForAlphabet "0123456789"
getWorkspacesForAlphabet a = do
(W.StackSet cur vis other _floating) <- gets windowset
let screenTypeById =
Map.fromList $
map (first (W.tag . W.workspace)) $
(cur, Current) : map (,Visible) vis
return $
map
(\c -> ([c], fromMaybe Hidden $ Map.lookup [c] screenTypeById))
a
-- Returns, starting x, dzen width
calculatePositions :: Int -> Int -> Rectangle -> [a] -> [(Rectangle, a)]
calculatePositions
pointerX'
pointerY'
(Rectangle (toInt -> x) (toInt -> y) (toInt -> w) (toInt -> h))
as =
let radius :: (Num a) => a
radius = 300
nas = max 2 (length as)
dPhi = 2 * pi / fromIntegral nas
circDiff = sqrt $ (radius * cos dPhi - radius) ** 2 + (radius * sin dPhi) ** 2
size :: (Integral a) => a
size = round $ min 100 (circDiff * 0.5)
sizeD2 :: (Integral a) => a
sizeD2 = fi size `div` 2
pointerX =
max (radius + sizeD2 + x) $
min pointerX' (x + w - radius - sizeD2)
pointerY =
max (radius + sizeD2 + y) $
min pointerY' (y + h - radius - sizeD2)
in zipWith
( \phi a ->
let xpos = radius * cos phi + fi pointerX
ypos = radius * sin phi + fi pointerY
r =
Rectangle
(round xpos - sizeD2)
(round ypos - sizeD2)
(fi size)
(fi size)
in (r, a)
)
[3 * pi / 2, 3 * pi / 2 + dPhi ..]
as
embedRectangle :: Rectangle -> Int -> Int -> Rectangle
embedRectangle r@(Rectangle x y w h) w' h' | w' > fi w || h' > fi h = r
embedRectangle (Rectangle x y w h) w' h' =
Rectangle
(x + fi (w `div` 2) - fi (w' `div` 2))
(y + fi (h `div` 2) - fi (h' `div` 2))
(fi w')
(fi h')
launchDzen :: Rectangle -> (String, ScreenType) -> X Handle
launchDzen (Rectangle x y w h) (wsTag, screenType) = do
pipe <-
spawnPipe
( printf
"dzen2 -fn \"Monofur Nerd Font:size=15\" -x %d -y %d -w %d -h %d -bg '#222' -fg '%s'"
x
y
w
h
( case screenType of
Current -> "#ff8888"
Visible -> "#8888ff"
Hidden -> "#aaa"
)
)
io $ do
hPutStrLn pipe wsTag
hFlush pipe
return pipe
|