-- 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.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, 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"), ("", unGrab *> spawn ("scrot " ++ myScreenshotDir ++ myScreenshotName)), ("S-", unGrab *> spawn ("scrot -s " ++ myScreenshotDir ++ myScreenshotName)), ("M-S-", 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 "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