reversed(top()) code tags rss about

Fix tmux hang in X

September 25, 2013
[terminal] [gnu/linux] [xorg] [xterm] [issue] [bash] [tmux] [ssh]

Recently I needed to use tmux. Turned out that it wasn’t even installed on my system, which, of course, wasn’t a problem. After it was installed and ran I was presented with a prompt that didn’t responed to anything. The only thing that worked well was pkill tmux.

Four hours of tries showed that tmux seems to work in general, but it waits eternally for something inside select() system call. The strangest part of all this was that it worked just fine in native linux console and through SSH. As a workaround just to be able to use it I did this ugly thing:

  1. Started sshd.
  2. Connected to localhost from xterm.
  3. Ran and used tmux.

Then I noticed another strange thing, bash prompt was of regular grey color. Trying to use other of my customizations (aliases, functions etc.) didn’t succeed either. I found it! My .bashrc broke tmux. Bisection through commenting parts of the .bashrc revealed this piece of code:

# check cursor position and add new line if we're not in the first column
function prompt-command()
{
    exec < /dev/tty
    local OLDSTTY=$(stty -g)
    stty raw -echo min 0
    echo -en "\033[6n" > /dev/tty && read -sdR CURPOS
    stty $OLDSTTY
    [[ ${CURPOS##*;} -gt 1 ]] && echo "${color_error}↵${color_error_off}"
}
PROMPT_COMMAND='prompt-command'

Do you see what is wrong with it? (By the way, it’s based on this StackOverflow answer).

No? Here it is:

    exec < /dev/tty

The stdin of the shell is changed, but never restored. And tmux seems to be unable to handle such redirection (GNU screen works well with it). Now when the reason is known, I can reproduce the issue on demand with this command:

exec < /dev/tty && tmux

I see two possible ways to solve the issue:

  1. Run body of the function in another process (sub-shell fits well in this case).
  2. Save and restore old value of stdin.

The first solution could look like this:

# check cursor position and add new line if we're not in the first column
function prompt-command()
{
    (
        exec < /dev/tty
        local OLDSTTY=$(stty -g)
        stty raw -echo min 0
        echo -en "\033[6n" > /dev/tty && read -sdR CURPOS
        stty $OLDSTTY
        [[ ${CURPOS##*;} -gt 1 ]] && echo "${color_error}↵${color_error_off}"
    )
}
PROMPT_COMMAND='prompt-command'

And the second one:

# check cursor position and add new line if we're not in the first column
function prompt-command()
{
    exec 100<&0
    exec < /dev/tty

    local OLDSTTY=$(stty -g)
    stty raw -echo min 0
    echo -en "\033[6n" > /dev/tty && read -sdR CURPOS
    stty $OLDSTTY
    [[ ${CURPOS##*;} -gt 1 ]] && echo "${color_error}↵${color_error_off}"

    # restore real stdout and close duplicate
    exec 0<&100 100<&-
}
PROMPT_COMMAND='prompt-command'

I chose to use the second variant even though it requires assumption that file descriptor number 100 is unused. This should generally be faster than forking a process.