Clicking a workspace switches to it; one visible on the other screen is swapped onto the focused screen. xmobar uses UnsafeXMonadLog so the click actions are kept.
303 lines
12 KiB
Haskell
303 lines
12 KiB
Haskell
-- Import
|
|
|
|
import Data.Monoid
|
|
import Graphics.X11.ExtraTypes.XF86
|
|
import Control.Monad (forM_, liftM2, when)
|
|
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.ClickableWorkspaces
|
|
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
|
|
. setEwmhSwitchDesktopHook W.greedyView
|
|
. 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, the wallpaper and trayer; shared with ~/.local/bin/movie
|
|
myScreenLayout :: String
|
|
myScreenLayout = "/home/fuxino/.local/bin/screen-layout"
|
|
|
|
-- 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"
|
|
|
|
-- Run when a monitor is (dis)connected; restarts trayer so it moves to the right screen
|
|
myScreenHotplug :: String
|
|
myScreenHotplug = myScreenLayout
|
|
|
|
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,
|
|
logHook = myLogHook
|
|
}
|
|
`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) (clickablePP 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 "systemctl --user start xautolock-inhibit"
|
|
spawnOnce "redshift-gtk"
|
|
spawnOnce "systemctl --user start udiskie"
|
|
spawnOnce "nm-applet"
|
|
spawnOnce "arch-audit-gtk"
|
|
spawnOnce "calcurse --daemon"
|
|
spawnOnce "dunst"
|
|
spawnOnce "lxpolkit"
|
|
spawnOnce "numlockx"
|
|
spawnOnce "blueman-applet"
|
|
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
|
|
|
|
-- Log hook
|
|
myLogHook :: X ()
|
|
myLogHook = raiseDiscoverOverlay
|
|
|
|
-- discover-overlay's windows are override-redirect, so xmonad doesn't manage them and
|
|
-- raising a floating window (e.g. a game) covers them; put them back on top after every change
|
|
raiseDiscoverOverlay :: X ()
|
|
raiseDiscoverOverlay = withDisplay $ \d -> do
|
|
root <- asks theRoot
|
|
(_, _, ws) <- io $ queryTree d root
|
|
forM_ ws $ \w -> do
|
|
cls <- io $ getClassHint d w
|
|
when (resClass cls == "Discover-overlay") $ io $ raiseWindow d w
|