From c7275031ba485b978759a6d42a21ced6bdfad14f Mon Sep 17 00:00:00 2001 From: Daniele Fucini Date: Thu, 8 Oct 2026 15:52:58 +0200 Subject: [PATCH] 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) --- .config/xmonad/xmonad.hs | 71 +++++++++++++++++++++++++++------------- 1 file changed, 49 insertions(+), 22 deletions(-) diff --git a/.config/xmonad/xmonad.hs b/.config/xmonad/xmonad.hs index 6676cd6..2ec8c65 100644 --- a/.config/xmonad/xmonad.hs +++ b/.config/xmonad/xmonad.hs @@ -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"), ("", unGrab *> spawn ("scrot " ++ myScreenshotDir ++ myScreenshotName)), ("S-", unGrab *> spawn ("scrot -s " ++ myScreenshotDir ++ myScreenshotName)), ("M-S-", 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