Last active
October 17, 2023 22:57
-
-
Save jsjolund/94f6821b248ff79586ba to your computer and use it in GitHub Desktop.
Example of using xmonad inside xfce
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
------------------------------------------------------------------------------- | |
-- 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