You are not logged in.

#1 2013-07-16 06:26:29

ParanoidAndroid
Member
Registered: 2012-10-14
Posts: 114

xmonad vim-style keybindings

I'd like to set up modal keybindings for xmonad, with something like the following setup:

[command mode] (spawns applications, system actions)
    i: application mode (enter focused application, giving it the keyboard)
    v: visual/movement mode (move between windows, alter layout of windows)
[mod4] (escapes to command mode)

I've heard this can be accomplished through something called 'submaps' can anyone explain how this might be done? I'm by no means a haskell monkey, so an example would be nice if someone has one lying around.

EDIT:

Yes, I know how to modify xmonad keybindings through xmonad.hs. I have done so extensively. I just need to figure out how to set a modal behavior as described above.

Another question: from what I understand from reading the xmonad wiki section on submaps, it appears that these are designed to create Emacs-style key setups. That is, one must press a key or keys to access the submap and then another key or keys to activate a listing in that submap, and then one must repeat the process for the next action. This is a problem, because as far as I can tell it makes it impossible to use "modal" keybindings. That is, one presses a key or keys to enter a submap, which is then kept open and active until one presses an escape key or enters another submap within that submap. Is there a way around that limitation?

for reference incase it is needed, my existing and traditionally laid out myKeys section from my xmonad.hs:

myKeys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
    [ ((mod4Mask, xK_x ), kill) -- close window
    , ((modMask, xK_q), restart "xmonad" True)
    , ((mod4Mask, xK_m ), nextWS) -- move to next workspace
    , ((mod4Mask, xK_n ), prevWS) -- move to prev workspace
    , ((mod4Mask .|. mod1Mask, xK_m ), shiftToNext) -- move window to next workspace
    , ((mod4Mask .|. mod1Mask, xK_n ), shiftToPrev) -- move window to prev workspace
    , ((mod4Mask, xK_o ), windows W.swapMaster) -- push to master
    , ((mod4Mask, xK_i ), windows W.focusMaster)
    , ((mod4Mask, xK_h), sendMessage Shrink) -- shrink the master area
    , ((modMask, xK_comma ), sendMessage (IncMasterN 1))
    , ((modMask, xK_period ), sendMessage (IncMasterN (-1)))
    , ((mod4Mask, xK_j ), windows W.focusDown) -- swap the focused window with the next window
    , ((mod4Mask, xK_0 ), (setLayout $ XMonad.layoutHook conf)) --Reset layout to workspaces default
    , ((mod4Mask, xK_k ), windows W.focusUp)  -- swap thefocused window with the previous window
    , ((mod4Mask, xK_l ), sendMessage Expand) -- expand the master area
    , ((mod4Mask .|. shiftMask, xK_q), io (exitWith ExitSuccess)) -- quit xmonad

    , ((0, xK_Print ), spawn "/home/bryant/.scripts/screenshot")
    , ((mod4Mask, xK_BackSpace ), spawn "/home/bryant/.scripts/screenlock")
    , ((mod4Mask .|. shiftMask, xK_r ), spawn "/home/bryant/.scripts/remind-notify")
    , ((mod4Mask, xK_space ), spawn "/home/bryant/.scripts/touchpadtoggle")

    , ((mod4Mask, xK_semicolon ), spawn "dmenu_run -b -p Run: -nf green -sb green -nb black -sf black -fn 'Terminus-16' -h 23")
    , ((mod4Mask .|. shiftMask, xK_t), spawn "urxvtc")
    , ((mod4Mask .|. shiftMask, xK_3 ), spawn "urxvtc -e calc")
    , ((mod4Mask .|. shiftMask, xK_a ), spawn "urxvtc -e mocp")
    , ((mod4Mask .|. shiftMask, xK_m ), spawn "urxvtc -e htop")
    , ((mod4Mask .|. shiftMask, xK_n ), spawn "urxvtc -e newsbeuter")
    , ((mod4Mask .|. shiftMask, xK_c ), spawn "urxvtc -e wyrd")
    , ((mod4Mask .|. shiftMask, xK_2 ), spawn "urxvtc -e mutt")
    , ((mod4Mask .|. shiftMask, xK_e ), spawn "urxvtc -e vim")
    , ((mod4Mask .|. shiftMask, xK_h ), spawn "urxvtc -e ranger")
    , ((mod4Mask .|. shiftMask, xK_p), spawn "urxvtc -e cboard")
    , ((mod4Mask .|. shiftMask, xK_g ), spawn "gimp")
    , ((mod4Mask .|. shiftMask, xK_d ), spawn "dwb")
    , ((mod4Mask .|. shiftMask, xK_l ), spawn "urxvtc -e lynx")
    , ((mod4Mask, xK_b ), spawn "sleep 0.5 && xset dpms force off")
    , ((mod4Mask, xK_s ), spawn "systemctl suspend")
    , ((0, 0x1008FF14 ), spawn "mocp -G")
    , ((0, 0x1008FF17 ), spawn "mocp -f")
    , ((0, 0x1008FF16 ), spawn "mocp -r")
    , ((0, xK_F6 ), spawn "/usr/bin/mute_toggle")
    , ((0, xK_F7 ), spawn "/usr/bin/vol_down")
    , ((0, xK_F8 ), spawn "/usr/bin/vol_up") ]

