\Escape: Window Title

31.10.2008 21:47 | blackhole_matej

Minule sme si ukázali, ako pomocou escape sekvencií ofarbiť terminál. Pre lepšiu navigáciu medzi oknami nám môžu poslúžiť nadpisy (window title). Tie sa vedia zobraziť v titulku grafického okna, ak také máme, prípadne v status riadku screenu. Nadpisy sa, podobne ako farby, tiež dajú nastaviť pomocou escape sekvencií.

Xterm ----
Titulok xterm-kompatibilného terminálu je možné nastaviť pomocou escape sekvencie esc]0;text^G (kde ^G je znak BEL, ASCII 7, resp. '\a'). Konkrétnejšie na mieste nuly môžu byť:

  • 2 – nastaví titulok okna
  • 1 – nastaví titulok ikony (napr. Eterm)
  • 0 – kombinácia 1+2

V praxi je najjednoduchšie veriť nule. Podporujú ju určite rxvt, Konsole, aj PuTTY. Či to funguje, overíme jednoducho:

echo -en "\033]0;test\007"

Screen ----
Veľa ľudí, ktorí pracujú so vzdialeným textovým terminálom, iste používa screen. Umožňuje mať spustených viacero okien, ktoré bežia na vzdialenom stroji, čím sa vyhneme zaveseniu programov pri strate spojenia, alebo napríklad aj ušetríme prenosové pásmo počas kompilácie niečoho (prepnutím sa dočasne do menej "ukecaného" okna). Pre tých, čo sa so screenom ešte nestretli, jednoduchý začiatok získajú prečítaním si prvých pár odstavcov z Gentoo Linux Wiki (žiaľ v tejto chvíli nefunkčné), alebo man screen(1).

Titulok terminálu, v ktorom screen beží, je nastavovaný priamo screenom, ovplyvniť ho vieme pomocou jeho príkazu hardstatus string. Aby vedel screen povedať terminálu, že hardstatus má vypísať do titulku okna, potrebuje na to správne termcapinfo. Dobré sú napríklad takéto riadky v .screenrc alebo /etc/screenrc pre screen verzie 3.x, resp. 4.x:

# screen v3:
hardstatus string 'myhost: %n* %t -- %W'
hardstatus lastline
termcapinfo xterm 'hs:ts=\E]2;:fs=\007:ds=\E]2;screen\007'
# screen v4:
hardstatus string '%H: %-w%{= BW}%50>%n%f* %t%{-}%+w%<'
hardstatus lastline
termcapinfo xterm 'hs:ts=\E]2;:fs=\007:ds=\E]2;screen\007'
Samotný nadpis okna vo vnútri screenu môžeme nastaviť niekoľkými spôsobmi:

  • ctrl-a : title názov
  • ctrl-a A názov
  • spustením programu screen -t nazov cislo_okna prikaz
  • automaticky na názov spúšťaného príkazu pomocou shelltitle '$ |sh'
  • vypísaním sekvencie ESCknázovESC\ (ESC je znak escape, k je obyčajné písmeno)

Posledný spôsob (vypísaním escape sekvencie) nám umožní automatizovať, teda napríklad nastaviť titulok okna podľa názvu aktuálneho adresára zakomponovaním do promptu apod. Automatické nastavenie pomocou príkazu screenu shelltitle sa zdá byť na prvý pohľad dobré, avšak má vážny nedostatok: aby fungovalo, musí byť vypísaná sekvencia ESCkESC\, a teda nemôžeme naraz vypísať názov aktuálneho adresára a vzápätí názov spúšťaného programu.

Manuálny title ----
Predchádzajúce poznatky zhrnieme v malom skripte, ktorý nastaví názov okna pre xterm alebo screen (rozpozná ich podľa premennej $TERM). Pracovne je nazvaný my-title:

#!/usr/bin/env bash
#:~/bin/my-title
#
# set window title (in xterm or screen terminal)
if [ "$1" = "" ]; then
        t="`dirs`"
else
        t="$1"
fi
case $TERM in
        xterm*)
                echo -en "\033];`whoami`@`hostname -s`: $t\007"
                ;;
        screen*)
                echo -en "\033k"
                # 1=login shell, 2=this script run (not sourced) from it
                if [ "$SHLVL" -le 2 ]; then
                        # if the shell is a login shell, screen must be
                        # running on a different machine - include hostname:
                        echo -en "`hostname -s`:"
                fi
                echo -en "$t\033\134"
                ;;
