in Others

Reasonable mouse support in tmux

Yes, we finally got sane, configurable mouse support. In version 2.1 they changed mouse-mode, mouse-select-window/pane etc with single mouse switch. Mouse actions now generates key events that can be mapped as ordinary keys.

In my distro (Ubuntu 14.04) there is version 1.8 of tmux, so we need to get latest from sources:

sudo apt-get build-dep tmux
sudo apt-get clean tmux
wget https://github.com/tmux/tmux/releases/download/2.1/tmux-2.1.tar.gz
tar xzf tmux-2.1.tar.gz
cd tmux-2.1
./configure
make
sudo make install

In manual (man tmux) in paragraph MOUSE SUPPORT we could read that new key events available are named MouseUpX, MouseDownX and MouseDragX where X is button no (1-3), followed by location suffix that describe where you are pointing cursor (Pane, Border or Status). So when you right-click on the status line, events MouseDown3Status and MouseUp3Status will be emitted.

Ok, but how is that better than the former method? You could now define your mouse behaviour as you like. That include (some limited) use of scripting. E.g. to spawn new window after selected by right click on the status line label, you could add something like this to your .tmux.rc:

# don't forget to turn mouse on
set mouse on

bind-key -n MouseDown3Status new-window -a -t=

Option -t= means that the target is window/panel (depends on command) that is clicked.

Or maybe you want to be able to reorder windows in status bar by drag & drop?

bind-key -n MouseDrag1Status swap-window -t=

Ok, that’s great, but we all know what you really want in tmux.

Scroll with mouse in every situation

Yep, it is possible with tmux 2.1. It is not pretty, but it works. And by every situation I mean normal and alternative terminal mode and also tmux copy mode (when you can scroll through history). You could even scroll up to access this mode.

bind-key -n WheelUpPane \
    if-shell -Ft= "#{?pane_in_mode,1,#{mouse_button_flag}}" \
        "send-keys -M" \
        "if-shell -Ft= '#{alternate_on}' \
            'send-keys Up Up Up' \
            'copy-mode'"

bind-key -n WheelDownPane \
    if-shell -Ft= "#{?pane_in_mode,1,#{mouse_button_flag}}" \
        "send-keys -M" \
        "send-keys Down Down Down"

Command if-shell -F is used to check given variable value. If it is non-zero and non empty, first argument will be evaluated, otherwise, second one. Flag pane_in_mode is set if pane is in tmux copy mode. mouse_button_flag is set when running app is actively capturing mouse (like vim). alternate_on is set whenever terminal working in alternate mode (where there is no history to scroll by, like top). If you want to debug these variables, you could print them in status line

set -g status-right 'mouse_btn_flag:#{mouse_button_flag} pane_in_mode:#{pane_in_mode} alt:#{alternate_on}'

Construction like #{?pane_in_mode,1,#{mouse_button_flag}} checks the value of first variable and returns 1 if it is non-zero, second variable value otherwise. It is logical OR constructed with if-shell syntax.

Starting from second example – wheel down. If we are in tmux copy mode or running app want to catch mouse, we send mouse escape strings directly (send-keys -M will pass through mouse events). Otherwise we are sending down arrow key three times. Why not pass mouse event in all cases? Well, if running app don’t tell terminal to catch mouse, most terminals will be doing same thing. That’s why you could scroll through less and man pages.

Wheel up scenario has one more condition added. You can go to copy mode, and then scroll through tmux history when you are not in alternative mode.

This is most sane setup that I’ve been able to come with. It works with shells, vim, man pages, less, htop, mc without breaking terribly anything. One drawback (for me) is, scrolling through tmux copy mode progress by one line at a time. It probably could be fixed by adding extra condition to these lines, but I’m afraid that it will break something. And it is obfuscated enough, already.

For reference, here is my .tmux.conf.

Write a Comment