Last edited by ParanoidAndroid (2013-07-17 01:10:02)

Offline

#2 2013-07-16 22:44:49

crowbar
Member
Registered: 2012-12-16
Posts: 11

Re: xmonad vim-style keybindings

From what i remember, all key bindings can be  set by modifying the xmonad.hs file in your ~/.xmonad folder. I don't know anything about haskell, but I learned a lot by reading the xmonad faq(s) .

Offline

#3 2013-07-18 02:32:33

ParanoidAndroid
Member
Registered: 2012-10-14
Posts: 114

Re: xmonad vim-style keybindings

Okay, so I managed to put this together. Here is my xmonad.hs as it stands now:

import XMonad
import System.Exit
import qualified XMonad.StackSet as W
import qualified Data.Map        as M
import XMonad.Util.Run
import XMonad.Actions.CycleWS
import Graphics.X11.ExtraTypes.XF86
import Graphics.X11.Xlib
import XMonad.Prompt
import XMonad.Prompt.Shell
import XMonad.Prompt.XMonad
import XMonad.Hooks.DynamicLog
import XMonad.Hooks.ManageDocks
import XMonad.Hooks.UrgencyHook
import XMonad.Actions.Submap as SM

-- layout
myLayout = avoidStruts (tiled ||| Mirror tiled ||| Full)
    where
        -- default tiling algorithm partitions the screen into two panes
        tiled = Tall nmaster delta ratio
             
        -- The default number of windows in the master pane
        nmaster = 1
                       
        -- Default proportion of screen occupied by master pane
        ratio = 1/2
                                 
        -- Percent of screen to increment by when resizing panes
        delta = 3/100

-- window rules
myManageHook = composeAll
    [ className =? "MPlayer"        --> doFloat
--    , className =? "Gimp"           --> doFloat
    , className =? "feh"            --> doFloat
    , resource  =? "desktop_window" --> doIgnore ]

-- vim-style modal keybindings
myNormSubMap = [ ((0, xK_Super_L), (SM.submap . M.fromList $ myComSubMap)) ]

myVisualSubMap = [
      ((0, xK_x), kill >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_k), prevWS >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_j), nextWS >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_m), shiftToNext >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_n), shiftToPrev >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_o), windows W.swapMaster >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_h), sendMessage Shrink >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_period), sendMessage (IncMasterN 1) >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_comma), sendMessage (IncMasterN (-1)) >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_d), windows W.focusDown >> (SM.submap . M.fromList $ myVisualSubMap))
--    , ((0, xK_0), (setLayout $XMonad.layoutHook conf) >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_f), windows W.focusUp >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_l), sendMessage Expand >> (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_Super_L), (SM.submap . M.fromList $ myComSubMap)) ]

myComSubMap = [
      ((0, xK_r), spawn "xmonad --recompile && xmonad --restart" >> (SM.submap . M.fromList $ myComSubMap))
    , ((0, xK_Print), spawn "/home/bryant/.scripts/screenshot" >> (SM.submap . M.fromList $ myComSubMap))
--    , ((0, xK_q), io (exitWith ExitSuccess)) >> (SM.submap . M.fromList $ myComSubMap)
    , ((0, xK_l), spawn "/home/bryant/.scripts/screenlock" >> (SM.submap . M.fromList $ myComSubMap))
    , ((0, xK_z), spawn "/home/bryant/.scripts/touchpadtoggle" >> (SM.submap . M.fromList $ myComSubMap))
    , ((0, xK_s), spawn "systemctl suspend" >> (SM.submap . M.fromList $ myComSubMap))
    , ((0, xK_b), spawn "sleep 0.5 && xset dpms force off" >> (SM.submap . M.fromList $ myComSubMap))
    , ((0, xK_v), (SM.submap . M.fromList $ myVisualSubMap))
    , ((0, xK_a), (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_i), (SM.submap . M.fromList $ myNormSubMap))
    ]

