Files
dotfiles/.config/xmonad/xmonad.hs
T
Daniele Fucini 4d905774b8 Cap XMonad volume at 100% and clean up config
- Raise volume with wpctl, capped at 100%
- Define workspace names once instead of repeating the strings
- Remove unused imports and the never-matching transmission-gtk rule
- Add missing type signatures
- Document why the isFullscreen rule is needed alongside ewmhFullscreen
2026-10-08 16:11:27 +02:00

298 lines
12 KiB
Haskell

-- Import
import Data.Monoid
import Graphics.X11.ExtraTypes.XF86
import Control.Monad (liftM2)
import XMonad
import XMonad.Actions.PhysicalScreens
import XMonad.Hooks.EwmhDesktops
import XMonad.Hooks.ManageDocks
import XMonad.Hooks.ManageHelpers
import XMonad.Hooks.Rescreen
import XMonad.Hooks.StatusBar
import XMonad.Hooks.StatusBar.PP
import XMonad.Layout.Grid
import XMonad.Layout.Magnifier
import XMonad.Layout.NoBorders
import XMonad.Layout.PerWorkspace
import XMonad.Layout.Renamed
import XMonad.Layout.Spacing
import XMonad.Layout.Spiral
import XMonad.Layout.Tabbed
import XMonad.Layout.ThreeColumns
import qualified XMonad.StackSet as W
import XMonad.Util.EZConfig
import qualified XMonad.Util.Hacks as Hacks
import XMonad.Util.Loggers
import XMonad.Util.NamedScratchpad
import XMonad.Util.SpawnOnce
-- Main
main :: IO ()
main =
xmonad
. ewmhFullscreen
. ewmh
. dynamicEasySBs myStatusBars
. addRandrChangeHook (spawn myScreenHotplug)
$ myConfig
-- Variables
myModMask :: KeyMask
myModMask = mod4Mask
myTerminal :: String
myTerminal = "kitty"
myBrowser :: String
myBrowser = "zen-browser"
myNormBorderColor :: String
myNormBorderColor = "#1f2335"
myFocusBorderColor :: String
myFocusBorderColor = "#41a6b5"
myKeyboardLayoutIT :: String
myKeyboardLayoutIT = " -layout it"
myKeyboardLayoutUS :: String
myKeyboardLayoutUS = " -layout us"
myKeyboardLayoutSK :: String
myKeyboardLayoutSK = " -layout sk -variant qwerty"
myScreenshotDir :: String
myScreenshotDir = "/home/fuxino/Pictures/Screenshots/"
myScreenshotName :: String
myScreenshotName = "Screenshot-%Y-%m-%d-%H%M%S.png"
-- Sets up the outputs and the wallpaper, and leaves the tray's monitor in $tray
myScreenLayout :: String
myScreenLayout =
"if xrandr --query | grep -q '^HDMI-1-0 connected'; then"
++ " xrandr --output HDMI-1-0 --preferred --primary --output eDP-1 --right-of HDMI-1-0 --preferred; tray=1;"
++ " else xrandr --output eDP-1 --preferred --primary --output HDMI-1-0 --off; tray=0; fi"
++ "; ~/.fehbg"
-- Replaces any running trayer, so there's never more than one
myTrayer :: String
myTrayer =
"pkill -x trayer; while pgrep -x trayer > /dev/null; do sleep 0.1; done; \
\trayer -l --edge top --align right --SetDockType true \
\--SetPartialStrut true --expand true --widthtype request \
\--tint 0x232634 --height 18 --monitor $tray"
-- Safe to re-run (e.g. spawnOnce after this string changes and xmonad restarts)
myScreenSetup :: String
myScreenSetup =
"sleep 1; " ++ myScreenLayout ++ "; pgrep -x picom > /dev/null || picom -b -c; " ++ myTrayer
-- Run when a monitor is (dis)connected; restarts trayer so it moves to the right screen
myScreenHotplug :: String
myScreenHotplug = myScreenLayout ++ "; " ++ myTrayer
myScratchpads :: [NamedScratchpad]
myScratchpads =
[ NS "Riseup-vpn" "riseup-vpn" (className =? "RiseupVPN") (customFloating $ W.RationalRect 0.15 0.4 0.15 0.4),
NS "Terminal" spawnTerminal (className =? "scratch-terminal") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6),
NS "Music" spawnMusic (className =? "scratch-music") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6),
NS "Mail" spawnMail (className =? "scratch-mail") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6),
NS "Cal" spawnCal (className =? "scratch-cal") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6),
NS "Btop" spawnBtop (className =? "scratch-btop") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6)
]
where
spawnTerminal = myTerminal ++ " --class scratch-terminal -T kitty-float"
spawnMusic = myTerminal ++ " --class scratch-music -T ncmpcpp ncmpcpp"
spawnMail = myTerminal ++ " --class scratch-mail -T mutt bash -c 'pushd ~/Downloads > /dev/null; mutt; popd > /dev/null'"
spawnCal = myTerminal ++ " --class scratch-cal -T calcurse calcurse"
spawnBtop = myTerminal ++ " --class scratch-btop -T btop btop"
-- Config
myConfig =
def
{ modMask = myModMask,
terminal = myTerminal,
workspaces = myWorkspaces,
focusedBorderColor = myFocusBorderColor,
normalBorderColor = myNormBorderColor,
layoutHook = myLayoutHook,
startupHook = myStartupHook,
manageHook = myManageHook,
handleEventHook = myHandleEventHook
}
`additionalKeysP` [ ("M-b", sendMessage ToggleStruts),
("M-S-l", spawn "slock"),
("<Print>", unGrab *> spawn ("scrot " ++ myScreenshotDir ++ myScreenshotName)),
("S-<Print>", unGrab *> spawn ("scrot -s " ++ myScreenshotDir ++ myScreenshotName)),
("M-S-<Print>", unGrab *> spawn ("scrot -u " ++ myScreenshotDir ++ myScreenshotName)),
("M-d", spawn "dmenu_run"),
("M-p", spawn "passmenu -i"),
("M-f", sendMessage $ JumpToLayout "Tabbed"),
("M-i", spawn $ "setxkbmap" ++ myKeyboardLayoutIT),
("M-u", spawn $ "setxkbmap" ++ myKeyboardLayoutUS),
("M-s", spawn $ "setxkbmap" ++ myKeyboardLayoutSK),
("M-S-v", namedScratchpadAction myScratchpads "Riseup-vpn"),
("M-S-s", namedScratchpadAction myScratchpads "Terminal"),
("M-S-a", namedScratchpadAction myScratchpads "Cal"),
("M-o", namedScratchpadAction myScratchpads "Btop"),
("M-x", spawn "bluetoothctl connect F4:9D:8A:B9:8C:EE"),
("M-S-x", spawn "bluetoothctl disconnect F4:9D:8A:B9:8C:EE")
]
`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%"),
((0, xF86XK_AudioRaiseVolume), spawn "wpctl set-volume -l 1.0 @DEFAULT_AUDIO_SINK@ 10%+"),
((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 ..],
(f, mask) <- [(viewScreen def, 0), (sendToScreen def, shiftMask)]
]
-- Workspaces
myWorkspaces :: [String]
myWorkspaces = [wsTerm, wsWeb, wsGames, wsChat, wsMedia, wsDocs, wsVM] ++ map show [8, 9]
wsTerm, wsWeb, wsGames, wsChat, wsMedia, wsDocs, wsVM :: String
wsTerm = "1:\xf489 "
wsWeb = "2:\xe743 "
wsGames = "3:\xf1b6 "
wsChat = "4:\xf10b "
wsMedia = "5:\xead9 "
wsDocs = "6:\xeb69 "
wsVM = "7:\xea7a "
-- Layout
myLayoutHook = onWorkspace wsWeb myWebLayout $ onWorkspace wsGames myGamesLayout $ onWorkspace wsMedia myVideoLayout myDefaultLayout
where
myWebLayout = avoidStruts $ renamed [CutWordsLeft 1] $ spacingWithEdge 3 $ myTabbed ||| tiled ||| Mirror tiled ||| threeCol ||| Grid ||| spiral 0.856
myGamesLayout = avoidStruts $ smartBorders Full
myVideoLayout = avoidStruts $ smartBorders Full
myDefaultLayout = avoidStruts $ renamed [CutWordsLeft 1] $ spacingWithEdge 3 $ tiled ||| Mirror tiled ||| myTabbed ||| threeCol ||| Grid ||| spiral 0.856
threeCol =
renamed [Replace "ThreeCol"] $
magnifiercz' 1.3 $
ThreeColMid nmaster delta ratio
myTabbed =
renamed [Replace "Tabbed"] $
tabbed shrinkText myTabConfig
myTabConfig =
def
{ activeColor = "#24283b",
inactiveColor = "#737aa2"
}
tiled = Tall nmaster delta ratio
nmaster = 1
ratio = 1 / 2
delta = 3 / 100
-- Xmobar
-- One bar per connected screen. The laptop config leaves room for trayer, so it
-- goes on the laptop screen, which is screen 1 with HDMI and screen 0 without.
myStatusBars :: ScreenId -> X StatusBarConfig
myStatusBars (S i) = do
nScreens <- gets (length . W.screens . windowset)
let rc = if nScreens > 1 && i /= 1 then "xmobarrc_hdmi" else "xmobarrc_laptop"
pure $ statusBarProp ("xmobar -x " ++ show i ++ " ~/.config/xmobar/" ++ rc) (pure myXmobarPP)
myXmobarPP :: PP
myXmobarPP =
def
{ ppSep = pink " • ",
ppTitleSanitize = xmobarStrip,
ppCurrent = wrap " " "" . xmobarBorder "Top" "#ff79c6" 2,
ppHidden = cultured . wrap " " "",
ppHiddenNoWindows = outerspace . wrap " " "",
ppUrgent = sunsetorange . wrap (keylime "!") (keylime "!"),
ppOrder = \[ws, l, _, wins] -> [ws, l, wins],
ppExtras = [logTitles formatFocused formatUnfocused]
}
where
formatFocused = wrap (cultured "[") (cultured "]") . pink . ppWindow
formatUnfocused = wrap (outerspace "[") (outerspace "]") . cultured . ppWindow
ppWindow :: String -> String
ppWindow = xmobarRaw . (\w -> if null w then "untitled" else w) . shorten 20
outerspace, pink, sunsetorange, cultured, keylime :: String -> String
pink = xmobarColor "#ff79c6" ""
cultured = xmobarColor "#f8f8f2" ""
keylime = xmobarColor "#f1fa8c" ""
sunsetorange = xmobarColor "#ff5555" ""
outerspace = xmobarColor "#44475a" ""
-- Autostart
myStartupHook :: X ()
myStartupHook = do
spawnOnce myScreenSetup
spawnOnce "xsetroot -cursor_name left_ptr"
spawnOnce "xautolock -time 10 -locker slock -detectsleep"
spawnOnce "redshift-gtk"
spawnOnce "udiskie"
spawnOnce "nm-applet"
spawnOnce "arch-audit-gtk"
spawnOnce "calcurse --daemon"
spawnOnce "dunst"
spawnOnce "lxpolkit"
spawnOnce "numlockx"
spawnOnce "blueman-applet"
spawnOnce "mousemove"
spawnOnce "adb start-server"
spawnOnce "/home/fuxino/.local/bin/discover-overlay-wait"
-- Manage hook
myManageHook :: ManageHook
myManageHook =
composeAll
[ className =? "Gpodder" --> doShift wsMedia,
className =? "An Anime Game Launcher" --> doShift wsGames,
className =? "Qalculate-gtk" --> doFloat,
title =? "Genshin Impact" --> doShift wsGames,
className =? "Signal" --> doShift wsChat,
className =? "Spotify" --> doShift wsMedia,
className =? "Transmission-gtk" --> doShift wsWeb,
className =? "Viewnior" --> doFloat,
className =? "Vivaldi-stable" --> doShift wsWeb,
className =? "Xmessage" --> doFloat,
className =? "Xreader" --> doShift wsDocs,
className =? "calibre" --> doShift wsDocs,
className =? "discord" --> doShift wsChat,
className =? "explorer.exe" --> doShift wsGames,
className =? "feh" --> doFloat,
title =? "glxgears" --> doFloat,
className =? "library_manager" --> doShift wsDocs,
className =? "libreoffice-calc" --> doShift wsDocs,
className =? "libreoffice-writer" --> doShift wsDocs,
className =? "mpv" --> doFullFloat,
className =? "mpv" --> viewShift wsMedia,
className =? "freetube" --> doShift wsMedia,
className =? myTerminal --> doShift wsTerm,
className =? "scrcpy" --> doShift wsMedia,
className =? "steam" --> doShift wsGames,
className =? "steam_app_109600" --> doFloat,
className =? "steam_app_109600" --> doShift wsGames,
className =? "steam_app_289070" --> doFloat,
className =? "steam_app_289070" --> doShift wsGames,
className =? "VirtualBox Machine" --> doShift wsVM,
className =? "VirtualBox Machine" --> doFullFloat,
className =? "VirtualBox Manager" --> doShift wsVM,
className =? "zen" --> doShift wsWeb,
isDialog --> doFloat,
-- Windows that open already fullscreen; ewmhFullscreen only handles later requests
isFullscreen --> doFullFloat
]
<+> namedScratchpadManageHook myScratchpads
where viewShift = doF . liftM2 (.) W.greedyView W.shift
-- Handle event hook
myHandleEventHook :: Event -> X All
myHandleEventHook =
handleEventHook def
<> Hacks.trayerAboveXmobarEventHook
<> Hacks.trayerPaddingXmobarEventHook
<> Hacks.fixSteamFlicker