Skip to content

Instantly share code, notes, and snippets.

@mikeboiko
Last active March 23, 2024 07:32
Show Gist options
  • Star 27 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mikeboiko/b6e50210b4fb351b036f1103ea3c18a9 to your computer and use it in GitHub Desktop.
Save mikeboiko/b6e50210b4fb351b036f1103ea3c18a9 to your computer and use it in GitHub Desktop.
Automatically update $DISPLAY for each tmux pane after attaching to session
set-hook -g client-attached 'run-shell /bin/update_display.sh'
#!/usr/bin/env bash
# The problem:
# When you `ssh -X` into a machine and attach to an existing tmux session, the session
# contains the old $DISPLAY env variable. In order the x-server/client to work properly,
# you have to update $DISPLAY after connection. For example, the old $DISPLAY=:0 and
# you need to change to DISPLAY=localhost:10.0 for the ssh session to
# perform x-forwarding properly.
# The solution:
# When attaching to tmux session, update $DISPLAY for each tmux pane in that session
# This is performed by using tmux send-keys to the shell.
# This script handles updating $DISPLAY within vim also
NEW_DISPLAY=$DISPLAY
# NEW_DISPLAY=$(tmux show-env | sed -n 's/^DISPLAY=//p')
# Update $DISPLAY in bash, zsh and vim/nvim
tmux list-panes -s -F "#{session_name}:#{window_index}.#{pane_index} #{pane_current_command}" | \
while read pane_process
do
IFS=' ' read -ra pane_process <<< "$pane_process"
if [[ "${pane_process[1]}" == "zsh" || "${pane_process[1]}" == "bash" ]]; then
tmux send-keys -t ${pane_process[0]} "export DISPLAY=$NEW_DISPLAY" Enter
elif [[ "${pane_process[1]}" == *"vi"* ]]; then
tmux send-keys -t ${pane_process[0]} Escape
tmux send-keys -t ${pane_process[0]} ":let \$DISPLAY = \"$NEW_DISPLAY\"" Enter
tmux send-keys -t ${pane_process[0]} ":silent! xrestore" Enter
fi
done
@rjmccabe3701
Copy link

This has been bugging me for years. Thanks.. I extended this slightly to update panes running vim and python:
https://github.com/rjmccabe3701/linux_config/blob/master/scripts/update_display.sh

@mikeboiko
Copy link
Author

@rjmccabe3701, I like your additions to it!
Question for you about the vim part of it. I also attempted to do a very similar thing for vim, but neither my method or your method yielded the results I was looking for. The main reason I wanted to get the x-server working properly on tmux sessions was to get clipboard sharing between remote ssh sessions and my local machine.
When I simply set DISPLAY from within vim, my vim still uses a different clipboard from my main system. I still seem to have to exit vim and update DISPLAY in my shell in order to fix the vim clipboard.
Does vim behave in the same way for you?

@rjmccabe3701
Copy link

@mikeboiko : I found VcXsrv works the best.

I have this thing selected
image

Take a look at my zsh/vim/tmux setup in my linux_config repo. You can customize linux_config. My personal customizations are in the linux_config_custom repo. Here is my Vim clipboard configuration:

https://github.com/rjmccabe3701/linux_config_custom/blob/master/dot_files/myvimrc#L5-L44

I also have clipboard integration with tmux. In a perfect world the tmux-yank plugin would do this, but it doesn't seem to work for me. So I run this tmux_fixups.sh script.

@mikeboiko
Copy link
Author

@rjmccabe3701, thanks for sharing your config.
I saw that you're using neovim, while I'm using 8.2. After doing some more research, I ended up finding https://gist.github.com/antonkratz/ebfcefdb5fdf266631e4985d65535322#solution-2
It seems that vim doesn't automatically restore x-connections once they are dropped. You have to run :xrestore. I haven't done any thorough testing on neovim, but judging from your rc and the fact that the xrestore command doesn't exist, it seems this is done automatically.
I'm glad to get the vim portion of this script finally working!