myAppSubMap = [
      ((0, xK_t), spawn "urxvtc" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_3), spawn "urxvtc -e calc" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_a), spawn "urxvtc -e mocp" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_m), spawn "urxvtc -e htop" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_n), spawn "urxvtc -e newsbeuter" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_w), spawn "urxvtc -e wyrd" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_2), spawn "urxvtc -e mutt" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_e), spawn "urxvtc -e vim" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_h), spawn "urxvtc -e ranger" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_d), spawn "dwb" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_g), spawn "gimp" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_l), spawn "urxvtc -e lynx" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_h), spawn "urxvtc -e ranger" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_d), spawn "dwb" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_g), spawn "gimp" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_l), spawn "urxvtc -e lynx" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_p), spawn "urxvtc -e cboard" >> (SM.submap . M.fromList $ myAppSubMap))
    , ((0, xK_Super_L), (SM.submap . M.fromList $ myComSubMap))
    ]

-- keys
myKeys conf@(XConfig {XMonad.modMask = modMask}) = M.fromList $
    [ ((mod4Mask, xK_x ), kill) -- close window
    , ((mod1Mask, xK_m ), SM.submap . M.fromList $ myComSubMap)
    , ((modMask, xK_q), restart "xmonad" True)
    , ((mod4Mask, xK_m ), nextWS) -- move to next workspace
    , ((mod4Mask, xK_n ), prevWS) -- move to prev workspace
    , ((mod4Mask .|. mod1Mask, xK_m ), shiftToNext) -- move window to next workspace
    , ((mod4Mask .|. mod1Mask, xK_n ), shiftToPrev) -- move window to prev workspace
    , ((mod4Mask, xK_o ), windows W.swapMaster) -- push to master
    , ((mod4Mask, xK_i ), windows W.focusMaster)
    , ((mod4Mask, xK_h), sendMessage Shrink) -- shrink the master area
    , ((modMask, xK_comma ), sendMessage (IncMasterN 1))
    , ((modMask, xK_period ), sendMessage (IncMasterN (-1)))
    , ((mod4Mask, xK_j ), windows W.focusDown) -- swap the focused window with the next window
    , ((mod4Mask, xK_0 ), (setLayout $ XMonad.layoutHook conf)) --Reset layout to workspaces default
    , ((mod4Mask, xK_k ), windows W.focusUp)  -- swap thefocused window with the previous window
    , ((mod4Mask, xK_l ), sendMessage Expand) -- expand the master area
    , ((mod4Mask .|. shiftMask, xK_q), io (exitWith ExitSuccess)) -- quit xmonad

    , ((0, xK_Print ), spawn "/home/bryant/.scripts/screenshot")
    , ((mod4Mask, xK_BackSpace ), spawn "/home/bryant/.scripts/screenlock")
    , ((mod4Mask .|. shiftMask, xK_r ), spawn "/home/bryant/.scripts/remind-notify")
    , ((mod4Mask, xK_space ), spawn "/home/bryant/.scripts/touchpadtoggle")

    , ((mod4Mask, xK_semicolon ), spawn "dmenu_run -b -p Run: -nf green -sb green -nb black -sf black -fn 'Terminus-16' -h 23")
    , ((mod4Mask .|. shiftMask, xK_t), spawn "urxvtc")
    , ((mod4Mask .|. shiftMask, xK_3 ), spawn "urxvtc -e calc")
    , ((mod4Mask .|. shiftMask, xK_a ), spawn "urxvtc -e mocp")
    , ((mod4Mask .|. shiftMask, xK_m ), spawn "urxvtc -e htop")
    , ((mod4Mask .|. shiftMask, xK_n ), spawn "urxvtc -e newsbeuter")
    , ((mod4Mask .|. shiftMask, xK_c ), spawn "urxvtc -e wyrd")
    , ((mod4Mask .|. shiftMask, xK_2 ), spawn "urxvtc -e mutt")
    , ((mod4Mask .|. shiftMask, xK_e ), spawn "urxvtc -e vim")
    , ((mod4Mask .|. shiftMask, xK_h ), spawn "urxvtc -e ranger")
    , ((mod4Mask .|. shiftMask, xK_p), spawn "urxvtc -e cboard")
    , ((mod4Mask .|. shiftMask, xK_g ), spawn "gimp")
    , ((mod4Mask .|. shiftMask, xK_d ), spawn "dwb")
    , ((mod4Mask .|. shiftMask, xK_l ), spawn "urxvtc -e lynx")
    , ((mod4Mask, xK_b ), spawn "sleep 0.5 && xset dpms force off")
    , ((mod4Mask, xK_s ), spawn "systemctl suspend")
    , ((0, 0x1008FF14 ), spawn "mocp -G")
    , ((0, 0x1008FF17 ), spawn "mocp -f")
    , ((0, 0x1008FF16 ), spawn "mocp -r")
    , ((0, xK_F6 ), spawn "/usr/bin/mute_toggle")
    , ((0, xK_F7 ), spawn "/usr/bin/vol_down")
    , ((0, xK_F8 ), spawn "/usr/bin/vol_up") ]