Comment

  1. Improvement suggestion for:
    1. In VIM scroll the text (like it should) rather than just move the cursor.
    2. Copy mode exits when reaching the bottom.
    3. Use 3 lines per wheel tick also for copy-mode.

    bind-key -n WheelUpPane \
    if-shell -Ft= “#{?pane_in_mode,1,#{mouse_button_flag}}” \
    “send-keys -M ; send-keys -M ; send-keys -M” \
    “if-shell -Ft= ‘#{alternate_on}’ \
    ‘send-keys ^y ^y ^y’ \
    ‘copy-mode -e'”

    bind-key -n WheelDownPane \
    if-shell -Ft= “#{?pane_in_mode,1,#{mouse_button_flag}}” \
    “send-keys -M ; send-keys -M ; send-keys -M” \
    “send-keys ^e ^e ^e”

  2. Thanks for your input. I was also thinking about using triple send-keys to scroll by 3 lines, but was afraid that it will break something else. Did you find any issues with that setup?
    Switch -e with copy-mode doesn’t work with my setup for some reason.
    Also, do you consider ^y and ^e keys being better than Up and Down?

  3. -e for copy-mode means exit copy mode when scrolling down and reaching the bottom. It might only be available in newer tmux. You can remove the e part if your version doesn’t support it (or build a newer version yourself).

    In vim, however, mouse_button_flag was not set, but mouse_standard_flag was set. So your code just sent up/down keys which move the cursor instead of scrolling, so ^e and ^y scroll in vim without moving the cursor (like wheel scroll should typically behave) and still work at other places (less, man), so I thought it would be better.

    But apparently it’s not a great generic solution since not all apps handle them (e.g. htop or nano), and also vim in visual mode is not great with them.

    After I posted my version, I found out that in vim, if i set ttymouse=xterm2, then the flags (button/standard) state reverse, and then your original code works fine for vim too.

    The mouse_standard_flag apparently gets set in apps which handle “basic” mouse (vim without that option, nano with mouse mode).

    I did not notice any issues with the three lines scroll after about a day and several apps.

    I did, however, notice one issue for the scroll-down action: If a long operation is happening on the non-alt screen (such as compilation etc), then scrolling down inserts a down key which prints at the console as an escape sequence (since nothing handles it).

    I ended up with the following:
    – In vim, set ttymouse=xterm2 to make the button_flag get set and nicer mouse behavior in general.
    – Use 3 lines scroll
    – Don’t send down key in non-alt screen.
    – Exit copy-mode when reaching the bottom (the “e” thingy).

    “`
    bind-key -n WheelUpPane \
    if-shell -Ft= “#{?pane_in_mode,1,#{?mouse_button_flag}}” \
    “send-keys -Mt=; send-keys -Mt=; send-keys -Mt=” \
    “if-shell -Ft= ‘#{alternate_on}’ \
    ‘send-keys -t= Up Up Up’ \
    ‘copy-mode -et='”

    bind-key -n WheelDownPane \
    if-shell -Ft= “#{?pane_in_mode,1,#{?mouse_button_flag,1,#{?alternate_on,0,1}}}” \
    “send-keys -Mt=; send-keys -Mt=; send-keys -Mt=” \
    “send-keys -t= Down Down Down”
    “`

    All that being said, I just _know_ that some day some new case will not behave correctly with something 😉

    Anyway, thanks again for your code and approach – it’s by far the best I’ve seen around (which makes you wonder – why doesn’t tmux has this kind of thing built in like normal terminals behave by default…)

  4. Obviously the first mouse_button_flag should not have a ‘?’ (never edit code in web text boxes!)

  5. I am using ttymouse=sgr in vim and my config works with that setup (with mouse_button_flag). Maybe we should use something like “mouse_button_flag or mouse_standard_flag”, but I’m not sure how to write this with tmux config syntax.
    I can’t scroll in htop when I run it directly under xterm (without using tmux) so it seams that htop is not handling mouse scroll at all. So I will stay with Up/Down keys for now.
    copy-mode with -e switch is now working for me. I have been using some outdated version of tmux from git, not the version I was writing about in my post. Great feature.

    Why they didn’t make this behavior as default? It is a mistery to me, also.

  6. Hi, i’m used to scrolling with the mouse inside less. Unfortunately, i’ve not been able to get it working. Did you happen to have any luck with that?