1 {-# LANGUAGE GHC2024 #-}
2 {-# OPTIONS_GHC -Wall #-}
3 {-# OPTIONS_GHC -fno-warn-missing-signatures #-}
5 import qualified Data.Char as Char
6 import Data.List as List
9 import qualified Data.Map as Map
10 import Control.Arrow (first)
12 -- import XMonad.Actions.DwmPromote
13 -- import XMonad.Actions.Warp
14 -- import XMonad.Layout.Maximize
15 -- import XMonad.Layout.Monitor
16 -- import XMonad.Layout.ResizableTile
17 -- import XMonad.Layout.TabBarDecoration
18 -- import XMonad.Util.EZConfig
19 -- import XMonad.Util.EZConfig(additionalKeys)
20 -- import XMonad.Util.WorkspaceCompare
21 import XMonad hiding ((|||))
22 import XMonad.Actions.CopyWindow
23 import XMonad.Actions.CycleWS
24 import XMonad.Actions.SwapWorkspaces
25 import XMonad.Actions.UpdatePointer
26 import XMonad.Config.Azerty
27 import XMonad.Hooks.DynamicLog
28 import XMonad.Hooks.EwmhDesktops
29 import XMonad.Hooks.ManageDocks
30 import XMonad.Hooks.ManageHelpers
31 import XMonad.Hooks.RefocusLast
32 import XMonad.Hooks.Rescreen
33 import XMonad.Hooks.SetWMName
34 import XMonad.Hooks.StatusBar
35 import XMonad.Hooks.UrgencyHook
36 import XMonad.Layout.Columns
37 import XMonad.Layout.Fullscreen
38 import XMonad.Layout.Grid
39 import XMonad.Layout.LayoutCombinators
40 import XMonad.Layout.Magnifier
41 import XMonad.Layout.MultiToggle
42 import XMonad.Layout.MultiToggle.Instances
43 import XMonad.Layout.NoBorders
44 import XMonad.Layout.ResizableTile
45 import XMonad.Layout.Spiral
46 import XMonad.Layout.Tabbed
47 import XMonad.Layout.ThreeColumns
48 import XMonad.Layout.WindowNavigation
49 import XMonad.ManageHook
50 import XMonad.Operations (unGrab)
52 import XMonad.Prompt.FuzzyMatch
53 import XMonad.Prompt.Pass
54 import XMonad.Prompt.Window
55 import XMonad.Util.NamedScratchpad
56 import XMonad.Util.SpawnOnce
57 import qualified XMonad.StackSet as W
60 withUrgencyHook NoUrgencyHook $
61 -- dzenUrgencyHook { args = ["-bg", "darkgreen", "-xs", "1"] } $
62 --addAfterRescreenHook myAfterRescreenHook $
63 addRandrChangeHook (spawnExec "autorandr --change") $
64 dynamicSBs barSpawner $
66 setEwmhActivateHook doAskUrgent $
71 , focusFollowsMouse = True
72 , focusedBorderColor = "#00b10b"
73 , handleEventHook = handleEventHook def
74 , keys = \conf@XConfig{XMonad.modMask} ->
76 let xK_XF86Backward = 0x1008ff26
77 xK_XF86Forward = 0x1008ff27 in
80 ((modMask, xK_Return), spawnExec $ XMonad.terminal conf)
82 , ((modMask, xK_Menu), spawnCommand)
83 , ((modMask, xK_space), spawnCommand)
84 -- Browse the filesystem
85 , ((modMask, xK_BackSpace), spawnExec "systemd-run --user --unit=app-org.rofi.caja@$RANDOM -p CollectMode=inactive-or-failed caja")
88 , ((0, xK_Pause), unGrab >> spawnExec "loginctl lock-session \"$XDG_SESSION_ID\"")
89 , ((modMask, xK_Delete), unGrab >> spawnExec "loginctl lock-session \"$XDG_SESSION_ID\"")
91 -- Take a full screenshot
92 , ((0, xK_Print), spawn "mkdir -p ~/Images/screenshots && scrot --quality 42 ~/Images/screenshots/'%Y-%m-%d_%H-%M-%S.png' && caja ~/Images/screenshots")
93 -- Take a selective screenshot
94 , ((modMask, xK_Print), spawn "select-screenshot")
97 , ((0, 0x1008FF12), spawnExec "pactl -- set-sink-mute @DEFAULT_SINK@ toggle") -- XF88AudioMute
98 , ((0, 0x1008FF11), spawnExec "pactl -- set-sink-volume @DEFAULT_SINK@ -5%") -- XF86AudioLowerVolume
99 , ((0, 0x1008FF13), spawnExec "pactl -- set-sink-volume @DEFAULT_SINK@ +5%") -- XF86AudioRaiseVolume
101 -- , ((0, 0x1008FF16), spawnExec "")
103 -- , ((0, 0x1008FF14), spawnExec "")
105 -- , ((0, 0x1008FF17), spawnExec "")
107 -- , ((0, 0x1008FF2C), spawnExec "eject -T")
109 -- Close focused window.
110 , ((modMask, xK_Escape), kill)
113 , ((modMask, xK_c), spawnExec "clipster --select --primary")
115 -- Temporarily maximize a window
116 , ((modMask, xK_f), sendMessage $ XMonad.Layout.MultiToggle.Toggle FULL)
117 -- , ((modMask, xK_f), withFocused (sendMessage . maximizeRestore))
119 -- Cycle through the available layout algorithms
120 , ((modMask, 0x13bd), sendMessage NextLayout) -- oe (²)
121 , ((modMask, xK_ampersand), sendMessage $ JumpToLayout "ResizableTall") -- & (1)
122 , ((modMask, xK_eacute), sendMessage $ JumpToLayout "Mirror ResizableTall") -- é (2)
123 , ((modMask, xK_quotedbl), sendMessage $ JumpToLayout "Tabbed Simplest") -- ' (3)
124 , ((modMask, xK_apostrophe), sendMessage $ JumpToLayout "Magnifier Grid") -- " (4)
125 , ((modMask, xK_parenleft), sendMessage $ JumpToLayout "Spiral") -- ( (5)
126 , ((modMask, xK_minus), sendMessage $ JumpToLayout "Full") -- - (6)
127 , ((modMask, xK_egrave), sendMessage $ JumpToLayout "ThreeCol") -- è (7)
128 , ((modMask, xK_underscore), sendMessage $ JumpToLayout "Columns") -- _ (8)
130 -- Reset the layouts on the current workspace to default
131 -- , ((modMask .|. shiftMask, xK_space), setLayout $ XMonad.layoutHook conf)
133 -- Resize viewed windows to the correct size.
134 , ((modMask, xK_n), refresh)
136 -- Move focus to the master window
137 , ((modMask .|. shiftMask, xK_exclam), windows W.focusMaster)
138 -- Swap the focused window and the master window
139 , ((modMask, xK_exclam), windows W.swapMaster)
141 -- Swap the focused window with the next window.
142 --, ((modMask, xK_o), windows W.swapDown >> windows W.focusMaster)
143 -- Swap the focused window with the previous window.
144 , ((modMask, xK_m), windows W.swapUp >> windows W.focusMaster)
147 , ((modMask, xK_h), sendMessage $ Go L)
148 , ((modMask, xK_m), sendMessage $ Go R)
149 --, ((modMask, xK_i), sendMessage $ Go U)
150 --, ((modMask, xK_k), sendMessage $ Go D)
151 , ((modMask, xK_i), windows W.focusUp)
152 , ((modMask, xK_k), windows W.focusDown)
153 , ((modMask, xK_j), prevWS)
154 , ((modMask, xK_l), nextWS)
155 , ((modMask, xK_Left), windows W.focusUp)
156 , ((modMask, xK_Right), windows W.focusDown)
157 --, ((modMask, xK_Left), onGroup W.focusUp')
158 --, ((modMask, xK_Right), onGroup W.focusDown')
159 , ((modMask, xK_Up), sendMessage $ Go U)
160 , ((modMask, xK_Down), sendMessage $ Go D)
163 , ((modMask .|. shiftMask, xK_h), sendMessage MoveLeft)
164 , ((modMask .|. shiftMask, xK_m), sendMessage MoveRight)
165 , ((modMask .|. controlMask, xK_h), sendMessage MoveLeft)
166 , ((modMask .|. controlMask, xK_m), sendMessage MoveRight)
167 , ((modMask .|. controlMask, xK_i), sendMessage MoveUp)
168 , ((modMask .|. controlMask, xK_k), sendMessage MoveDown)
169 , ((modMask .|. controlMask, xK_j), shiftToPrev >> prevWS)
170 , ((modMask .|. controlMask, xK_l), shiftToNext >> nextWS)
173 , ((modMask .|. shiftMask, xK_l), sendMessage HorizontalExpand)
174 , ((modMask .|. shiftMask, xK_j), sendMessage HorizontalShrink)
175 , ((modMask .|. shiftMask, xK_i), sendMessage VerticalExpand)
176 , ((modMask .|. shiftMask, xK_k), sendMessage VerticalShrink)
178 -- Push window back into tiling.
179 , ((modMask, xK_t), withFocused $ windows . W.sink)
181 -- Change the number of windows in the master area
182 , ((modMask, xK_Up), sendMessage $ IncMasterN 1)
183 , ((modMask, xK_Down), sendMessage $ IncMasterN (-1))
185 -- Toggle the status bar gap.
186 , ((modMask, xK_b), sendMessage ToggleStruts)
189 , ((modMask .|. shiftMask, xK_End), io exitSuccess)
191 , ((modMask, xK_End), restart "xmonad" True)
193 , ((modMask, xK_p), passPrompt promptConfig)
194 , ((modMask .|. controlMask, xK_p), passGeneratePrompt promptConfig)
195 , ((modMask .|. controlMask .|. shiftMask, xK_p), passRemovePrompt promptConfig)
196 , ((modMask, xK_Tab), windowMultiPrompt promptConfig [(Goto, allWindows), (Goto, wsWindows)])
198 -- Jump to latest viewed workspace
199 , ((modMask, xK_less), toggleWS' ["NSP"])
201 -- Workspace management
202 -- XF86Back: Switch to previous workspace
203 , ((0, xK_XF86Backward), prevWS)
204 , ((modMask, xK_Page_Up), prevWS)
205 -- Switch to next workspace
206 , ((0, xK_XF86Forward), nextWS)
207 , ((modMask, xK_Page_Down), nextWS)
208 -- XF86Back: Move the current client to the previous workspace and go there
209 , ((modMask, xK_XF86Backward), shiftToPrev >> prevWS)
210 -- Move the current client to the next workspace and go there
211 , ((modMask, xK_XF86Forward), shiftToNext >> nextWS)
212 -- Switch to previous workspace
213 -- Switch to next workspace
215 -- Move the current client to the previous workspace
216 , ((0 .|. shiftMask , xK_XF86Backward), shiftToPrev )
217 -- Move the current client to the next workspace
218 , ((0 .|. shiftMask , xK_XF86Forward), shiftToNext )
221 -- Toggle copying window on all workspaces (sticky window)
222 , ((modMask, xK_s), do
223 copies <- wsContainingCopies -- NOTE: consider only hidden workspaces
225 [] -> windows copyToAll
226 _ -> killAllOtherCopies
229 -- Resize the master area
230 --, ((modMask, xK_Left), sendMessage Shrink)
231 --, ((modMask, xK_Right), sendMessage Expand)
232 -- Resize windows in ResizableTall mode
233 --, ((modMask .|. shiftMask, xK_Left), sendMessage MirrorShrink)
234 --, ((modMask .|. shiftMask, xK_Right), sendMessage MirrorExpand)
237 -- Dynamic scratchpads
239 [ [ ((modMask .|. altMask .|. controlMask, key), withFocused $ toggleDynamicNSP name)
240 , ((modMask .|. altMask, key), dynamicNSPAction name)
242 | (key, chr) <- zip [xK_a..] ['a'..'z']
248 -- Note: those keybindings override dynamic scratchpads above
249 [ ((modMask .|. altMask, xK_b), namedScratchpadAction scratchpads "btop")
250 , ((modMask .|. altMask, xK_c), namedScratchpadAction scratchpads "中文")
251 , ((modMask .|. altMask, xK_d), namedScratchpadAction scratchpads "dino")
252 , ((modMask .|. altMask, xK_e), namedScratchpadAction scratchpads "english")
253 , ((modMask .|. altMask, xK_h), namedScratchpadAction scratchpads "htop")
254 , ((modMask .|. altMask, xK_m), namedScratchpadAction scratchpads "matrix")
255 , ((modMask .|. altMask, xK_n), namedScratchpadAction scratchpads "notes")
256 , ((modMask .|. altMask, xK_o), namedScratchpadAction scratchpads "concerns")
257 , ((modMask .|. altMask, xK_s), namedScratchpadAction scratchpads "signal")
258 , ((modMask .|. altMask, xK_v), namedScratchpadAction scratchpads "pavucontrol")
259 , ((modMask .|. altMask, xK_space), namedScratchpadAction scratchpads "terminal")
260 , ((modMask .|. altMask, xK_Return), namedScratchpadAction scratchpads "terminal")
261 , ((modMask .|. altMask, xK_BackSpace), resetFocusedNSP)
264 -- mod-[F1..F9], Switch to workspace N
265 [ ((modMask, k), windows $ W.greedyView i)
266 | (i, k) <- zip (workspaces conf) [xK_F1 ..] ++
267 zip (workspaces conf) [xK_1 ..]
270 -- mod-shift-[F1..F9], Move client to workspace N
271 [ ((modMask .|. shiftMask, k), windows $ W.shift i)
272 | (i, k) <- zip (workspaces conf) [xK_F1 ..] ++
273 zip (workspaces conf) [xK_1 ..]
276 {- NOTE: with Xinerama
277 [((m .|. modMask, k), windows $ onCurrentScreen f i)
278 | (i, k) <- zip (workspaces' conf) [xK_F1 ..]
279 , (f, m) <- [(W.greedyView, 0), (W.shift, shiftMask)] ]
282 -- mod-{w,e,r}, Switch to physical/Xinerama screens 1, 2, or 3
283 [ ((modMask, key), screenWorkspace sc >>= flip whenJust (windows . W.view))
284 | (key, sc) <- zip [xK_w, xK_e, xK_r] [0 ..]
287 -- mod-shift-{w,e,r}, Move client to screen 1, 2, or 3
288 [ ((modMask .|. shiftMask, key), screenWorkspace sc >>= flip whenJust (windows . W.shift))
289 | (key, sc) <- zip [xK_w, xK_e, xK_r] [0 ..]
292 -- mod-ctrl-[F1..F9], Swap workspace with workspace N
293 -- mod-ctrl-[1..9], Swap workspace with workspace N
294 [ ((modMask .|. controlMask, k), windows $ swapWithCurrent i)
295 | (i, k) <- zip (workspaces conf) [xK_F1 ..] ++
296 zip (workspaces conf) [xK_1 ..]
298 {- NOTE: with Xinerama
299 [((modMask .|. controlMask, k), windows $ onCurrentScreen swapWithCurrent i)
300 | (i, k) <- zip (workspaces' conf) [xK_F1 ..] ]
302 , layoutHook = smartBorders $
303 mkToggle (NOBORDERS ?? FULL ?? EOT) $ -- enable temporarily maximizing a window
304 avoidStruts $ -- prevents windows from overlapping dock windows
305 let tall = ResizableTall 1 (1%200) (8%13) [] in
307 -- addTabs shrinkText tabBar (subLayout [] Simplest (Columns 1 []))
309 ||| tabbed shrinkText tabConfig
312 ||| magnifiercz (13%10) Grid
314 ||| noBorders (fullscreenFull Full)
315 ||| ThreeColMid 1 (1%200) (1%2)
316 -- ||| Tall 1 (3/100) (1/2)
317 , manageHook = composeAll
318 -- [ , isFullscreen --> (doF W.focusDown <+> doFullFloat)
319 [ isFullscreen --> doFullFloat
321 , manageDocks -- NOTE: do not tile dock windows
322 , namedScratchpadManageHook scratchpads
323 , resource =? "desktop_window" --> doIgnore
324 , className =? "Gimp" --> doFloat
325 , resource =? "gpicview" --> doSink
326 , className =? "mpv" --> doFloat
327 , className =? "ultrastardx" --> doSink
328 --, className =? "MPlayer" --> doShift "3:media" -- <+> doFloat
329 --, className =? "vlc" --> doShift "3:media"
330 , className =? "trayer" --> doIgnore
333 , mouseBindings = \XConfig{XMonad.modMask} ->
336 -- mod-button1, Set the window to floating mode and move by dragging
337 ((modMask, button1), floatMoveNoexclusive)
339 -- mod-button2, Raise the window to the top of the stack
340 , ((modMask, button2), \w -> focus w >> windows W.swapMaster)
342 -- mod-button3, Set the window to floating mode and resize by dragging
343 , ((modMask, button3), resizeNoexclusive)
345 , ((modMask, button4), \_ -> windows W.focusUp)
346 , ((modMask, button5), \_ -> windows W.focusDown)
348 -- Cycle through workspaces
349 , ((controlMask .|. modMask, button5), nextNonEmptyWS)
350 , ((controlMask .|. modMask, button4), prevNonEmptyWS)
352 , normalBorderColor = "#7C7C7C"
355 <+> addExclusives exclusiveScratchpads
356 <+> spawnExec "wmname XMonad"
357 <+> spawnExec "xrdb -all .Xresources"
358 <+> spawn "sleep 1 && xmodmap .Xmodmap"
359 <+> spawnExec "xset r rate 250 25"
360 <+> spawnExec "xinput --set-button-map 'Logitech USB Receiver Mouse' 1 2 3 4 5 6 7 2"
361 <+> spawnExec "xset b off"
362 <+> spawnExec "xhost local:root"
363 <+> spawnExec "setxkbmap -option keypad:pointerkeys"
364 -- Useful for programs launched by rofi
365 <+> spawnExec (unwords [ "systemctl --user import-environment"
366 , "DBUS_SESSION_BUS_ADDRESS"
367 , "GDK_PIXBUF_MODULE_FILE"
368 , "GIO_EXTRA_MODULES"
376 , "LD_LIBRARY_PATH" -- For sane and pipewire
378 , "NIX_PROFILES" -- fcitx5 does not work without it…
379 , "PASSWORD_STORE_DIR"
381 , "QTWEBKIT_PLUGIN_PATH"
389 -- <+> spawnOnce "exec arbtt-capture -r 60"
390 -- <+> spawnOnce "exec parcellite"
391 -- <+> spawnOnce "exec xautolock"
392 -- <+> spawnOnce "exec redshift-gtk -l -45.7800:1.9700 -t 6500:3700"
393 <+> spawnOnce "exec nm-applet"
394 , terminal = "$TERMINAL"
395 , workspaces = {- withScreens nScreens $ -}
396 {-["1:work","2:web","3:media"] ++-}
397 map show [1::Int .. 9]
399 -- updatePointer (Relative 0.5 0.5)
401 nsHideOnFocusLoss scratchpads
402 updatePointer (0.5, 0.5) (0, 0)
406 { activeBorderColor = "#7C7C7C"
407 , activeColor = "#000000"
408 , activeTextColor = "#00FF00"
409 , inactiveBorderColor = "#7C7C7C"
410 , inactiveColor = "#000000"
411 , inactiveTextColor = "#EEEEEE"
412 , fontName = "Hack 7"
415 spawnCommand = spawnExec "rofi -show run -no-disable-history -run-command \"bash -c 'systemd-run --user --unit=app-org.rofi.\\$(systemd-escape \\\"{cmd}\\\")@\\$RANDOM -p CollectMode=inactive-or-failed {cmd}'\""
417 barSpawner :: ScreenId -> X StatusBarConfig
418 barSpawner 0 = pure $ topXmobar <> traySB
419 --barSpawner 1 = pure $ xmobar1
420 barSpawner _ = pure $ topXmobar -- nothing on the rest of the screens
422 -- Display properties of the root window:
423 -- xprop -display $DISPLAY -root
424 topXmobar = statusBarPropTo "_XMONAD_XMOBAR0" "xmobar -x 0 ~/.config/xmonad/xmobar0.hs" (pure topPP)
428 { ppCurrent = xmobarColor "black" "#CCCCCC"
429 , ppHidden = xmobarColor "#CCCCCC" "black"
430 , ppHiddenNoWindows = xmobarColor "#606060" "black"
431 , ppLayout = \s -> xmobarColor "black" "#606060" $
434 "ResizableTall" -> " | "
435 "Mirror ResizableTall" -> " - "
436 "Tabbed Simplest" -> " + "
437 "Magnifier Grid" -> " ~ "
443 , ppTitle = xmobarColor "white" "black" . shorten 50
444 , ppUrgent = xmobarColor "yellow" "black"
448 traySB :: StatusBarConfig
455 , "--distancefrom top,right"
459 , "--monitor primary"
462 , "--transparent true"
463 , "--widthtype request"
469 nextNonEmptyWS _ = moveTo Next (WSIs ((not .) <$> isWindowSpaceVisible))
470 prevNonEmptyWS _ = moveTo Prev (WSIs ((not .) <$> isWindowSpaceVisible))
472 isWindowSpaceVisible :: X (WindowSpace -> Bool)
473 isWindowSpaceVisible = do
474 vs <- gets (map (W.tag . W.workspace) . W.visible . windowset)
475 return (\w -> W.tag w `elem` vs)
477 spawnExec s = spawn $ List.unwords $ [ "exec" ] <> systemdCat <> [ s ]
478 systemdCat = [ "systemd-cat" , "--priority=info", "--stderr-priority=warning", "--level-prefix=false" , "--" ]
480 promptConfig :: XPConfig
482 { font = "xft:monospace-"<>show fontSize
486 , fgHLight = "#000000"
487 , borderColor = "darkgreen"
488 , promptBorderWidth = 1
489 , promptKeymap = promptKeyMap
490 , position = CenteredAt { xpCenterY = 0.3, xpWidth = 0.5 }
491 , height = fontSize + 11
495 , autoComplete = Nothing -- Just 500000 -- nanoseconds
496 , showCompletionOnTab = False
497 , completionKey = (0, xK_Down)
498 , prevCompletionKey = (0, xK_Up)
499 , searchPredicate = fuzzyMatch -- isPrefixOf
501 , defaultPrompter = const ""
502 , alwaysHighlight = True
503 , maxComplRows = Just 10
504 , maxComplColumns = Just 1
505 , changeModeKey = xK_twosuperior
510 promptKeyMap :: Map.Map (KeyMask,KeySym) (XP ())
511 promptKeyMap = Map.fromList $
512 List.map (first $ (,) controlMask) -- control + <key>
513 [ (xK_z, killBefore) -- kill line backwards
514 , (xK_k, killAfter) -- kill line forwards
515 , (xK_u, killBefore) -- kill line backwards
516 , (xK_a, startOfLine) -- move to the beginning of the line
517 , (xK_e, endOfLine) -- move to the end of the line
518 , (xK_m, deleteString Next) -- delete a character foward
519 , (xK_b, moveCursor Prev) -- move cursor forward
520 , (xK_f, moveCursor Next) -- move cursor backward
521 , (xK_BackSpace, killWord Prev) -- kill the previous word
522 , (xK_y, pasteString) -- paste a string
523 , (xK_g, quit) -- quit out of prompt
524 , (xK_bracketleft, quit)
527 List.map (first $ (,) altMask) -- meta key + <key>
528 [ (xK_BackSpace, killWord Prev) -- kill the prev word
529 , (xK_f, moveWord Next) -- move a word forward
530 , (xK_b, moveWord Prev) -- move a word backward
531 , (xK_d, killWord Next) -- kill the next word
532 , (xK_n, moveHistory W.focusUp') -- move up through history
533 , (xK_p, moveHistory W.focusDown') -- move down through history
536 List.map (first $ (,) 0) -- <key>
537 [ (xK_Return, setSuccess True >> setDone True)
538 , (xK_KP_Enter, setSuccess True >> setDone True)
539 , (xK_BackSpace, deleteString Prev)
540 , (xK_Delete, deleteString Next)
541 , (xK_Left, moveCursor Prev)
542 , (xK_Right, moveCursor Next)
543 , (xK_Home, startOfLine)
544 , (xK_End, endOfLine)
545 , (xK_Down, moveHistory W.focusUp')
546 , (xK_Up, moveHistory W.focusDown')
553 -- HowTo(develop): use xprop to see window properties to query them
555 [ NS { name = "btop", cmd = "$TERMINAL --title=btop -e btop", query = title =? "btop", hook }
556 , NS { name = "concerns", cmd = "$TERMINAL --title=concerns -e sh -c 'cd $HOME/work/sourcephile && vi Concerns.md'", query = title =? "concerns", hook }
557 , NS { name = "english", cmd = "$TERMINAL --title=english -e sh -c 'cd $HOME/files/notes/english && vi vocabulary.org'", query = title =? "english", hook }
558 , NS { name = "htop", cmd = "$TERMINAL --title=htop -e htop", query = title =? "htop", hook }
559 , NS { name = "matrix", cmd = "element-desktop", query = className =? "Element", hook }
560 , NS { name = "notes", cmd = "$TERMINAL --title=notes -e sh -c 'cd $HOME/files/notes && vi notes.md'", query = title =? "notes", hook }
561 , NS { name = "pavucontrol", cmd = "pavucontrol", query = className =? "pavucontrol", hook }
562 , NS { name = "signal", cmd = "signal-desktop", query = className =? "Signal", hook }
563 , NS { name = "terminal", cmd = "$TERMINAL --title=scratch-term", query = title =? "scratch-term", hook }
564 , NS { name = "中文", cmd = "$TERMINAL --title=中文 -e sh -c 'cd $HOME/files/notes/中文 && vi Vocabulary.hs'", query = title =? "中文", hook }
566 where hook = customFloating (W.RationalRect (1/2) (0) (1/2) (1))
568 exclusiveScratchpads = [ [ name | NS{name} <- scratchpads ] ]