esac
Ak máme na stroji roota, umiestnime ho napríklad do /usr/local/bin/, inak do ~/bin (ktorý máme, samozrejme, v $PATH). Pri spustení s parametrom nastaví titulok podľa neho, inak použije aktuálny adresár.

Automatický title ----
Vyššie už bola spomenutá možnosť nastavovať titulok automaticky podľa názvu aktuálneho adresára. Trik spočíva v zakomponovaní escape sekvencie do promptu. Treba však rozlíšiť, či budeme vypisovať sekvenciu pre xterm, alebo pre screen, a čo vlastne chceme nastaviť ako titulok.
O niečo zložitejšie je dostať názov aktuálne bežiaceho programu do titulku okna. V zásade je niekoľko prístupov:

  • alias – vytvoriť si alias pre najčastejšie spúšťané programy, napríklad:
    alias p 'my-title PINE; pine -d 0 -p ~/mail/.pinerc'
  • v screene pomocou shelltitle – potom však nie je vidno názov aktuálneho adresára (ako už bolo spomenuté hore)
  • mágiou v shelli – vypísaním escape sekvencie s názvom spúšťaného programu tesne pred jeho spustením.

Pri poslednom spôsobe nemôžeme sekvenciu vypísať len tak priamo, pretože by pri presmerovaných výstupoch (napríklad aj cat | grep | less) došlo k výpisu sekvencie do vnútra rúry a tým by sme mohli v horšom prípade aj niečo pokaziť. Preto treba sekvenciu doručiť nie na stdout, ale na zariadenie používateľovho terminálu. V rôznych shelloch sa to robí všelijako.

tcsh ----
Pre výpis aktuálneho adresára do titulku xtermu stačí zakomponovať do set prompt= napríklad reťazec "%{\033]0;%n@%m: %~\007%}". Je medzi znakmi %{ a %}, aby shell vedel, že ich výpis neovplyvní dĺžku príkazového riadku. V titulku potom budeme mať zobrazený username@hostname:currentpath.
Vypísanie spúšťaného programu do titulku môžeme dosiahnuť pomocou aliasu postcmd, ktorý sa vykonáva vždy tesne pred spustením zadaného príkazu. Názov programu získame z histórie pomocou '!'. Vypisovať nesmieme na stdout, ale do /dev/$tty. Celý alias môže potom vyzerať približne takto:

alias postcmd "echo -n "'"'"`cat ~/bin/esc`]0;`whoami`@`hostname -s`: \!#`cat ~/bin/beep`"'"'" > /dev/$tty"
Interný príkaz echo v tcsh nepozná parameter -e, teda nevieme ním vypisovať špeciálne znaky. Preto si ich vyrobíme inak – buď použijeme /bin/echo, ak to podporuje, alebo bash. Keďže quotovanie v tcsh je hrôza, môžeme mať špeciálne znaky dopredu vyrobené a uložené v súboroch esc a beep:
# making special chars in tcsh:
bash -c 'echo -en "\033"' > ~/bin/esc
bash -c 'echo -en "\007"' > ~/bin/beep

bash ----
Aktuálny adresár dostaneme do titulku v bashi veľmi podobne, escape sekvenciou vo vnútri premennej $PS1, napríklad reťazcom "\[\033]0;\u@\h: \w\007\]". Hranaté zátvorky slúžia na rovnaký účel, ako zložené pre tcsh.
S výpisom spusteného programu do titulku je to v Bashi horšie. Nemá ekvivalent 'postcmd' v tcsh, ani 'preexec' v zsh. David Pashley však vykutral, že by sa na tento účel dal použiť DEBUG trap. Ten sa vykoná pre každý jednoduchý príkaz. Názov toho príkazu vidno v premennej $BASH_COMMAND. Čo je však naozaj problém, je zistiť názov aktuálneho terminálového zariadenia. (Priamo to ide len cez prompt s '\l', cez ktorý sa však trap nastaviť nedá – DEBUG trapy sa smerom nahor nededia [smerom k deťom len pri zapnutom set -o functrace].) Preto na zistenie názvu tty zneužijeme tcsh, skrátene:

trap "echo -en '\e]0;`whoami`@`hostname -s`: \$BASH_COMMAND\007' > /dev/`tcsh -c 'echo $tty'`" DEBUG
Nevýhodou je, že to bude fungovať len v bash verzii >=3.1, v nižších sa premmenná $BASH_COMMAND totiž mení aj vo vnútri trapu, resp. je prázdna. Oproti tcsh vieme zobraziť len jednotlivé príkazy, a nie celý zadaný riadok, s tým sa však dá žiť, dokonca niekedy je to tak prehľadnejšie.

Ultimátna kombinácia ----
Skombinovaním farieb a titulkov (pre prípady xtermu, screenu lokálne, vzdialeného stroja v screene) v univerzálne použiteľnom bash/C-shell skripte získame celkom pekný shell. Skripty sú trochu dlhšie, na stiahnutie:

  • bash farby + titulky – umiestnime napr. do /etc/profile.d/, alebo niekam do homediru (~/bin). Pozor, keďže bash nemá jeden konzistentný rc súbor, ktorý by púšťal zakaždým pre login aj non-login shelly, potrebujeme to spúšťať z .bashrc:
    # part of .bashrc
    . ~/bin/prompt-colors+title.bash
    a tiež z .bash_profile – ja v ňom štandardne používam:
    # part of .bash_profile
    . ~/.bashrc
  • tcsh farby + titulky – umiestnime napr. do ~/bin a voláme z .cshrc:
    # part of .cshrc
    source ~/bin/prompt-colors+title.csh

Keď skript dáme aj na ostatné stroje, kam sa prihlasujeme, výsledok môže vyzerať takto nejako pre screen:

a pre xterm:

Záver ----
Zobrazenie cesty aktuálneho adresára v titulku xtermu a screenu používam už niekoľko rokov bez žiadnych problémov. Nedokázal som vtedy vyriešiť vypisovanie bežiaceho programu, zrejme preto, že screen to nevedel poriadne, a bash vôbec :) – túto vymoženosť som začal používať až pred pár dňami. Zatiaľ som nenarazil na nejaké veľké neželané vedľajšie efekty, snáď okrem prebliknutia titulku, keď sa vykonávajú krátke príkazy. Zrejme tých pár znakov navyše zhorší zahltenie linky, ale nie badateľne a nastavený DEBUG trap možno trochu znižuje performance – nemám ho však s -o functrace, platí teda len pre aktuálny shell a nie pre skripty.

Zdroje ----

Doplnenie (edit):
Medzi neželané vedľajšie efekty patrí skomolenie názvu jobu (iba v bashi, kvôli trapu). Nedá sa potom ľahko orientovať medzi zastavenými jobmi, pretože sa všetky volajú rovnako:

^ZYou have suspended the program.  Type 'fg' to return
[2]+  Stopped    echo -en "\ek$BASH_COMMAND\e\134" >/dev/pts/6
matej@aelia:~$ jobs
[1]-  Stopped    echo -en "\ek$BASH_COMMAND\e\134" >/dev/pts/6
[2]+  Stopped    echo -en "\ek$BASH_COMMAND\e\134" >/dev/pts/6
S týmto sa dá žiť, najmä ak človek používa viac programy na popredí v screene ako správu jobov na pozadí.

    • Re: \Escape: Window Title 31.10.2008 | 22:02
      Avatar blackhole_matej   Používateľ

      Zabudol som dodať, že samozrejme farby v "ultimátnom" skripte si každý môže nastaviť podľa chuti, nahradením sekvencií "\033[*m" v riadku, kde sa nastavuje premenná prompt resp. PS1.

    • Re: \Escape: Window Title 01.11.2008 | 10:39
      Avatar blackhole_tommyhot   Používateľ

      Velmi pekny clanok, ja len doplnim, ze je prakticke (a hlavne je to prehladne) ulozit escape sekvencie pre jednotlive farby do premennych (napr do ~/.bashrc) a potom pracovat prave s tymi premennymi.
      ----------
      tommyhot@hackingmachine:~$ microsoft &> /dev/null

    • Re: \Escape: Window Title 04.11.2008 | 13:30
      Avatar vid   Používateľ

      Pekny clanok, dufam ze seriu ukoncis nejakym uvodom k ncurses, pokial viem v praxi sa pouziva ta, aby clovek nemusel specialne kodovat pre kazdy terminal.