myLogHook = dynamicLog

main = do

xmonad $ defaultConfig
    {

    -- override settings
   terminal = "urxvtc"
   , borderWidth = 1
   , modMask = mod4Mask
   , workspaces = ["1","2","3","4","5","6","7","8","9","10"]
   , normalBorderColor  = "#000000"
   , focusedBorderColor = "#00ee00"
   , focusFollowsMouse = False
   , layoutHook = myLayout
   , manageHook = manageDocks <+> myManageHook
   , handleEventHook = docksEventHook
   , logHook = myLogHook
   , keys = myKeys
    }

As you can see, I kept my original, traditional keybindings in addition to the new submappings, and stuck those under a keybinding in the myKeys section. A few things:

1. spawning stuff doesn't work. I have to enter spawn mode ('a') and then hit the app key, and exit the spawn mode. Only after exiting will it spawn the application.

2. The vim-style mode (initiated by pressing 'Alt-m') doesn't seem to be persistent. It reverts to original keybindings after an action is completed in a mode.

3. I need a way to display what mode I'm in, preferably through conky.

Does anyone have a solution to any of these problems? the first two are the more pressing.

EDIT:

I altered my config so that a single button, Menu, triggers command mode from normal keybindings and the submappings. Spawning stuff is still ugly: I have to press the Menu button twice after hitting an app-spawn key to get the app to open. Additionally, visual mode is screwy. If I hit 'v' and then 'x', it should kill the selected window, which it does. Instead of then resizing the remaining windows to fill the screen as with my normal keybindings, the window is deleted to leave an empty space and the focus is sent to nowhere (rather than to the next available window). Persistency of the modes is still an issue, as well: despite the recursive setup, I have to keep punching Menu to keep command mode active, for example. Otherwise it defaults to my original keybindings. Can someone see a solution to these problems?

Last edited by ParanoidAndroid (2013-07-18 18:47:47)

Offline

#4 2014-05-07 00:38:49

michaelrose
Member
Registered: 2014-05-07
Posts: 1

Re: xmonad vim-style keybindings

I've set up something similar for i3wm another tiling wm
Firstly xcape is setup so that pressing window/super or the shift keys produces a different key than holding them down.
Tap super to enter command mode

in command mode

q kill app
dw kill window
dd kill all apps in workspace
da kill all visable apps across all visible workspaces
r resize mode where hjkl is used to resize
o[a-zA-Z] wherin the key used is a quickkey for opeing a given app ie v for vim or b for browser
f[a-zA-Z] wherin the key used is a quickkey for focusing the same app as used in o
w[a-z] switch to workspace[a-z] where pressing and releasing a shift is the same as super -> w thus tap shift then a letter to switch workspaces
m[a-z] move currently selected app or apps to [a-z]
t[a-z] take currenly selected app or apps to [a-z] and go there
g[a-z] get apps on [a-z] and bring to current workspace
R[a-z] -> [a-z] rearange apps from first target to second target without going there
s or v split horizontally or vertically makes the next item opened split in the manner chosen ie sot would open a terminal horizonally below the current app
at arrange tabbed
ah arange splith
av arrange splitv
af arrange floating
F fullscreen
shift+hjkl move window accordingly

an andio mode in which
h and l change tracks
p plays/pauses
j and k control volume
n moves all currenly playing audio streams to the next output making that the default and the output effected by the forementioned volume controls

double tapping super also brings up dmenu to start applications

looks like so http://imgur.com/Msvp3we

Offline

#5 2014-12-03 15:14:38

sharethewisdom
Member
Registered: 2014-09-22
Posts: 62

Re: xmonad vim-style keybindings

@michaelrose Is your config on github? Have you made any changes to that setup? I'd like to make the permanent switch from i3 to xmonad once I've learned more Haskell basics.

I'd be interested to know if anyone finds a timed interpretation of keys presses useful. (i.e. record the number of presses within say, a ~300ms timespan)

I used to time double tabbed keys (not while inserting) in windows because I had too few keys/combinations for too few modes. hmm The idea of using only one button on the mouse being the only input that gets timed is a bit absurd anyway...

I strongly advocate thinking in semantic terms for as many modes as it takes (they should be clearly indicated when active tough). But sometimes I feel that more can be done against using key combinations. Why not use double presses for letters to upper-case them? Or design a device for foot-operated modifier pedals? Free those hands from playing chords and focus on the melody! (not the organ player in me speaking)

Last edited by sharethewisdom (2014-12-03 15:17:26)

Offline

Board footer

Powered by FluxBB