Implement keychains
A keychain is a space separated key binding, such that we can define for example:
hc keybind 'Mod4-m Right' focus Right
hc keybind 'Mod4-m Left' focus Left
This only binds Mod4-m and when this is pressed, there are two keybindings Left and Right</Right>.
Open questions for me:
- Do keychains have to be prefix free? What happens if I now run the command
hc keybind Mod4-m fullscreen toggle
does it remove the above keychains? If not, what happens when I press Mod4-m?
- Are other keychain commands active while we are in a key chain? That is, if we run
hc keybind Mod4-f fullscreen toggle
and press Mod4-m, is the Mod4-f still active?
- I think should remove the keychains.
- Enter other keychains should de-activite an active keychain.
And one more suggestion: can leave (with a keystroke, eg. Esc) an active keychain without running any command.
- Agree with @uzsolt
- I think a keychain deactivates any other keys.
a. It would be nice to have more-than-two keychains, e.g. Mod4-m, r, l (to resize to left)
b. A hook could be fired so that I can show active keychain somewhere (another hook when leaving the keychain)
c. To the suggestion by @uzsolt: these keys should be configurable (a command like leave_keychain—if it was for some reason used when no keychain active, this would do nothing)
- I think a keychain deactivates any other keys.
c. To the suggestion by @uzsolt: these keys should be configurable (a command like
leave_keychain—if it was for some reason used when no keychain active, this would do nothing)
I've also thought about this, but these two points together imply that one has to bind this for every keychain:
hc keybind Mod4-m Escape leave_keychain
hc keybind Mod4-m l r focus right
hc keybind Mod4-m Escape leave_keychain
hc keybind Mod4-m l focus right
hc keybind Mod4-x Escape leave_keychain
hc keybind Mod4-x t spawn xterm
Some maybe we need another "modifier" that says that a keybind is active in keychains, so instead of the above snippet it suffices to write:
hc keybind InKeychains-Escape leave_keychain
hc keybind Mod4-m l r focus right
hc keybind Mod4-m l focus right
hc keybind Mod4-x t spawn xterm
I've also thought about this, but these two points together imply that one has to bind this for every keychain
Right. Who says every chain has to have the same keys to leave it? If you look at i3's resize “keychain” (btw. not leaving the keychain until explicitly said is another good idea), they must define it there, too.
Some maybe we need another "modifier" that says that a keybind is active in keychains
IMO, this is not necessary, but I think many people would make use of it.
When I'm thinking of it, don't we already have a leave_keychain in true (unless the i3's “mode” way used)?
When I'm thinking of it, don't we already have a
leave_keychainintrue(unless the i3's “mode” way used)?
That's right :-)
Timeout. If enter into one keychain and doesn't press any key in some seconds should leave the keychain.
Event. Would be nice events to enter keychain (maybe with identifier), leave keychain.
Instead of calling them "keychains" wouldn't it be better to call them "mode" instead?
Because that is what they seem to be, at least to me.
As "modes" they are "relatively" well known and well understood principle of modal operation. It would also be nice if they matched what users expect from "mode" as used in other "modes capable" software like vi/vim, tmux, qute, zsh and whatnot.
Configuration wise this seems much better to me as well:
hc keybind Mod4-m enter_mode my_resize_mode
hc keybind -m my_resize_mode Right focus right
hc keybind -m my_resize_mode Left focus Left
hc keybind -m my_resize_mode Esc leave_mode
Now when user is happy with my_resize_mode "internal bindings", but not the chord it is entered with, they can still easily rebind mode entering chord to something else while rest of the config is untouched:
$ hc keybind Mod4-r enter_mode my_resize_mode
This is how zsh does it. I don't use modes that often besides zsh, I think tmux and vim use slightly more baked-in configuration like Thorsten proposes, with chord and space embedded. Not sure, I am not modal expert anyway.
Should modes be fully fledged herbst objects they could be exposed and introspected by attributes:
$ hc set_attr modes.my_resize_mode.timeout 500
$ hc get_attr modes.my_resize_mode.timeout
In such case, I imagine first keybind of mode would "auto create" it for object system to use, but I believe something should still be provided to be able create some pre-configured modes without actual "enter" bind yet, maybe like this (?):
hc create_mode my_resize_mode
hc keybind -m my_resize_mode Right focus right
Just my thoughts about it.
Hello everyone,
I was about to suggest a feature like this, but I might as well contribute here.. I'm 200% with you @etosan, I use zsh, vim & tmux for everything, and wanted something similar to the simple nature of zsh keymaps for hlwm.
zsh's modes (keymaps) do not even have a exit binding, you just switch to another mode.
The advantage I see of having an exit binding though is that you can easily implement a mode stack, where you can go to a mode, and say leave_mode to go to the previous mode on the stack.
The usual bindings would go into a main mode by default (or default?).
Then you can create modes, either fresh or by copying an existing mode. This can be useful to define a default exit binding or a timeout for example If I take the syntax from @etosan (which is great in my opinion), here is how I would make a mode that allows me to focus frames:
# define mode template
hc create_mode base_template
hc attr modes.base_template.timeout 10000 # 10sec
hc attr modes.base_template.timeout_action leave_mode
hc keybind -m base_template Escape leave_mode
# or
hc keybind -m base_template Escape enter_mode main
# define mode
hc create_mode frame_focus -c base_template # copy the mode
hc keybind -m frame_focus h focus -e left
hc keybind -m frame_focus j focus -e down
hc keybind -m frame_focus k focus -e up
hc keybind -m frame_focus l focus -e right
# define enter binding
hc keybind Mod4-Control-f enter_mode frame_focus
Notice that I used modes as an object with attributes to define the mode's timeout (in millisecond) and the mode's timeout action, to do something on timeout (would be required when timeout is specified, or leave_mode could be the default action).
Another attribute that could be useful is binding_fallback_mode, where you give it a mode to pass unknown keybinds to.
An easy extension of this would be to have a special value like last_mode, that would pass unknown keybinds to the last mode in the stack. This system can be used to 'activate' multiple modes at the same time in any order.
I can see this being useful with a top level management mode (at the same conceptual level as the main mode) that I'd use to change the layout, resize frames/windows, add frames, arrange monitors, etc. And each kind of action would be a separate mode that can be activated at the same time or not ¯\_(ツ)_/¯
To better control the stack of modes there could be a command (or an argument for leave_mode) to disable all the modes that are after/above the mode that matched the key, to clear 'temp' modes.
This could be used as an alternative to defining an exit binding. For example:
# define base mode
hc create_mode base_template
hc attr modes.base_template.binding_fallback_mode main # unknown keys will be interpreted by the main mode
# define temp mode (same as the other example above)
hc create_mode frame_focus -c base_template # copy the mode
hc keybind -m frame_focus h focus -e left
hc keybind -m frame_focus j focus -e down
hc keybind -m frame_focus k focus -e up
hc keybind -m frame_focus l focus -e right
# in main config, define a 'global exit binding' to exit any other active mode (above in the stack of modes)
hc keybind Mod4-Escape leave_mode all_above
By default the mode stack looks like main. When the frame_focus mode is enabled, the stack looks like main frame_focus, it is on top of the main mode, and with the fallback I can still use my normal keys to go to another tag or something.
Now if I press Mod4-Escape, the key is unknown for frame_focus so it checks the main mode (as per the binding_fallback_mode attr), the key matches in the main mode and will leave all modes above the main mode, so the stack will looks like main to finish...
To simplify the work if this is the solution we want to go for, the number of thing to add (in addition to this mode system) would be:
- command
create_mode(with flag-cto copy a mode on creation) - command
leave_mode(with optionalall_above) - top level object
modes - object mode with the following fields
timeout,timeout_action,binding_fallback_mode
And I think that's all
I love how a simple system allows many things to be possible, and from what I've seen in hlwm there are many simple but powerful systems, and a mode one would fit perfectly!! :smiley:
Just my opinionated thoughts about this, what do you think about it?
Thanks @bew and @etosan; I think you convinced me to implement modes and mode-sensitive keybindings instead of implementing key chains directly :)
You have my +1 for the mode stack concept. It's similar to how QMK keyboard firmware implements layers.
Worth noting that there is some prior art in how Openbox represents chains as nested XML, and Sxhkd supports only one-off "chords". Both of them support a global "escape" key (C-g by default in Openbox, like in Emacs, and Esc by default in Sxhkd) that break out of a chain. This is like the leave_keychain proposal above, but notably it exists outside the usual key binding machinery and is completely global: C-g works everywhere in Openbox, not just at certain points in certain chains.
With arbitrary modes, it is easy to get "lost" in the mode stack, whereas in a simpler chain-based system you always have a global exit path back to the "main" mode. Getting lost in the layer stack is a nontrivial problem in QMK. It is likely to be a problem in Herbstluftwm as well, even if you are clever and added a "current HLWM mode" widget to your status bar :)
QMK solves the problem by offering a configurable keybinding that takes you directly to a specified layer and clears the stack. So you can give yourself an exit path that works just like Emacs/Openbox C-g. Vim/Neovim similarly has its C-\ C-n mapping. I believe this corresponds to the all_above command in the Zsh-like proposal above.
There is also an argument to be made that a mode stack is distinct from a keybind chain, and that perhaps an ideal system would support both. Vim is a great exmple of such a system.
I'm not entirely sure if I have a point to make, other than pointing out some related prior art that I think is relevant and worth examining. I especially wanted to highlight the "getting lost in the mode stack" problem and bring up the possibility that a mode stack and keybind chains/chords are not necessarily equivalent when it comes to the ergonomics of actually writing your key bindings.
Thanks for your comment! I'm not sure whether one really needs a stack of modes; I thought a plain mode is sufficient and one should be able to specify in keybind whether a keybind returns to the default mode or stays in the current mode. More specifically, I don't see a scenario where one would want to return to somewhere in the mode stack, I only see the scenario to either stay or to completely return to the default mode.
Only adding to this discusion after catching up, is that some (understand any) command to exit into "default" modal scope in any circumstance, mode stack or not, any chord level and keypress count (depth), is great idea and would be really perfect addition! Eg. global mode "cancel".
I not very keen on modal editors per se, but I can get around in vim somewhat (enough to make edits with it at 5-9 job as it is mandated there). After years of being dex & nano user, that sucks. That is for entirely different time and place, but I do believe actual modes, as implemented in vim are pure evil and actual vim (I have not tried neovim yet) is very poor tool at communicating them (mentioned smart widget). It's configurability in this regard is also pretty weak. If you grew up with vim in formative years, this is not a problem, but if not, it can be really painful. However modes, as optional context sensitive control scheme, as zsh allows it, seem to me much better approach to me (i3 seems to follow that scheme too, I guess).
With that out of my system, I want to point out: it's impossible to count how many times I press escape daily (usually in multiple cadences) only to be sure I am in known state in vim so that it does not do any bullshit I don't expect. Having this bindable to single key chord would be really helpful.
That is all I wanted to add.
As a new user to herbstluft who misses key chords a lot, here are my $0.02: There are two different suggestions here:
-
key chords like in vim or emacs:
These return to the “normal” mapping after one keypress. These should be familiar to essentially everyone who has used an IDE or more advanced text editor before.
-
modes like in i3 or zsh
Modes usually stay active after a second key press.
Imho, these two have different use cases and are not mutually exclusive. 1. can be implemented witch 2, but less elegant, imho.
Without a mode stack, option 2. can be configured today by putting the bindings for each mode in a separate shell script and calling them accordingly. E.g:
# autostart
# no keybinds here
# …
"$HOME/.config/herbstluftwm/mode-1.sh"
# …
# mode-1.sh
# clear keymap here
# …
hc keybind "$Mod-m" spawn "$HOME/.config/herbstluftwm/mode-2.sh
# …
# mode-2.sh
# clear keymap here
# …
hc keybind "Escape" spawn "$HOME/.config/herbstluftwm/mode-1.sh
# …
One could even implement stacking in these scripts (e.g. by using a file in /tmp/). This solution also feels more in line to me with the philosophy of herbstluft to me.
That being said, option 1 seems to be such a logical exension of the syntax that I'd really like to see it. (I may even take a stab at it; but that could take a while).