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.Default
import Data.Monoid import Data.Monoid
import Graphics.X11.ExtraTypes.XF86 import Graphics.X11.ExtraTypes.XF86
import Control.Monad (liftM2)
import XMonad import XMonad
import XMonad.Actions.PhysicalScreens import XMonad.Actions.PhysicalScreens
import XMonad.Hooks.DynamicLog import XMonad.Hooks.DynamicLog
@@ -11,6 +12,7 @@ import XMonad.Hooks.FloatConfigureReq
import XMonad.Hooks.InsertPosition import XMonad.Hooks.InsertPosition
import XMonad.Hooks.ManageDocks import XMonad.Hooks.ManageDocks
import XMonad.Hooks.ManageHelpers import XMonad.Hooks.ManageHelpers
import XMonad.Hooks.Rescreen
import XMonad.Hooks.StatusBar import XMonad.Hooks.StatusBar
import XMonad.Hooks.StatusBar.PP import XMonad.Hooks.StatusBar.PP
import XMonad.Layout.Grid import XMonad.Layout.Grid
@@ -36,7 +38,8 @@ main =
xmonad xmonad
. ewmhFullscreen . ewmhFullscreen
. ewmh . ewmh
. withEasySB (xmobar1 <> xmobar2) defToggleStrutsKey . dynamicEasySBs myStatusBars
. addRandrChangeHook (spawn myScreenHotplug)
$ myConfig $ myConfig
-- Variables -- Variables
@@ -68,29 +71,46 @@ myScreenshotDir = "/home/fuxino/Pictures/Screenshots/"
myScreenshotName :: String myScreenshotName :: String
myScreenshotName = "Screenshot-%Y-%m-%d-%H%M%S.png" 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 :: String
myScreenSetup = myScreenSetup =
"sleep 1 && xrandr --output HDMI-1-0 --preferred --primary --output eDP-1 --right-of HDMI-1-0 --preferred && ~/.fehbg" "sleep 1; " ++ myScreenLayout ++ "; pgrep -x picom > /dev/null || picom -b -c; " ++ myTrayer
++ " && picom -b -c"
++ " && trayer -l --edge top --align right --SetDockType true \ -- Run when a monitor is (dis)connected; restarts trayer so it moves to the right screen
\--SetPartialStrut true --expand true --widthtype request \ myScreenHotplug :: String
\--tint 0x232634 --height 18 --monitor 1" myScreenHotplug = myScreenLayout ++ "; " ++ myTrayer
myScratchpads :: [NamedScratchpad] myScratchpads :: [NamedScratchpad]
myScratchpads = myScratchpads =
[ NS "Riseup-vpn" "riseup-vpn" (className =? "RiseupVPN") (customFloating $ W.RationalRect 0.15 0.4 0.15 0.4), [ 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 "Terminal" spawnTerminal (className =? "scratch-terminal") (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 "Music" spawnMusic (className =? "scratch-music") (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 "Mail" spawnMail (className =? "scratch-mail") (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 "Cal" spawnCal (className =? "scratch-cal") (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 "Btop" spawnBtop (className =? "scratch-btop") (customFloating $ W.RationalRect 0.2 0.2 0.6 0.6)
] ]
where where
spawnTerminal = myTerminal ++ " -T kitty-float" spawnTerminal = myTerminal ++ " --class scratch-terminal -T kitty-float"
spawnMusic = myTerminal ++ " -T ncmpcpp ncmpcpp" spawnMusic = myTerminal ++ " --class scratch-music -T ncmpcpp ncmpcpp"
spawnMail = myTerminal ++ " -T mutt bash -c 'pushd ~/Downloads > /dev/null; mutt; popd > /dev/null'" spawnMail = myTerminal ++ " --class scratch-mail -T mutt bash -c 'pushd ~/Downloads > /dev/null; mutt; popd > /dev/null'"
spawnCal = myTerminal ++ " -T calcurse calcurse" spawnCal = myTerminal ++ " --class scratch-cal -T calcurse calcurse"
spawnBtop = myTerminal ++ " -T btop btop" spawnBtop = myTerminal ++ " --class scratch-btop -T btop btop"
-- Config -- Config
myConfig = myConfig =
@@ -105,7 +125,8 @@ myConfig =
manageHook = myManageHook, manageHook = myManageHook,
handleEventHook = myHandleEventHook handleEventHook = myHandleEventHook
} }
`additionalKeysP` [ ("M-S-l", spawn "slock"), `additionalKeysP` [ ("M-b", sendMessage ToggleStruts),
("M-S-l", spawn "slock"),
("<Print>", unGrab *> spawn ("scrot " ++ myScreenshotDir ++ myScreenshotName)), ("<Print>", unGrab *> spawn ("scrot " ++ myScreenshotDir ++ myScreenshotName)),
("S-<Print>", unGrab *> spawn ("scrot -s " ++ myScreenshotDir ++ myScreenshotName)), ("S-<Print>", unGrab *> spawn ("scrot -s " ++ myScreenshotDir ++ myScreenshotName)),
("M-S-<Print>", unGrab *> spawn ("scrot -u " ++ 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 delta = 3 / 100
-- Xmobar -- Xmobar
xmobar1 = statusBarProp "xmobar -x 0 ~/.config/xmobar/xmobarrc_hdmi" (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.
xmobar2 = statusBarProp "xmobar -x 1 ~/.config/xmobar/xmobarrc_laptop" (pure myXmobarPP) 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 :: PP
myXmobarPP = myXmobarPP =
@@ -211,6 +236,7 @@ myStartupHook = do
spawnOnce "blueman-applet" spawnOnce "blueman-applet"
spawnOnce "mousemove" spawnOnce "mousemove"
spawnOnce "adb start-server" spawnOnce "adb start-server"
spawnOnce "/home/fuxino/.local/bin/discover-overlay-wait"
-- Manage hook -- Manage hook
myManageHook :: ManageHook myManageHook :: ManageHook
@@ -236,8 +262,8 @@ myManageHook =
className =? "libreoffice-calc" --> doShift "6:\xeb69 ", className =? "libreoffice-calc" --> doShift "6:\xeb69 ",
className =? "libreoffice-writer" --> doShift "6:\xeb69 ", className =? "libreoffice-writer" --> doShift "6:\xeb69 ",
className =? "mpv" --> doFullFloat, className =? "mpv" --> doFullFloat,
className =? "mpv" --> doShift "5:\xead9 ", className =? "mpv" --> viewShift "5:\xead9 ",
className =? "FreeTube" --> doShift "5:\xead9 ", className =? "freetube" --> doShift "5:\xead9 ",
className =? myTerminal --> doShift "1:\xf489 ", className =? myTerminal --> doShift "1:\xf489 ",
className =? "scrcpy" --> doShift "5:\xead9 ", className =? "scrcpy" --> doShift "5:\xead9 ",
className =? "steam" --> doShift "3:\xf1b6 ", className =? "steam" --> doShift "3:\xf1b6 ",
@@ -254,6 +280,7 @@ myManageHook =
isFullscreen --> doFullFloat isFullscreen --> doFullFloat
] ]
<+> namedScratchpadManageHook myScratchpads <+> namedScratchpadManageHook myScratchpads
where viewShift = doF . liftM2 (.) W.greedyView W.shift
-- Handle event hook -- Handle event hook
myHandleEventHook :: Event -> X All myHandleEventHook :: Event -> X All