@dichotomies
Copy link

Neat script, thanks for sharing!

The command set-hook -g client-attached 'run-shell /bin/update_display.sh' was ignored by tmux in my case, but changing it to set-hook -g client-attached 'run-shell "sh /bin/update_display.sh"' made it work.

@mikeboiko
Copy link
Author

Neat script, thanks for sharing!

The command set-hook -g client-attached 'run-shell /bin/update_display.sh' was ignored by tmux in my case, but changing it to set-hook -g client-attached 'run-shell "sh /bin/update_display.sh"' made it work.

No problem!
Was update_display.sh executable? Perhaps running sudo chmod +x update_display.sh would have fixed it too. Either way, glad it works for you :)

@dichotomies
Copy link

You're right, that makes sense. Just double checked and this works also. Thanks for pointing that out.

@qjzh0603
Copy link

hi mikeboiko. I have the same trouble with you. Thx for shaing! But it doesn't work for me. I add "echo $NEW_DISPLAY > ~/1.log" in update_display.sh to debug it. The file 1.log created by update_display.sh is blank or with a blank line. It seems that tmux command in update_display.sh means nothing in run-shell env. I found other command in linux works, for example, "ls ./" can echo to 1.log normally

@mikeboiko
Copy link
Author

Probably related to the shell that tmux is using.
Try running which tmux in your regular shell.
Mine outputs: /usr/bin/tmux.
You could hard-code this path in update_display.sh

@qjzh0603
Copy link

qjzh0603 commented Jun 2, 2022

hi mikeboiko, you're right, my tmux is in my $HOME path. It does not work, although i add it to $PATH. As you've said, it works when I use the abs path. Thank you.
But I found a small bug: when i detach tmux from a foreground vim, and i attach tmux session again, the vim will xrestore the right $DISPLAY. After quit vim, the bash terminal would not update $DISPLAY automatically

@mikeboiko
Copy link
Author

@qjzh0603, glad you got it working.
You're right, I experience the same annoyance. There isn't a good automated solution to this problem that I know of, so I just created an alias to update my display manually after I quit vim:
alias ud='export DISPLAY="`tmux show-env | sed -n \"s/^DISPLAY=//p\"`"'

@robertbu
Copy link

Thanks for the script!. BTW: I added hook to session switching:
set-hook -g client-session-changed 'run-shell /bin/update_display.sh'
It works. However, DISPLAY is updated every time I switch session event if it's not necessary. Tried to guard the update script with
if [[ $NEW_DISPLAY != $DISPLAY ]]; then
It seems that this condition always satisfies even if DISPLAY really needs update. $DISPLAY gives the value of the shell before attaching to tmux session. So the condition is matched.

@mikeboiko
Copy link
Author

Thanks for the script!. BTW: I added hook to session switching: set-hook -g client-session-changed 'run-shell /bin/update_display.sh' It works. However, DISPLAY is updated every time I switch session event if it's not necessary. Tried to guard the update script with if [[ $NEW_DISPLAY != $DISPLAY ]]; then It seems that this condition always satisfies even if DISPLAY really needs update. $DISPLAY gives the value of the shell before attaching to tmux session. So the condition is matched.

@robertbu, I like it! I have updated my script with this logic.

@mikeboiko
Copy link
Author

@robertbu, actually that new logic didn't work. When update-display.sh evaluated $DISPLAY, it was already seeing the new DISPLAY value.
In fact, I simplified the script to use: NEW_DISPLAY=$DISPLAY
I'm not sure if you had to do anything else to make this conditional logic work on your side. Would love to get it working too.

@graham33
Copy link

graham33 commented Mar 2, 2024

Thanks for this. I made a version which updates WAYLAND_DISPLAY based on this: https://github.com/graham33/scripts/blob/master/update_wayland_display.sh. It's just a search and replace on your script, ideally it would be great to have one script that handled both, or maybe any env var in the tmux update-environment list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment