Skip to content

Instantly share code, notes, and snippets.

@jsjolund
Last active October 17, 2023 22:57
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jsjolund/94f6821b248ff79586ba to your computer and use it in GitHub Desktop.
Save jsjolund/94f6821b248ff79586ba to your computer and use it in GitHub Desktop.
Example of using xmonad inside xfce
-------------------------------------------------------------------------------
-- Configuration for using Xmonad inside Xfce, KDE and standalone.
--
-- Xfce: It is recommended to disable/remove xfwm4 and xfdesktop.
-- KDE: Plasma works with xmonad, except
-- 1. Mouse cursor cannot focus on empty monitors.
-- 2. Panel start-menu search field cannot receive input.
-------------------------------------------------------------------------------
import qualified Data.Map as M
import Data.Maybe
import Graphics.X11.ExtraTypes.XF86
import System.Directory
import System.IO
import System.Posix.Env
import XMonad hiding ((|||))
import XMonad.Actions.CopyWindow
import XMonad.Actions.CycleWS
import XMonad.Actions.GroupNavigation
import XMonad.Config.Desktop
import XMonad.Config.Gnome
import XMonad.Config.Kde
import XMonad.Config.Xfce
import XMonad.Hooks.DynamicLog
import XMonad.Hooks.EwmhDesktops
import XMonad.Hooks.FloatNext
import XMonad.Hooks.InsertPosition
import XMonad.Hooks.ManageDocks
import qualified XMonad.Hooks.ManageDocks as ManageDocks
import XMonad.Hooks.ManageHelpers
import qualified XMonad.Hooks.ManageHelpers as ManageHelpers
import XMonad.Hooks.SetWMName
import XMonad.Layout.ComboP
import XMonad.Layout.LayoutCombinators
import XMonad.Layout.Named
import XMonad.Layout.NoBorders
import XMonad.Layout.Reflect
import XMonad.Layout.ResizableTile
import qualified XMonad.StackSet as W
import XMonad.Util.EZConfig
import XMonad.Util.Run
myStartupHook = setWMName "LG3D"
myModMask = mod4Mask
myBorderWidth = 1
myWorkspaces = map show [1 .. 9 :: Int]
myTerminal = "urxvt"
-- Keyboard --
myKeys :: [((KeyMask, KeySym), X ())]
myKeys =
-- launching and killing programs
[ ((myModMask, xK_Return), spawn myTerminal),
((myModMask .|. shiftMask, xK_c), spawn "xkill"),
((myModMask, xK_c), kill),
-- layouts
((myModMask, xK_space), sendMessage NextLayout),
((myModMask, xK_b), sendMessage ToggleStruts),
-- floating layer stuff
((myModMask, xK_t), withFocused $ windows . W.sink),
-- focus
((myModMask, xK_j), windows W.focusDown),
((myModMask, xK_k), windows W.focusUp),
((myModMask, xK_m), windows W.focusMaster),
((myModMask, xK_Right), nextWS),
((myModMask, xK_Left), prevWS),
((myModMask .|. shiftMask, xK_Right), shiftToNext >> nextWS),
((myModMask .|. shiftMask, xK_Left), shiftToPrev >> prevWS),
-- change focus to different windows across workspaces
((myModMask, xK_Tab), nextMatch Forward isOnAnyVisibleWS),
((myModMask .|. shiftMask, xK_Tab), nextMatch Backward isOnAnyVisibleWS),
-- swapping
((myModMask .|. shiftMask, xK_Return), windows W.swapMaster),
((myModMask .|. shiftMask, xK_j), windows W.swapDown),
((myModMask .|. shiftMask, xK_k), windows W.swapUp),
((myModMask, xK_s), sendMessage SwapWindow),
-- increase or decrease number of windows in the master area
((myModMask, xK_comma), sendMessage (IncMasterN 1)),
((myModMask, xK_period), sendMessage (IncMasterN (-1))),
-- resizing
((myModMask, xK_h), sendMessage Shrink),
((myModMask, xK_l), sendMessage Expand),
((myModMask .|. shiftMask, xK_h), sendMessage MirrorShrink),
((myModMask .|. shiftMask, xK_l), sendMessage MirrorExpand),
-- ungrab mouse cursor from applications which can grab it (games)
((myModMask, xK_i), spawn "xdotool key XF86Ungrab"),
-- qjoypad PS3 controller, uses xev keysym
((0, 0x1008ff42), prevWS),
((0, 0x1008ff41), nextWS),
((0, 0x1008ff65), kill),
((0, 0x1008ff8a), spawn "/home/user/.local/bin/tv_toggle")
]
++
-- mod-{q,w,e,r} %! Switch to physical/Xinerama screens 1, 2, 3, 4
-- mod-shift-{q,w,e,r} %! Move client to screen 1, 2, 3, 4
[ ((m .|. myModMask, key), screenWorkspace sc >>= flip whenJust (windows . f))
| (key, sc) <- zip [xK_w, xK_r, xK_e, xK_q] [0 ..],
(f, m) <- [(W.view, 0), (W.shift, shiftMask)]
]
-- Additional key bindings
myKeysStandalone =
[ ((mod1Mask .|. shiftMask, xK_q), spawn "xscreensaver-command -lock"),
((0, xF86XK_MonBrightnessUp), spawn "brightnessctl set +5%"),
((0, xF86XK_MonBrightnessDown), spawn "brightnessctl set 5%-"),
((0, xF86XK_AudioRaiseVolume), spawn "amixer -D pipewire sset Master 10%+"),
((0, xF86XK_AudioLowerVolume), spawn "amixer -D pipewire sset Master 10%-"),
((0, xF86XK_AudioMute), spawn "amixer -D pipewire sset Master toggle"),
((0, xF86XK_AudioMicMute), spawn "amixer -D pipewire sset Capture toggle")
]
myKeysXfce =
[ ((myModMask, xK_o), spawn "xfrun4"),
((myModMask, xK_f), spawn "pcmanfm"),
((controlMask .|. shiftMask, xK_q), spawn "xfce4-session-logout"),
((mod1Mask .|. shiftMask, xK_q), spawn "xfce4-screensaver-command -a"),
((0, 0x1008ff5d), spawn "xfce4-popup-applicationsmenu -p")
]
myKeysKde =
[ ((myModMask, xK_Return), spawn "konsole"),
((myModMask, xK_o), spawn "krunner"),
((myModMask, xK_f), spawn "dolphin"),
((controlMask .|. shiftMask, xK_q), spawn "qdbus org.kde.ksmserver /KSMServer logout 1 3 3"),
((mod1Mask .|. shiftMask, xK_q), spawn "qdbus org.kde.ksmserver /ScreenSaver org.freedesktop.ScreenSaver.Lock")
]
-- Layouts --
myLayoutHook' = tile ||| rtile ||| full ||| mtile
where
rt = ResizableTall 1 (2 / 100) (1 / 2) []
-- normal vertical tile
tile = named "[]=" $ smartBorders rt
-- normal vertical tile, master opposite side
rtile = named "=[]" $ reflectHoriz $ smartBorders rt
-- normal horizontal tile
mtile = named "M[]=" $ smartBorders $ Mirror rt
-- fullscreen without tabs
full = named "[]" $ noBorders Full
myLayoutHook = smartBorders (avoidStruts myLayoutHook')
-- xprop fields used in manage hook:
-- resource (also known as appName) is the first element in WM_CLASS(STRING)
-- className is the second element in WM_CLASS(STRING) title is WM_NAME(STRING)
-- https://hackage.haskell.org/package/xmonad-0.15/docs/XMonad-ManageHook.html
checkSkipTaskbar :: Query Bool
checkSkipTaskbar = ManageHelpers.isInProperty "_NET_WM_STATE" "_NET_WM_STATE_SKIP_TASKBAR"
-- Avoid the master window, but otherwise manage new windows normally
avoidMaster :: W.StackSet i l a s sd -> W.StackSet i l a s sd
avoidMaster = W.modify' $ \c -> case c of
W.Stack t [] (r : rs) -> W.Stack t [r] rs
_ -> c
myManageHook' :: ManageHook
myManageHook' =
composeAll
. concat
$ [ [ManageHelpers.transience'], -- move transient windows like dialogs/alerts on top of their parents
[className =? "krunner" --> doIgnore >> doFloat],
[className =? "Xfrun4" --> doRectFloat (W.RationalRect 0.4 0.45 0.25 0.07)],
[className =? "Xfce4-panel" --> doSink],
[(className =? "plasmashell" <&&> checkSkipTaskbar) --> doIgnore <+> hasBorder False], -- Ignore kde widgets
[className =? c --> doFloat | c <- myClassFloats],
[className =? c --> ManageHelpers.doFullFloat | c <- myFullFloats],
[className =? c --> doIgnore <+> hasBorder False | c <- myIgnores],
[className =? c --> ManageHelpers.doCenterFloat | c <- myCenterFloats],
[className =? c --> doShift (myWorkspaces !! ws) | (c, ws) <- myShifts],
[title =? t --> doFloat | t <- myTitleFloats],
[title =? t --> ManageHelpers.doCenterFloat | t <- myTitleCenterFloats],
[role =? r --> ManageHelpers.doCenterFloat | r <- myRoleCenterFloats],
[ManageHelpers.isFullscreen --> ManageHelpers.doFullFloat],
[ManageHelpers.isDialog --> doFloat],
[checkModal --> ManageHelpers.doCenterFloat]
]
where
myIgnores = ["Xfce4-notifyd"]
myCenterFloats =
[ "zenity",
"mpv"
]
myTitleCenterFloats =
[ "File Operation Progress",
"Downloads",
"Save as...",
"Ulauncher Preferences"
]
myClassFloats =
[ "confirm",
"file_progress",
"dialog",
"download",
"error",
"Gimp",
"notification",
"pinentry-gtk-2",
"splash",
"toolbar",
"Peek",
"yakuake",
"gpclient"
]
myRoleCenterFloats = ["GtkFileChooserDialog"]
myTitleFloats = ["Media viewer", "Yad"]
myFullFloats = []
-- workspace numbers start at 0
myShifts =
[ ("VirtualBox Manager", 8)
]
-- Log hook for xmobar
myLogHook xmproc =
dynamicLogWithPP
xmobarPP
{ ppOutput = hPutStrLn xmproc,
ppTitle = xmobarColor "green" "" . shorten 50
}
-- Check if window is modal
checkModal :: Query Bool
checkModal = ManageHelpers.isInProperty "_NET_WM_STATE" "_NET_WM_STATE_MODAL"
myManageHook :: ManageHook
myManageHook =
composeAll
[ ManageDocks.manageDocks,
-- open windows at the end if they are not floating
fmap not (willFloat <||> checkModal) --> insertPosition Below Newer,
floatNextHook,
myManageHook'
<+> (fmap not isDialog --> doF avoidMaster)
]
-- Match against @WM_NAME@.
name :: Query String
name = stringProperty "WM_CLASS"
-- Match against @WM_WINDOW_ROLE@.
role :: Query String
role = stringProperty "WM_WINDOW_ROLE"
-- Match a string against any one of a window's class, title, name or role.
matchAny :: String -> Query Bool
matchAny x = foldr ((<||>) . (=? x)) (return False) [className, title, name, role]
-- Desktops --
desktop "gnome" = gnomeConfig
desktop "xmonad-gnome" = gnomeConfig
desktop "kde" = kdeConfig
desktop "kde-plasma" = kdeConfig
desktop "plasma" = kdeConfig
desktop "xfce" = xfceConfig
desktop _ = desktopConfig
-- Config --
main :: IO ()
main = do
session <- getEnv "DESKTOP_SESSION"
let deskKeys
| session == Just "plasma" = myKeysKde
| session == Just "xfce" = myKeysXfce
| session == Just "xmonad" = myKeysStandalone
| otherwise = myKeys
let defDesktopConfig = maybe desktopConfig desktop session
myDesktopConfig =
defDesktopConfig
{ modMask = myModMask,
borderWidth = myBorderWidth,
startupHook = myStartupHook,
workspaces = myWorkspaces,
layoutHook = myLayoutHook,
manageHook =
myManageHook <+> manageDocks
<+> manageHook defDesktopConfig
}
`additionalKeys` myKeys
-- When running standalone (no DE), try to spawn xmobar
xmobarInstalled <- doesFileExist "/usr/bin/xmobar"
if session == Just "xmonad" && xmobarInstalled
then do
mproc <- spawnPipe "/usr/bin/xmobar ~/.xmonad/xmobar.hs"
xmonad $
myDesktopConfig
{ logHook = myLogHook mproc
}
`additionalKeys` deskKeys
else do xmonad $ ewmhFullscreen . ewmh $ myDesktopConfig `additionalKeys` deskKeys
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment