feat: implement modal keybindings for layout and master pane control

Add XMonad.Hooks.Modal to separate layout switching and master pane
manipulation into distinct modal namespaces. This resolves keybinding
conflicts and creates a more organized interaction model.

Changes:
- Layout mode (M-a l): one-shot layout selection (d/m/f)
- Master mode (M-a m): persistent pane adjustments with arrow keys
- Restore ppExtras with logMode to display active mode in xmobar
- Remove conflicting Home/End/PageUp/PageDown bindings from master pane
- Simplify navigation by moving screen focus back to Home/End
This commit is contained in:
♥ Minnie ♥ 2025-10-03 17:30:29 +08:00
parent 9ce0ee6dcc
commit 7568c2aa01
Signed by: jasmine
GPG key ID: 8563E358D4E8040E
2 changed files with 53 additions and 28 deletions

View file

@ -90,12 +90,18 @@ main = xmonad
**Key architectural components:**
- **Navigation2D**: Directional window and screen navigation using arrow keys and Navigation2D module
- **Custom keybindings**: Leader-key approach (`M-a` prefix) for spawning applications and layout switching
- **Modal keybindings**: Uses `XMonad.Hooks.Modal` for organizing keybindings into modes (inspired by i3wm)
- Layout mode (`M-a l`): Unified mode for both layout operations
- `d/m/f` - Jump to layout (exits immediately)
- Arrow keys - Adjust master/slave split (stays in mode, `<Escape>` to exit)
- Spawn mode (`M-a s`): Launch infrequent applications (exits immediately)
- `t` - Thunar file manager
- `s` - Screenshot tool
- **Leader-key approach**: `M-a` prefix for spawning applications and entering modes
- **Window manipulation**: Uses `XMonad.Actions.RotSlaves` for window rotation (mimics wezterm behavior)
- **Layout system**: Three layouts - dynamic tiling, maximised, and fullscreen
- **StatusBar integration**: Communicates with xmobar via `_XMONAD_LOG_1` X property
- **Mnemonic bindings**: Control (M-C) for layout manipulation, Shift (M-S) for window operations
- **StatusBar integration**: Communicates with xmobar via `_XMONAD_LOG_1` X property, displays active mode via `logMode`
- **Multimedia keys**: Uses `Graphics.X11.ExtraTypes.XF86` for XF86 multimedia key support (audio controls)
### XMobar Configuration (`src/xmobar.hs`)

View file

@ -6,12 +6,14 @@ import XMonad.Actions.RotSlaves
import XMonad.Hooks.DynamicLog
import XMonad.Hooks.EwmhDesktops
import XMonad.Hooks.ManageDocks
import XMonad.Hooks.Modal
import XMonad.Hooks.StatusBar
import XMonad.Layout.NoBorders
import XMonad.Layout.Renamed
import XMonad.Layout.Spacing
import XMonad.StackSet qualified as W
import XMonad.Util.EZConfig
import XMonad.Util.Font
import XMonad.Util.Loggers
import Graphics.X11.ExtraTypes.XF86
@ -30,6 +32,7 @@ main = xmonad
. ewmhFullscreen
. ewmh
. withSB myXmobar
. modal [layoutMode, spawnMode]
$ myConfig
myConfig = def
@ -65,23 +68,18 @@ myScrot = "scrot -s '%Y%m%d_%H%M%S.png' -e 'mv $f ~/Pictures/scrots
myKeymap =
--
-- LeaderKey
-- Core operations
--
-- spawning programs
[ ("M-a n", spawn myTerminal )
, ("M-a e", spawn myLauncher )
, ("M-a t", spawn myFileManager)
, ("M-a s", spawn myScrot )
-- toggling layouts
, ("M-a d", sendMessage $ JumpToLayout "dynamic tiling")
, ("M-a m", sendMessage $ JumpToLayout "maximised" )
, ("M-a f", sendMessage $ JumpToLayout "fullscreen" )
-- quit window
-- spawn/kill programs
[ ("M-a n", spawn myTerminal)
, ("M-a e", spawn myLauncher)
, ("M-a q", kill)
-- modal modes
, ("M-a l", setMode "layout")
, ("M-a s", setMode "launch")
--
-- Navigation
@ -96,12 +94,12 @@ myKeymap =
, ("M-<Right>", moveTo Next hiddenWS)
-- window rotation
, ("M-S-<Up>", rotAllUp)
, ("M-S-<Down>", rotAllDown)
, ("M-<Page_Up>" , rotAllUp)
, ("M-<Page_Down>", rotAllDown)
-- focus screens
, ("M-S-<Left>", prevScreen)
, ("M-S-<Right>", nextScreen)
, ("M-<Home>", prevScreen)
, ("M-<End>" , nextScreen)
-- switch workspaces
, ("M-1", windows $ W.greedyView "code" )
@ -117,12 +115,6 @@ myKeymap =
, ("M-S-4", windows $ W.shift "games")
, ("M-S-5", windows $ W.shift "misc" )
-- master pane manipulation
, ("M-<Home>", sendMessage Shrink) -- shrink master pane width
, ("M-<End>", sendMessage Expand) -- expand master pane width
, ("M-<Page_Up>", sendMessage (IncMasterN 1)) -- more windows in master
, ("M-<Page_Down>", sendMessage (IncMasterN (-1))) -- fewer windows in master
-- master window operations
, ("M-<Delete>", windows W.focusMaster)
, ("M-S-<Delete>", windows W.swapMaster )
@ -141,6 +133,30 @@ myKeymap =
]
--
-- Modal Modes
--
layoutMode :: Mode
layoutMode = mode "layout" $ mkKeysEz
-- jump to layout (exits immediately)
[ ("t", sendMessage (JumpToLayout "dynamic tiling") >> exitMode)
, ("m", sendMessage (JumpToLayout "maximised" ) >> exitMode)
, ("f", sendMessage (JumpToLayout "fullscreen" ) >> exitMode)
-- adjust master/slave split (stays in mode)
, ("<Left>", sendMessage Shrink)
, ("<Right>", sendMessage Expand)
, ("<Up>", sendMessage (IncMasterN 1))
, ("<Down>", sendMessage (IncMasterN (-1)))
]
spawnMode :: Mode
spawnMode = mode "launch" $ mkKeysEz
[ ("t", spawn myFileManager >> exitMode)
, ("s", spawn myScrot >> exitMode)
]
--
-- Layouts
--
@ -175,8 +191,11 @@ myXmobarPP = def
, ppHiddenNoWindows = grey0 . wrap " " ""
, ppUrgent = red . wrap " " ""
, ppLayout = aqua . wrap (grey0 " <fn=1>[</fn> ") (grey0 " <fn=1>]</fn> ")
, ppOrder = \[ws, l, _] -> [ws, l]
, ppOrder = \case { [ws, l, title, mode] -> [ws, l, mode, title]; xs -> xs }
, ppExtras = [lMode]
}
where
lMode = xmobarColorL "#d8a657" "#282828" . fixedWidthL AlignCenter "-" 6 $ logMode
--