Wednesday, October 6, 2010

Terminal Escape Sequences

I went across this topic some time ago. The point is that text terminal can (and usually does) support / implement several so called escape sequences. They are called escape because they start with the escape character. There is bunch of escape sequences (standard and non-standard too) for all kind of fancy stuff you can do on the text terminal (moving the cursor, deleting, inserting, changing text attribute like color and so on).

There exist several standards about escape sequences and a lot of terminals historically with different support. Terminal for the application is just special character device connected to so called 'stdin', 'stdout' and 'stderror' of running application. To find out the terminal device connected to 'stdin' use 'tty' command.

$ tty
/dev/pts/0

Terminal device is just a black box for the running application. When application wants to display something it writes to the 'stdout' so terminal device in fact. In case application reads from 'stdin' it reads from terminal device so in fact it reads user input.

Terminal device returned by 'tty' can be:

  • real terminal device (eg. /dev/tty1)

    Example of this can be so called virtual terminal / console. Terminal is a combination of capability to display the output (screen, or part of the screen = window in case of terminal emulator) and to provide some character input stream. In this sense linux has several hardware virtual terminals from which one can be connected to the physical display and input devices at a time. For switching there is a VT (virtual terminal) switch command 'chvt' (change virtual terminal). By issuing '$ chvt 1' you will be switched to virtual terminal 1 the same way as you would press Ctrl-Alt-F1 (note that while in X server session there are elevated permissions needed).

  • 'pseudo terminal slave' terminal device (eg. /dev/pts/0)

    Pseudo terminals is quite clever abstraction how to fool any application about it's input and output. Let's take an example of so called 'terminal emulator' like 'xterm'. Terminal emulator is just regular X application. It will probably create some window where the application can listen for all events happening on configured input devices. Afterwards it will create so called 'pseudo terminal master and slave' device pair by opening file '/dev/ptmx'. It will receive so called 'master' file descriptor and by further API calls will open the 'slave' counterpart (e.g. /dev/pts/0). Then it starts let say the bash with the 'stdin' and 'stdout' connected to this slave device. Anything the bash will output to the 'stdout', xterm will read from 'master' device and anything xterm will write to 'master' device will be read by the bash on the 'stdin'. There are lot of programs which are using this feature ... to name a few 'sshd', 'expect' (a tool for automating interactive applications) or terminal multiplexers like 'screen' or 'tmux'.

The goal of application developers is to make application work on all terminals in ideal case. But from terminal to terminal there are differences. This is the purpose of the 'termcap' (terminal capability) or newer 'terminfo' database where terminals are indexed by name with specifying capabilities they can perform. The name of the terminal is determined by the environment variable 'TERM'. You can query terminfo database by the 'tput' tool (eg. '$ tput clear' will clear screen and home cursor same as a 'clear' command). Other useful tool is 'infocmp' which will in most simple way without any parameters print out all capabilities of current terminal. By executing '$ infocmp rxvt' it will do the same but specifically for 'rxvt' terminal. To ease application development there is 'curses' / 'ncurses' library which takes all escape sequences burden away.

To tryout some escape sequences we need to write to the terminal which means write to the 'stdout'. Commands like 'echo -ne' or 'printf' will do the job. Keep in mind that your shell can trick you. It is common that after you press 'return' and the command is executed the shell will execute some logic around displaying the new empty prompt which can negate or at least interfere your escape sequence. In bash there is a 'PROMPT_COMMAND' variable holding command evaluated prior displaying prompt, and then 'PS1' variable holding prompt itself which can contain escape sequences too.

To send just dedicated output to the terminal open 2 terminals. Assume the first one is connected to '/dev/pts/0' and the second is connected to '/dev/pts/1'. Then lets say in first terminal we can echo something targeting second terminal.

$ printf '\e[G' > /dev/pts/1

I will name below few interesting sequences I used.

To change the color of the text cursor use below sequence. I've tested it and it works in terminal emulators 'xterm', 'urxvt' and 'gnome-terminal'. However it is not working inside my favorite terminal multiplexers 'screen' neither in 'tmux' which is still bugging me. I will have to do some research about it.

cursor_color() {
    printf '\e]12;%s\e\' "$1"
}
cursor_color '#C0C0C0'
cursor_color 'sienna2'

If you want to change text cursor color for the 'linux' terminal which corresponds to the virtual terminal / console you will have to use something different.

cursor_color_console() {
    printf '\e[?16;0;%sc' "$1"
}
cursor_color_console 112

This is example how it would be possible to augment the prompt. This sequence is composed from several sequences.

  • '\e[s' - will save current cursor position
  • '\e[G' - will move the cursor to the beginning of the current line
  • '\e[u' - will restore saved cursor position
prompt() {
    printf '\e[s\e[G%s\e[u' "$1"
}

This will set so called window 'hardstatus' (in the 'screen' terminology). It belongs to the group of OSC escape sequences (OSC = Operating System Command) and it should change icon name and window title to the string enclosed between the start of sequence and the end designated by the '\e\' which is ST (String Terminator) but also the BEL character (007) should do the job.

hstatus() {
    printf '\e]0;%s\e\' "$1"
}

Some eye candy sequences dealing with the text attributes. Below is the example of colored primary prompt and colorizing setup for 'less'.

excerpt from ~/.bashrc
PS1='\[\e[1;37m\]\u@\h:\!:\#:\w\$\[\e[0m\] '

export LESS="-aMRS#3"
export LESS_TERMCAP_mb=$(printf "\e[1;37m")      # begin blinking
export LESS_TERMCAP_md=$(printf "\e[1;37m")      # begin bold
export LESS_TERMCAP_me=$(printf "\e[0m")         # end mode
export LESS_TERMCAP_se=$(printf "\e[0m")         # end standout-mode
export LESS_TERMCAP_so=$(printf "\e[1;41;97m")   # begin standout-mode
export LESS_TERMCAP_ue=$(printf "\e[0m")         # end underline
export LESS_TERMCAP_us=$(printf "\e[0;4;93m")    # begin underline

Below are couple of links to resources I've found useful when dealing with escape sequences.

# $ man console_codes
# http://en.wikipedia.org/wiki/ANSI_escape_code
# http://rtfm.etla.org/xterm/ctlseq.html
# http://linuxgazette.net/137/anonymous.html
# http://wiki.archlinux.org/index.php/Color_Bash_Prompt
# http://www.comptechdoc.org/os/linux/howlinuxworks/linux_hlvt100.html