Make XMonad screen setup adapt to connected monitors

- Match scratchpads by WM_CLASS instead of title, so they no longer
  get shifted to workspace 1 or lose track of their window
- Spawn one xmobar per connected screen (dynamicEasySBs)
- Pick the xrandr layout and tray monitor based on whether HDMI is
  connected, and re-run it on monitor hotplug
- Don't let one failed startup step block the rest
- Make the startup command safe to re-run (no duplicate trayer/picom)
This commit is contained in:
Daniele Fucini
2026-10-08 15:52:58 +02:00
parent d1d4fc742e
commit c7275031ba
+49 -22
View File
@@ -3,6 +3,7 @@
import Data.Default
import Data.Monoid
import Graphics.X11.ExtraTypes.XF86
import Control.Monad (liftM2)
import XMonad
import XMonad.Actions.PhysicalScreens
import XMonad.Hooks.DynamicLog
@@ -11,6 +12,7 @@ import XMonad.Hooks.FloatConfigureReq
import XMonad.Hooks.InsertPosition
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
@@ -36,7 +38,8 @@ main =
xmonad
. ewmhFullscreen
. ewmh
. withEasySB (xmobar1 <> xmobar2) defToggleStrutsKey
. dynamicEasySBs myStatusBars
. addRandrChangeHook (spawn myScreenHotplug)
$ myConfig
-- Variables
@@ -68,29 +71,46 @@ 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 && xrandr --output HDMI-1-0 --preferred --primary --output eDP-1 --right-of HDMI-1-0 --preferred && ~/.fehbg"
++ " && picom -b -c"
++ " && trayer -l --edge top --align right --SetDockType true \
\--SetPartialStrut true --expand true --widthtype request \
\--tint 0x232634 --height 18 --monitor 1"
"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 (title =? "kitty-float") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6),
NS "Music" spawnMusic (title =? "ncmpcpp") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6),
NS "Mail" spawnMail (title =? "mutt") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6),
NS "Cal" spawnCal (title =? "calcurse") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6),
NS "Btop" spawnBtop (title =? "btop") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6)
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 ++ " -T kitty-float"
spawnMusic = myTerminal ++ " -T ncmpcpp ncmpcpp"
spawnMail = myTerminal ++ " -T mutt bash -c 'pushd ~/Downloads > /dev/null; mutt; popd > /dev/null'"
spawnCal = myTerminal ++ " -T calcurse calcurse"
spawnBtop = myTerminal ++ " -T btop btop"
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 =
@@ -105,7 +125,8 @@ myConfig =
manageHook = myManageHook,
handleEventHook = myHandleEventHook
}
`additionalKeysP` [ ("M-S-l", spawn "slock"),
`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)),
@@ -164,9 +185,13 @@ myLayoutHook = onWorkspace "2:\xe743 " myWebLayout $ onWorkspace "3:\xf1b6 " myG
delta = 3 / 100
-- Xmobar
xmobar1 = statusBarProp "xmobar -x 0 ~/.config/xmobar/xmobarrc_hdmi" (pure myXmobarPP)
xmobar2 = statusBarProp "xmobar -x 1 ~/.config/xmobar/xmobarrc_laptop" (pure myXmobarPP)
-- 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 =
@@ -211,6 +236,7 @@ myStartupHook = do
spawnOnce "blueman-applet"
spawnOnce "mousemove"
spawnOnce "adb start-server"
spawnOnce "/home/fuxino/.local/bin/discover-overlay-wait"
-- Manage hook
myManageHook :: ManageHook
@@ -236,8 +262,8 @@ myManageHook =
className =? "libreoffice-calc" --> doShift "6:\xeb69 ",
className =? "libreoffice-writer" --> doShift "6:\xeb69 ",
className =? "mpv" --> doFullFloat,
className =? "mpv" --> doShift "5:\xead9 ",
className =? "FreeTube" --> doShift "5:\xead9 ",
className =? "mpv" --> viewShift "5:\xead9 ",
className =? "freetube" --> doShift "5:\xead9 ",
className =? myTerminal --> doShift "1:\xf489 ",
className =? "scrcpy" --> doShift "5:\xead9 ",
className =? "steam" --> doShift "3:\xf1b6 ",
@@ -254,6 +280,7 @@ myManageHook =
isFullscreen --> doFullFloat
]
<+> namedScratchpadManageHook myScratchpads
where viewShift = doF . liftM2 (.) W.greedyView W.shift
-- Handle event hook
myHandleEventHook :: Event -> X All