13 KiB
Trying out XMonad in Home Manager
I haven't been particularly pleased with i3wm's container strategy and basically just miss some of the simplicity of operating with XMonad. I don't miss all the complexity of it though, but maybe I'm in a better position to write roam:Haskell now… Easy enough to fuck around and find out.
Minimal Haskell support in Emacs
Of course this will be included in Arroyo Emacs to make developing this configuration viable.
(use-package haskell-mode)
(provide 'cce/haskell)
xomand.hs
configuration file
Because KDE is a Base for my Emacs Desktop, I don't fuck with things like xmobar
or R&R support in this layer, let my desktop environment deal with all of that instead of me. It has built in integration to configure it to manage the Plasma windows and whatnot reasonably.
import XMonad
import XMonad.Config.Kde
<<imports>>
myMod = mod4Mask
main = xmonad myConfig
myConfig = kde4Config
{ modMask = myMod
, layoutHook = desktopLayoutModifiers $ myLayoutHook
, manageHook = manageHook kde4Config <+> myManageHook
, workspaces = myWorkspaces
<<kde4ConfigDecl>>
} `additionalKeys`
[ ((myMod, xK_Tab), toggleRecentWS) -- (ref:recentWS)
<<additionalKeys>>
]
<<myLayoutHook>>
<<myManageHook>>
<<myWorkspaces>>
<<helperFns>>
de-tangling this a bit is probably a fool's errand, but here we go…
XMonad's docs on configuring as well as the quick start guide will make a lot more sense of the hooks but broadly:
The basic kde4Config
"but rrix don't you run Plasma 5?" yes, yes I do, but unlike in the 3->4 transition the KDE developers were able to maintain a large amount of backwards compatibility. I think it would behoove the XMonad developers to recognize this and make, say, a kde3Config
for the 30 Trinity users still left but Sir, This is a Wendy's. So all this is broadly defined in XMonad.Config.Kde and XMonad.Config.Desktop except it doesn't talk about Plasma 5 at all.
Make sure if you do make your own kde4Config
that it pulls the default manageHook
and invokes desktopLayoutModifiers
out of the default configuration.
The only really notable things other than wiring up the hooks defined below are some simple appearance changes:
, focusedBorderColor = "#ebbe7b"
, normalBorderColor = "#707231"
, borderWidth = 4
EZConfig
makes it eazy to define new keybindings
There's not a lot of "there" there additionalKeys is used to add or override keybindings.
import XMonad.Util.EZConfig (additionalKeys)
import XMonad.Actions.CycleRecentWS (toggleRecentWS)
That's right, we're nesting noweb
functions!
I have a bunch of little helpers in i3wm and A World Without EXWM which I should collate and expand on for example to add my org-fc function… but anyways, S-a
opens my agenda, S-f
opens a new Emacs frame, S-g
opens my Journal, S-x
opens a new frame with the M-x
execute-interactive-command
open on it. S-z
opens my todo list. S-c
opens my Flash Cards.
, ((myMod, xK_a), spawn "org-agenda")
, ((myMod, xK_f), spawn "emacsclient -c -n")
, ((myMod, xK_g), spawn "org-journal")
, ((myMod, xK_x), spawn "meta-x")
, ((myMod, xK_z), spawn "org-todos")
, ((myMod, xK_c), spawn "emacsclient -c -e '(srs)' -n")
, ((myMod, xK_p), spawn "bash -c 'pkill krunner; krunner'")
There is also /rrix/complete-computing-environment/src/branch/main/(recentWS) above which toggles between the … most recent workspace … on S-Tab
. Useful for jumping between work terminals spread on the high-numbered workspaces when I am working on a high-context activity on my laptop display only. I provide it up there so that the first element of that list doesn't start with a comma, basically. All the other parts which go in to additionalKeys
should start with a commas as though they're (rightfully) being spliced in to a list.
myWorkspaces
culls down my workspace list a bit
I don't need nine workspaces. If I am working on that many different things it's a sign I should close stuff out. myWorkspaces
is included in the kde4Config
declaration above. This pattern is lifted from an example xmonad configuration.
myWorkspaces = ["front", "im", "music", "misc1", "misc2", "fullscreen"]
myLayoutHook
captures Layout Customization
myLayoutHook
customizes each of the layouts you can swap between (by default bound to S-SPC
)
- "main" window with everything tiled to the right
- "main" window with everything tiled below
- "main" window as full screen
- "three column" layout with the main window in the middle
- my IM windows are set to "three column" by default
- my fullscreen workspace is set to
fullscreen
by default.
myLayoutHook = imw $ fullscr $ tiled ||| Mirror tiled ||| full ||| threeCol
where
tiled = BW.boringWindows $ smartSpacingWithEdge 4 $ Tall nmaster delta ratio
full = BW.boringWindows $ noBorders $ Full
threeCol = BW.boringWindows $ smartSpacingWithEdge 4 $ ThreeColMid nmaster delta ratio
imw = onWorkspace "im" $ threeCol ||| tiled ||| Mirror tiled ||| full
fullscr = onWorkspace "fullscreen" full
nmaster = 1 -- Default number of windows in the master pane
ratio = 2/3 -- Default proportion of screen occupied by master pane
delta = 3/100 -- Percent of screen to increment by when resizing panes
This code block I think was ultimately what I left i3wm for. The "do it yourself" container design made it a real pain in the ass especially when swapping between my 3:2 laptop display and 21:9 widescreen desktop. Now it's just a Super-SPACE
away.
I modify my layouts with two modules:
- XMonad.Layout.BoringWindows lets a user mark a window as "boring". (lol) that is, it'll still manage, tile, apply rules to the windows, but if you cycle between them with the
focusUp
andfocusDown
commands that are provided in the module, the "boring" windows will be skipped. Great for something you want to keep an eye on like a terminal running a log tail while you're working. - XMonad.Layout.Spacing provides
smartSpacingWithEdge
which pushes my windows in a few pixels so that I can differentiate them easier, and so that I can see some of my beautiful art of rally desktop backgrounds 😉
The Boring Window commands are spliced in to /rrix/complete-computing-environment/src/branch/main/additionalKeys above:
, ((myMod, xK_j), BW.focusDown)
, ((myMod, xK_k), BW.focusUp)
, ((myMod, xK_m), BW.focusMaster)
, ((myMod, xK_s), BW.markBoringEverywhere)
, ((myMod .|. shiftMask, xK_s), BW.clearBoring)
This relies on these libraries:
import XMonad.Layout.ThreeColumns
import qualified XMonad.Layout.BoringWindows as BW
import XMonad.Layout.Spacing
import XMonad.Layout.NoBorders
import XMonad.Layout.PerWorkspace
myManageHook
provides rules that apply to windows when they are opened
This does a fair bit at once, and is based on some examples taken out of the xmonad docs. Seriously go read them and do your best to make sense of all these new and interesting operators. I'm sorry about all the Haskell and all the line-noise operators, I really am. I don't understand them either, they're mostly lifted from folks who took too many math courses at Uni.
myFloats = ["pinentry", "ksmserver-logout-greeter", "krunner"]
myTitleFloats = []
prodApps = ["firefox", "emacs"]
commApps = ["discord", "Signal", "Element", "telegram-desktop"]
mediaApps = ["cantata"]
myManageHook = composeAll . concat $
[ [ className =? c --> doFloat | c <- myFloats]
, [ title =? t --> doFloat | t <- myTitleFloats]
, [ className =? c --> doF (W.shift "1") | c <- prodApps]
, [ className =? c --> doF (W.shift "2") | c <- commApps]
, [ className =? c --> doF (W.shift "3") | c <- mediaApps]
, [ className =? "plasmashell" <&&> checkSkipTaskbar --> doIgnore] -- (ref:checkSkips)
, [ className =? "plasmashell" <&&> checkIsDesktop --> doIgnore]
]
myFloats
andmyTitleFloats
define window classes and titles which will be floated above the tiling layout. So things like my pass master password dialog (provided byGPG
pinentry), the KDE logout screen… this is not so confusing.prodApps
,commApps
, andmediaApps
are classes of windows which will be stuffed in to workspaces for productivity, communication, and media. workspaces 4-9 are basically overflow areas and places to stuff my "do-nothing" Firefox window.- the two rules in /rrix/complete-computing-environment/src/branch/main/(checkSkips) are used to signal to
XMonad
to not manage the Plasma desktop elements. These should be included in the defaultkde4Config
IMO but alas it seems like most of the folks who runXMonad
swap out forXMobar
…
import qualified XMonad.StackSet as W
import XMonad.Hooks.ManageHelpers (isInProperty)
These are the /rrix/complete-computing-environment/src/branch/main/(checkSkips) helpers..:
checkSkipTaskbar :: Query Bool
checkSkipTaskbar = isInProperty "_NET_WM_STATE" "_NET_WM_STATE_SKIP_TASKBAR"
checkIsDesktop :: Query Bool
checkIsDesktop = isInProperty "_NET_WM_WINDOW_TYPE" "_NET_WM_WINDOW_TYPE_DESKTOP"
home-manager configuration
{ pkgs, config, ... }:
rec {
home.sessionVariables = {
KDEWM="${pkgs.xmonad-with-packages}/bin/xmonad";
};
xsession.windowManager.xmonad = {
enable = true;
enableContribAndExtras = true;
config = ../files/xmonad.hs;
};
# <<plasma-xmonad>>
}
XMonad Auto Start on KDE >= 5.25
After KDE 5.25, Plasma now uses roam:SystemD to start up the desktop. This means that KDEWM
is ignored roam:because reasons. Well. Here's a work around. "just write a user unit and do a bunch of BS"
systemd.user.services.plasma-xmonad = {
Install.WantedBy = ["plasma-plasmashell.service"];
Unit.Description = "Start XMonad instead of KWin";
Unit.Before = "plasma-plasmashell.service";
Service.ExecStart = "${config.home.homeDirectory}/.xmonad/xmonad-${pkgs.stdenv.hostPlatform.system}";
Service.Slice="session.slice";
Service.Restart="on-failure";
};
home.activation.plasma-xmonad = ''
# systemctl --user mask plasma-kwin_x11.service
# systemctl --user daemon-reload
# back to kwin ...
systemctl --user unmask plasma-kwin_x11.service
systemctl --user disable plasma-xmonad.service
'';
Thinking about this Thread from roam:Geoffrey Litt….
Along these lines, one of my favorite findings in malleable software research:
Wendy Mackay found in 1991 that a very common reason people customized their Unix setups was to keep things the way they used to be
Kinda ironic, right?