Add a keybinding list on M-F1; match games by class pattern; update workspace icons

All keys get descriptions (NamedActions) and M-F1 lists them in dmenu. Proton
and Wine games go to ws 3 by class prefix/suffix instead of one rule per game;
GIMP and Audacity go to ws 5; small tools float; new icons for ws 2-7.
This commit is contained in:
Daniele Fucini
2026-10-26 17:49:04 +01:00
parent efea899ccb
commit 68bbcaeb82
+109 -49
View File
@@ -1,7 +1,7 @@
-- Import -- Import
import Data.Monoid import Data.Monoid
import Graphics.X11.ExtraTypes.XF86 import Data.Char (toLower)
import Control.Monad (forM_, liftM2, when) import Control.Monad (forM_, liftM2, when)
import Data.Maybe (isJust) import Data.Maybe (isJust)
import XMonad import XMonad
@@ -27,9 +27,11 @@ import XMonad.Util.ClickableWorkspaces
import XMonad.Util.EZConfig import XMonad.Util.EZConfig
import qualified XMonad.Util.Hacks as Hacks import qualified XMonad.Util.Hacks as Hacks
import XMonad.Util.Loggers import XMonad.Util.Loggers
import XMonad.Util.NamedActions
import XMonad.Util.NamedScratchpad import XMonad.Util.NamedScratchpad
import XMonad.Util.Run (runProcessWithInput) import XMonad.Util.Run (runProcessWithInput, spawnPipe)
import XMonad.Util.SpawnOnce import XMonad.Util.SpawnOnce
import System.IO (hClose, hPutStr)
-- Main -- Main
main :: IO () main :: IO ()
@@ -105,7 +107,7 @@ myScratchpads =
-- Config -- Config
myConfig = myConfig =
def addDescrKeys' ((myModMask, xK_F1), showKeys) myKeys $ def
{ modMask = myModMask, { modMask = myModMask,
terminal = myTerminal, terminal = myTerminal,
workspaces = myWorkspaces, workspaces = myWorkspaces,
@@ -117,44 +119,96 @@ myConfig =
handleEventHook = myHandleEventHook, handleEventHook = myHandleEventHook,
logHook = myLogHook logHook = myLogHook
} }
`additionalKeysP` [ ("M-b", sendMessage ToggleStruts),
("M-S-l", spawn "loginctl lock-session"), -- Keys
("M-S-n", spawn "dunstctl history-pop"), -- xmonad's described defaults minus the ones overridden here, so M-<F1> lists each key once
("M-C-n", spawn "dunstctl close-all"), myKeys :: XConfig Layout -> [((KeyMask, KeySym), NamedAction)]
("<Print>", unGrab *> spawn ("scrot " ++ myScreenshotDir ++ myScreenshotName)), myKeys c = dropEmptyGroups (filter ((`notElem` overridden) . fst) (defaultKeysDescr c)) ++ custom
("S-<Print>", unGrab *> spawn ("scrot -s " ++ myScreenshotDir ++ myScreenshotName)), where
("M-S-<Print>", unGrab *> spawn ("scrot -u " ++ myScreenshotDir ++ myScreenshotName)), custom = myCustomKeys c
("M-d", spawn "dmenu_run"), -- M-r/M-S-r: there's no third screen
("M-p", spawn "passmenu -i"), overridden = (myModMask, xK_r) : (myModMask .|. shiftMask, xK_r) : filter (/= (0, 0)) (map fst custom)
("M-f", sendMessage $ JumpToLayout "Tabbed"), -- (0, 0) is a subtitle or separator; drop it when nothing follows before the next one
("M-i", spawn $ "setxkbmap" ++ myKeyboardLayoutIT), dropEmptyGroups (((0, 0), _) : rest@(((0, 0), _) : _)) = dropEmptyGroups rest
("M-u", spawn $ "setxkbmap" ++ myKeyboardLayoutUS), dropEmptyGroups [((0, 0), _)] = []
("M-s", spawn $ "setxkbmap" ++ myKeyboardLayoutSK), dropEmptyGroups (x : rest) = x : dropEmptyGroups rest
("M-S-v", namedScratchpadAction myScratchpads "Riseup-vpn"), dropEmptyGroups [] = []
("M-S-s", namedScratchpadAction myScratchpads "Terminal"),
("M-S-a", namedScratchpadAction myScratchpads "Cal"), myCustomKeys :: XConfig Layout -> [((KeyMask, KeySym), NamedAction)]
("M-o", namedScratchpadAction myScratchpads "Btop"), myCustomKeys c =
("M-x", spawn "bluetoothctl connect F4:9D:8A:B9:8C:EE"), concatMap
("M-S-x", spawn "bluetoothctl disconnect F4:9D:8A:B9:8C:EE") (\(t, ks) -> subtitle t : mkNamedKeymap c ks)
[ ( "launchers",
[ ("M-d", addName "Run a program (dmenu)" $ spawn "dmenu_run"),
("M-p", addName "Passwords (passmenu)" $ spawn "passmenu -i"),
("<XF86HomePage>", addName "Browser" $ spawn myBrowser),
("<XF86Calculator>", addName "Calculator" $ spawn "qalculate-gtk")
] ]
`additionalKeys` [ ((0, xF86XK_AudioMute), spawn "pactl set-sink-mute $(pactl get-default-sink) toggle"), ),
((0, xF86XK_AudioLowerVolume), spawn "pactl set-sink-volume $(pactl get-default-sink) -10%"), ( "scratchpads",
((0, xF86XK_AudioRaiseVolume), spawn "wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 10%+"), [ ("M-S-s", addName "Terminal" $ namedScratchpadAction myScratchpads "Terminal"),
((0, xF86XK_AudioPlay), spawn "playerctl play-pause"), ("M-S-a", addName "Calendar (calcurse)" $ namedScratchpadAction myScratchpads "Cal"),
((0, xF86XK_AudioPause), spawn "playerctl pause"), ("M-o", addName "btop" $ namedScratchpadAction myScratchpads "Btop"),
((0, xF86XK_AudioStop), spawn "playerctl stop"), ("M-S-v", addName "RiseupVPN" $ namedScratchpadAction myScratchpads "Riseup-vpn"),
((0, xF86XK_AudioNext), spawn "playerctl next"), ("<XF86Mail>", addName "Mail (mutt)" $ namedScratchpadAction myScratchpads "Mail"),
((0, xF86XK_AudioPrev), spawn "playerctl previous"), ("<XF86Tools>", addName "Music (ncmpcpp)" $ namedScratchpadAction myScratchpads "Music")
((0, xF86XK_Calculator), spawn "qalculate-gtk"),
((0, xF86XK_HomePage), spawn myBrowser),
((0, xF86XK_Mail), namedScratchpadAction myScratchpads "Mail"),
((0, xF86XK_Tools), namedScratchpadAction myScratchpads "Music")
] ]
++ [ ((myModMask .|. mask, key), f sc) ),
| (key, sc) <- zip [xK_w, xK_e] [0 ..], ( "session and layout",
(f, mask) <- [(viewScreen def, 0), (sendToScreen def, shiftMask)] [ ("M-S-l", addName "Lock the screen" $ spawn "loginctl lock-session"),
("M-S-n", addName "Show older notification" $ spawn "dunstctl history-pop"),
("M-C-n", addName "Close all notifications" $ spawn "dunstctl close-all"),
("M-b", addName "Toggle the status bar gap" $ sendMessage ToggleStruts),
("M-f", addName "Tabbed layout" $ sendMessage $ JumpToLayout "Tabbed"),
("M-i", addName "Keyboard: Italian" $ spawn $ "setxkbmap" ++ myKeyboardLayoutIT),
("M-u", addName "Keyboard: US" $ spawn $ "setxkbmap" ++ myKeyboardLayoutUS),
("M-s", addName "Keyboard: Slovak" $ spawn $ "setxkbmap" ++ myKeyboardLayoutSK),
("M-x", addName "Connect earbuds" $ spawn "bluetoothctl connect F4:9D:8A:B9:8C:EE"),
("M-S-x", addName "Disconnect earbuds" $ spawn "bluetoothctl disconnect F4:9D:8A:B9:8C:EE")
] ]
++ [((myModMask, key), windows $ safeView ws) | (ws, key) <- zip myWorkspaces [xK_1 .. xK_9]] ),
( "screenshots",
[ ("<Print>", addName "Whole screen" $ unGrab *> spawn ("scrot " ++ myScreenshotDir ++ myScreenshotName)),
("S-<Print>", addName "Select an area" $ unGrab *> spawn ("scrot -s " ++ myScreenshotDir ++ myScreenshotName)),
("M-S-<Print>", addName "Focused window" $ unGrab *> spawn ("scrot -u " ++ myScreenshotDir ++ myScreenshotName))
]
),
( "media keys",
[ ("<XF86AudioMute>", addName "Mute" $ spawn "pactl set-sink-mute $(pactl get-default-sink) toggle"),
("<XF86AudioLowerVolume>", addName "Volume down" $ spawn "pactl set-sink-volume $(pactl get-default-sink) -10%"),
("<XF86AudioRaiseVolume>", addName "Volume up" $ spawn "wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 10%+"),
("<XF86AudioPlay>", addName "Play/pause" $ spawn "playerctl play-pause"),
("<XF86AudioPause>", addName "Pause" $ spawn "playerctl pause"),
("<XF86AudioStop>", addName "Stop" $ spawn "playerctl stop"),
("<XF86AudioNext>", addName "Next track" $ spawn "playerctl next"),
("<XF86AudioPrev>", addName "Previous track" $ spawn "playerctl previous")
]
),
( "workspaces and screens",
[ (m ++ show n, addName (d ++ show n ++ " (" ++ name ++ ")") $ windows $ f ws)
| (n, ws, name) <- zip3 [1 :: Int ..] myWorkspaces ["terminal", "web", "games", "chat", "media", "docs", "VM", "free", "free"],
(m, d, f) <- [("M-", "Workspace ", safeView), ("M-S-", "Send window to workspace ", W.shift)]
]
++ [ (m ++ k, addName (d ++ side) $ f def sc)
| (k, sc, side) <- [("w", 0, "left screen"), ("e", 1, "right screen")],
(m, d, f) <- [("M-", "Focus ", viewScreen), ("M-S-", "Send window to ", sendToScreen)]
]
)
]
-- Shows the key list in dmenu (type to filter; picking a line does nothing).
-- spawnPipe rather than runProcessWithInput, which would block xmonad while dmenu is open.
showKeys :: [((KeyMask, KeySym), NamedAction)] -> NamedAction
showKeys ks = addName "Show keybindings" $ do
h <- spawnPipe "dmenu -i -l 30 -p Keys"
io $ hPutStr h (unlines $ map tidy $ filter (not . null) $ lines $ unlines $ showKm ks) >> hClose h
where
-- showKm writes the mod key as M4- (it doesn't know modMask); re-pad after shortening it
tidy l@('>' : _) = l
tidy l = let (k, d) = break (== ' ') l in take 24 (fixMod k ++ repeat ' ') ++ dropWhile (== ' ') d
fixMod ('M' : '4' : '-' : r) = "M-" ++ fixMod r
fixMod (x : r) = x : fixMod r
fixMod [] = []
-- greedyView that keeps wsGames on screen 0 (HDMI): Wine games lose the mouse when moved between screens -- greedyView that keeps wsGames on screen 0 (HDMI): Wine games lose the mouse when moved between screens
safeView :: WorkspaceId -> WindowSet -> WindowSet safeView :: WorkspaceId -> WindowSet -> WindowSet
@@ -185,14 +239,15 @@ safeRescreen = do
myWorkspaces :: [String] myWorkspaces :: [String]
myWorkspaces = [wsTerm, wsWeb, wsGames, wsChat, wsMedia, wsDocs, wsVM] ++ map show [8, 9] myWorkspaces = [wsTerm, wsWeb, wsGames, wsChat, wsMedia, wsDocs, wsVM] ++ map show [8, 9]
-- Icons spill past their advance in the non-Mono Nerd Font: keep them ~1000-1160 units wide or gaps look uneven
wsTerm, wsWeb, wsGames, wsChat, wsMedia, wsDocs, wsVM :: String wsTerm, wsWeb, wsGames, wsChat, wsMedia, wsDocs, wsVM :: String
wsTerm = "1:\xf489 " wsTerm = "1:\xf489 "
wsWeb = "2:\xe743 " wsWeb = "2:\xf0ac "
wsGames = "3:\xf1b6 " wsGames = "3:\xf11b "
wsChat = "4:\xf10b " wsChat = "4:\xf086 "
wsMedia = "5:\xead9 " wsMedia = "5:\xefab "
wsDocs = "6:\xeb69 " wsDocs = "6:\xede2 "
wsVM = "7:\xea7a " wsVM = "7:\xf108 "
-- Layout -- Layout
myLayoutHook = onWorkspace wsWeb myWebLayout $ onWorkspace wsGames myGamesLayout $ onWorkspace wsMedia myVideoLayout myDefaultLayout myLayoutHook = onWorkspace wsWeb myWebLayout $ onWorkspace wsGames myGamesLayout $ onWorkspace wsMedia myVideoLayout myDefaultLayout
@@ -290,10 +345,15 @@ myStartupHook = do
myManageHook :: ManageHook myManageHook :: ManageHook
myManageHook = myManageHook =
composeAll composeAll
[ className =? "Gpodder" --> doShift wsMedia, [ -- Proton games are steam_app_<id>; Wine windows are named after their .exe
className ^? "steam_app_" --> doShift wsGames,
className $? ".exe" --> doShift wsGames,
className =? "Gpodder" --> doShift wsMedia,
className =? "An Anime Game Launcher" --> doShift wsGames, className =? "An Anime Game Launcher" --> doShift wsGames,
className =? "Qalculate-gtk" --> doFloat, className =? "Qalculate-gtk" --> doFloat,
title =? "Genshin Impact" --> doShift wsGames, title =? "Genshin Impact" --> doShift wsGames,
classIn ["gimp", "audacity"] --> doShift wsMedia,
classIn ["pavucontrol", "org.pulseaudio.pavucontrol", "zenity", "org.gnome.zenity", "lxappearance", "qt5ct", "qt6ct"] --> doFloat,
className =? "Signal" --> doShift wsChat, className =? "Signal" --> doShift wsChat,
className =? "Spotify" --> doShift wsMedia, className =? "Spotify" --> doShift wsMedia,
className =? "Transmission-gtk" --> doShift wsWeb, className =? "Transmission-gtk" --> doShift wsWeb,
@@ -303,7 +363,6 @@ myManageHook =
className =? "Xreader" --> doShift wsDocs, className =? "Xreader" --> doShift wsDocs,
className =? "calibre" --> doShift wsDocs, className =? "calibre" --> doShift wsDocs,
className =? "discord" --> doShift wsChat, className =? "discord" --> doShift wsChat,
className =? "explorer.exe" --> doShift wsGames,
className =? "feh" --> doFloat, className =? "feh" --> doFloat,
title =? "glxgears" --> doFloat, title =? "glxgears" --> doFloat,
className =? "library_manager" --> doShift wsDocs, className =? "library_manager" --> doShift wsDocs,
@@ -316,9 +375,7 @@ myManageHook =
className =? "scrcpy" --> doShift wsMedia, className =? "scrcpy" --> doShift wsMedia,
className =? "steam" --> doShift wsGames, className =? "steam" --> doShift wsGames,
className =? "steam_app_109600" --> doFloat, className =? "steam_app_109600" --> doFloat,
className =? "steam_app_109600" --> doShift wsGames,
className =? "steam_app_289070" --> doFloat, className =? "steam_app_289070" --> doFloat,
className =? "steam_app_289070" --> doShift wsGames,
className =? "VirtualBox Machine" --> doShift wsVM, className =? "VirtualBox Machine" --> doShift wsVM,
className =? "VirtualBox Machine" --> doFullFloat, className =? "VirtualBox Machine" --> doFullFloat,
className =? "VirtualBox Manager" --> doShift wsVM, className =? "VirtualBox Manager" --> doShift wsVM,
@@ -328,7 +385,10 @@ myManageHook =
isFullscreen --> doFullFloat isFullscreen --> doFullFloat
] ]
<+> namedScratchpadManageHook myScratchpads <+> namedScratchpadManageHook myScratchpads
where viewShift = doF . liftM2 (.) safeView W.shift where
viewShift = doF . liftM2 (.) safeView W.shift
-- Case-insensitive, as GTK4 and firejailed apps capitalise their class differently
classIn names = (`elem` names) . map toLower <$> className
-- Handle event hook -- Handle event hook
myHandleEventHook :: Event -> X All myHandleEventHook :: Event -> X All