Terminál

Do teď jste se naučili pouze vypisovat text řádek po řádku, písmenko za písmenkem. Víc toho jazyk C ani nenabízí. Terminály ovšem umožňují mnohem více. Můžete psát barevně, můžete posouvat kurzor různě po obrazovce (tedy vlastně chci říct po terminálu), můžete načítat znaky od uživatele, aniž by se mu na terminálu zobrazovali (třeba při zadávání hesla) atp. A o tom bude tato kapitola.

Terminál

Konzole v prostředí GNOME

Konzole v prostředí GNOME

Terminál (nebo se tomu také říká konzole) je jakékoliv (vstupně)výstupní zařízení. Nejčastěji se pod pojmem terminál rozumí obrazovka, nebo xterm (konzole). Xterm je vlastně emulátor terminálu. Terminálem ale může být i tiskárna.

V ranných dobách počítačového věku, kdy muži ještě byli muži, existovalo spoustu výrobců terminálů. A každý používal různé řídící instrukce. Různé sekvence znaků ovládali kurzor (posouvali ho po obrazovce), měnili barvy popředí a pozadí písma, měnili obnovovací frekvenci atd. Aby se z toho programátor nezbláznil, vytvořila se knihovna termios, která poskytuje funkce pro ovládání terminálu (vychází ze starší knihovny termio ze staršího unixu System V). Některé z funkcí této knihovny představím v této kapitole.

Ovládání terminálu v Bashi

Než se pustím do ukázek v jazyku C, dovolím si malou odbočku k bashi. Pokud se omezíte na to, že vám stačí, aby váš program fungoval jen v linuxovém emulátoru (konzole, xterm), můžete používat jako řídící znaky escape sekvence.

Popis toho, jak se používají escape sekvence najdete na stránce věnované příkazu echo. Dočtete se tam, že escape sekvence je vše, co začíná zpětným lomítkem. Příkaz echo pak znaky za zpětným lomítkem interpretuje nějakým speciálním způsobem. To už znáte i z C, '\n' je sekvence označující nový řádek…

Znak escape se označuje escape sekvencí \033, což je osmičkový zápis čísla 27. 27 je ascii kód escape znaku. A escape znak je právě řídící znak pro linuxový emulátor terminálu.

Je to trošku nedokonalé řešení. Problém je v tom, že když stisknete escape, terminál se musí rozhodnout, jestli má tento znak předat programu, který běží (například editoru vim), nebo jestli tímto znakem začíná escape sekvence, kterou bude nějak speciálně interpretovat (třeba změnou barvy). Terminál se rozhoduje podle toho, jak rychle přijde následující znak za esacpe. Pokud hodně rychle, pak to zřejmě nemohl stihnout člověk namačkat na klávesnici a je to escape znak. Problém ale je, pokud pracujete po síti. Může se totiž stát, že se escape sekvence rozdělí do několika packetů a terminál to zmate :-(. Řešení neexistuje, musíte jen doufat, že se to nestane …

Ale teď zpět k veselým věcem :-) Na základě toho, co jste si mohli přečíst v kapitole o příkazu echo jsem vytvořil skript v bashi, který vám bude zobrazovat v emulátoru terminálu datum v pravém horním rohu.

  1. #!/bin/bash
  2. # bash-datum.sh
  3. #
  4.  
  5. if [ "$TERM" != "xterm" -a "$TERM" != "linux" ]
  6. then
  7.         exit 1
  8. fi
  9.  
  10. ESC="\033["                     # ulozeni escape sekvence do promenne ESC
  11. COLOR="${ESC}0;34;47m"      # esc. sekv. pro zmenu barvy
  12. END_COLOR="${ESC}0m"        # obnoveni defaultni barvy
  13. SAVE_CURSOR="${ESC}s"       # ulozeni aktualni pozice kurzoru
  14. RESTORE_CURSOR="${ESC}u"    # nastaveni pozice kurozru na tu ulozenou
  15. DATE=`date`                     # ziskani data
  16.  
  17. size=${#DATE}               # delka retezce v promenne DATE
  18. col=`tput cols`                 # pocet sloupcu terminalu
  19. col=`expr $col - $size`         # pocet sloupcu terminalu - delka DATE
  20. line=0
  21.  
  22. GO_TO_POSITION="${ESC}${line};${col}f" # presun na pozici
  23.  
  24. echo -en "${SAVE_CURSOR}${GO_TO_POSITION}${COLOR}${DATE}${END_COLOR}${RESTORE_CURSOR}"

Pokud spustíte skript v něčem jiném než xtermu nebo linuxové konzoli (do které se přepnete pomocí CTLR+ALT+F1), pak se skript ukončí. Toto jsou jediné dva terminály, u kterých vím, že tam řídící znaky escape sekvencí fungují. (Fungují ale určitě i na jiných terminálech, to už si musíte vyzkoušet sami.) Pokud jste v programování bashe noví, dejte si pozor na mezery kolem hranatých závorek u if příkazu, jsou povinné. Uvozovky kolem "$TERM" taky, protože jinak v případě, že nebude $TERM definován, dojde k chybě.

Výsledek můžete vidět na obrázku.

bash-datum.sh

bash-datum.sh

Tento skript vypíše datum na obrazovku jen jednou. Pokud ho tam budete chtít mít trvale, můžete spustit v konzoli nekonečnou smyčku na pozadí:

$ while(./bash-datum.sh); do sleep 1; done &

Bez sleep 1 by vám tento cyklus strašně vytěžoval procesor.

Už vidím vaše nadšení, jak se chystáte naučit programovat v bashi, aby jste zjistili, co všechno ještě můžete udělat, jak si skript upravit a co všechno si můžete na konzoli zobrazit. Možná už jste si výše zmíněný cyklus zapsali do svého souboru ~/.bashrc, aby se spustil automaticky při každém spuštění bashe. To ale raději nedělejte!

Bash se spouští při mnoha příležitostech, o kterých ani netušíte. A ne vždy je přitom připojen k terminálu. Bohužel, výše napsaný skript v takovém případě způsobuje chybu. Podmínka, která skript ukončí, pokud není $TERM nastaven na xterm nebo linux sice většinu problémů řeší, ale třeba při startu KDE se spouští bash s $TERM nastaveným na xterm a z nějakého důvodu mi bash-skript.sh start KDE zablokoval. Takže automatické spuštění tohoto skriptu v xtermu silně nedoporučuji! S konzolou linux jsem zatím problémy neměl, takže můžete zkusit přidat cyklus s podmínkou,že se má spustit jen pokud je $TERM roven linux.

Pokud se někdy něco na terminálu špatně překreslí, zkuste CTRL+L pro jeho vyčištění.

Příkaz stty

Příkaz stty se používá k zobrazení a nastavení vlastností terminálu. Volba -a vypíše aktuální nastavení v pro člověka čitelném formátu.

$ stty -a
speed 38400 baud; rows 20; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?;
swtch = M-^?; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W;
lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts
-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc ixany imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke

Z výstupu se dočtete třeba to, že obnovovací frekvence je 38400 baudů. Což je pro emulátor terminálu vcelku irelevantní informace. Počet řádků je 20, sloupců 80 (protože sem si terminál zvětšil na tuto velikost), klávesová zkratka pro přerušení (SIGINT) je CTRL+c atd. Na předposledním řádku vidíte, že je zapnuté echo. V tomto módu se vypisuje na obrazovku vše, co uživatel píše. Echo můžete vypnout takto:

$ stty -echo

Od této chvíle neuvidíte, co na terminál píšete. To se hodí, pokud nemáte rádi, když vám někdo kouká přes rameno, co děláte :-). Ale spíš se to používá v bash skriptech při zadávání hesla.

Echo režim můžete nastavit zpět příkazem stty echo.

Další užitečná věc, kterou stty umí, je dát terminál „do pořádku“. Pokud si terminál rozhodíte tak, že ani nemůžete zadat znak nového řádku pomocí enteru, můžete zkusit klávesovou kombinaci CTRL+j.

stty sane # obnovi nastaveni terminalu - Ctrl-j (znak noveho radku)

Kanonický a nekanonický režim

Kanonický, nebo též normální režim je takový, ve kterém odesíláte příkaz programu až odesláním nového řádku (klávesou Enter).

Nekanonický režim odesílá programu každý stisknutý znak. V tomto režimu se musí program postarat o takové věci, jako je správná interpretace stisknutí klávesy na smazání znaku (backspace) atp. Nekanonický režim se může hodit při psaní her, kdy chcete ihned reagovat na stisk klávesy.

V nekanonickém režimu jsou důležité dvě hodnoty: min a time. Hodnota min udává, kolik je potřeba minimálně stisknout kláves, aby se odeslali programu. Hodnota time udává časový interval, po kterém se vstup odešle do programu, ikdyž nebylo stisknuto min kláves. Můžou nastat tyto 4 kombinace min a time:

MIN a TIME - jen pro nekanonický režim
mintimeJak se bude chovat čtení
00pokus o čtení ihned skončí, nic se nenačte. Tuto kombinaci raději nezkoušejte :-)
0>0čtení skončí po time desetin sekundy (funkce read() vrací 0), nebo při vstupu ukončeném enterem (read() vrací počet načtených znaků)
>00při čtení se čeká, dokud se nenačte alespoň min znaků
>0>0po obdržení prvního znaku se spustí mezičasovač time (běžící se restartuje). Čtení končí po načtení min znaků, nebo když vyprší časový limit time. Může se to hodit pro rozlišování mezi escape sekvencí a stiskem escape klávesy. Při práci po síti je to ale bohužel nespolehlivé.

Nekanonický režim můžete nastavit pomocí stty takto:

$ stty -icanon min 1 time 0

Praktické využití ukáži v této kapitole o kousek níže.

A to je asi tak k ovládání terminálu z bashe všechno. Podrobně vám vysvětlovat skriptování v bashi nebudu, na to si přečtěte tutoriál o příkazech v Linuxu.

Ovládání terminálu v C

Předchozí výlet do bashe tu není náhodou :-). Escape sekvence můžete používat i pomocí standardních funkcí na tisk znaků jazyka C.

Následující příkaz tiskne pomocí escape sekvencí text v různých barvách.

  1. /*------------------------------------------------*/
  2. /* 50terminal/colors.c                            */
  3. #include <stdio.h>
  4.  
  5. #define ESC "\033["
  6.  
  7. int main(void)
  8. {
  9.     char *end = ESC "0m";
  10.     int i;
  11.     for (i = 30; i <= 37; i++) {
  12.         printf(ESC "0;%im%s 0;%i%s\n", i, "normal", i, end);
  13.         printf(ESC "1;%im%s 1;%i%s\n", i, "bold  ", i, end);
  14.     }
  15.     printf("Konec.\n");
  16.  
  17.     return 0;
  18. }
  19.  
  20. /*------------------------------------------------*/

Výstup si můžete prohlédnout na obrázku:

Výstup z programu colors

Výstup z programu colors

Všiměte si, že na prvním řádku je černý text na černém pozadí, takže není nic vidět. Uživatel si může nastavit různé barevné schémata, takže je nutné, aby jste při změně barvy změnili i pozadí textu, pokud chcete mít jistotu, že bude text čitelný.

Můžete zkoušet i další řídíci sekvence, jak jsou popsány u příkazu echo. Vyzkoušejte si, co který terminál podporuje a co ne. Třeba takové blikání podporuje konzole z KDE, ale jenom když máte v nastavení zaškrtnuto, že je povolené! Konzole z KDE také jako jeden z mála terminálů podporuje italic písmo.

Nemusím asi moc zdůrazňovat, že používání těchto escape sekvencí je málo přenositelné. Ve Windows si s tím neškrtnete, v Linuxu ne každý terminál podporuje každou vymoženost (jako třeba ten italic text nebo blikání). A v některých unixech třeba nebude podporavaná ani ta změna barev.

Přiřazení IO k terminálu

Uživatel může standardní vstup, výstup i chybový výstup přesměrovat. Někdy se může hodit zjistit, jestli je některý souborový proud přesměrován, nebo je přiřazen k terminálu. K tomu slouží funkce isatty() z knihovny <unistd.h>.

int isatty(int fd);    

Funkce vrací 1, pokud je deskriptor souboru fd přiřazen k terminálu, 0 pokud není.

Co když je standardní výstup přesměrován a vy stejně chcete něco napsat na obrazovku (terminál)? Obvykle se pro to využívá standardní chybový výstup stderr, ale i ten může být přesměrován. V Linuxu ale existuje speciální zařízeni /dev/tty, které je vždy přiřazeno aktuálnímu terminálu, ve kterém program běží.

Dle normy POSIX je toto zařízení přistupné pro čtení i zápis, stejně jako obyčejný soubor. Výstup pak jde vždy na aktuální terminál spuštěného programu a vstup se čte z tohoto terminálu. Díky tomu, že je /dev/tty součástí normy POSIX, se můžete spolehnout na to, že jej najdete v každém dobrém Linuxu i Unixu.

A protože jeden příklad vydá za tisíc slov:

  1. /*------------------------------------------------*/
  2. /* 50terminal/isatty.c                            */
  3. #include <string.h>
  4. #include <stdio.h>
  5. #include <unistd.h>
  6. #include <stdlib.h>
  7.  
  8. int main(void)
  9. {
  10.     FILE *input, *output;
  11.     char tmp[80];
  12.  
  13.     input = fopen("/dev/tty", "r");
  14.     output = fopen("/dev/tty", "w");
  15.  
  16.     if (!input || !output) {
  17.         fprintf(stderr,
  18.             "Nelze otevřít /dev/tty pro čtení nebo zápis.\n");
  19.         return EXIT_FAILURE;
  20.     }
  21.  
  22.     if (!isatty(fileno(stdin))) {
  23.         fprintf(output,
  24.             "stdin neni připojeno k terminálu, končím!\n");
  25.         return EXIT_FAILURE;
  26.     }
  27.  
  28.     if (!isatty(fileno(stdout))) {
  29.         fprintf(output,
  30.             "stdout neni připojeno k terminálu, končím!\n");
  31.         return EXIT_FAILURE;
  32.     }
  33.  
  34.     if (!isatty(fileno(stderr))) {
  35.         fprintf(output,
  36.             "stderr neni připojeno k terminálu, končím!\n");
  37.         return EXIT_FAILURE;
  38.     }
  39.  
  40.     fprintf(output, "OK, můžeme pracovat :-)\n");
  41.     fprintf(stdout, "Něco mi řekni: ");
  42.     fflush(stdout);
  43.     fgets(tmp, sizeof(tmp), input);
  44.     tmp[strlen(tmp) - 1] = '\0';    /* odstranim znak konce radku '\n' */
  45.     fprintf(output, "\"%s\" říkáš?\nKončím!\n", tmp);
  46.  
  47.     return EXIT_FAILURE;
  48. }
  49.  
  50. /*------------------------------------------------*/

V příkladu by mělo být asi všechno jasné. Pomocí funkce fileno() získám deskriptor souboru reprzentující příslušný souborový proud a pak jej otestuji funkcí isatty().

To, že na řádkách 40 a 41 pro výstup používám output a pak stdout přičtěte na vrub mé rozmařilosti, v tomto příkladu je to úplně jedno, protože na těchto řádkách už je jasné, že je stdout přiřazen k terminálu, a tak půjde výstup na obrazovku.

Příklad použití programu isatty:

$ ./isatty > /tmp/xxx
stdout neni připojeno k terminálu, končím!
$ ./isatty 2> /tmp/xxx
stderr neni připojeno k terminálu, končím!
$ ./isatty < /tmp/xxx
stdin neni připojeno k terminálu, končím!
$ ./isatty
OK, můžeme pracovat :-)
co mi řekni: Mám tě rád!
"Mám tě rád!" říkáš?
Končím!

Tuto kontrolu můžete použít třeba k tomu, abyste uživatele přinutili zadávat heslo ručně, aby si ho neukládal někam do skritpu. Taky to znepříjemní práci případným crackerům vašeho programu, protože si nebudou moci generovat hesla v nějakém programu a pak je přesměrovávat do vašeho programu. Ale pozor, každý dobrý hacker dokáže váš program obelstít tak, aby kontrola pomocí isatty() byla obalamucena. Takže na to zase tak moc nespoléhejte ;-).

Zkouška kanonického režimu

Z programem isatty si můžete vyzkoušet, jak se bude chovat v nekanonickém režimu. Tak schválně:

$ stty -icanon min 1 time 0
$ ./isatty
OK, můžeme pracovat :-)
co mi řekni: Ahoj
"Ahoj" říkáš?
Končím!

Hmm, nic zvláštního se nestalo. Funkce fgets() sice dostává znak po znaku, ale stejně neskončí, dokud nenarazí na konec řádku, nebo nenačte max. počet znaků. Co se ale asi tak stane, když budu chtít něco z toho, co píšu, smazat?

$ stty -icanon min 1 time 0
$ ./isatty
OK, můžeme pracovat :-)
co mi řekni: Ahoj^?^?^?
"Ahoj###" říkáš?
Končím!

No vida, přeci jen nějaká změna. Místo toho, aby klávesa backspace smazala znak, vypíše se stříška a otazník. A v programu se uloží kód backspace, jako by se jednalo o normální znak. Při výpisu se místo toho zobrazí nějaký nesmysly, já sem tu pro ilustraci použil znak #. (Ano, zmáčkl jsem backspace 3x.)

A co takle nastavit min a time na 0?

$ stty -icanon min 0 time 0
$ ./isatty
OK, můžeme pracovat :-)
co mi řekni: "" říkáš?
Končím!

Funkce fgets() tentokrát hned dostala info o konci souboru, takže na ni nečekala a vrátila se bez načtení čehokoliv. Vlatně se divím, že program neskončil neoprávněným přistupem do paměti, protože řádka v kódu tmp[strlen(tmp) - 1] = '\0'; předpokládá, že byl načten alespoň jeden znak (znak nového řádku), což nebyl. To je tak, když člověk nekontroluje návratové hodnoty, že :-).

A nezapomeňte si na konec po sobě uklidit.

$ stty sane

Knihovna termios.h

Tak jo, konečně se dostává řada na knihvnu termios.h.

Informace o terminálu se získávají a nastavují pomocí struktury struct termios.

struct termios {
    tcflag_t c_iflag;
    tcflag_t c_oflag;
    tcflag_t c_cflag;
    tcflag_t c_lflag;
    cc_t c_cc[];
}

Položka c_iflag slouží k nastavení příznaků pro vstupní režim (input), c_oflag pro výstupní režim a c_cflag řídí hardwarové charakteristiky terminálu. Tyto příznaky jsou více méně nezajímavé (zvláště c_cflag je pro emulátory terminálů k ničemu).

Zajímavá je položka c_lflag (lokální režim). Ta slouží k nastavování různých charakteristik terminálu, jako je echo nebo kanonický režim.

Pole c_cc se používá k nastavování různých speciálních znaků, jako jsou např. min a max hodnoty pro nekanonický režim. Délka pole je „neznámá“. K prvkům se přistupuje pomocí definovaných maker.

Změna vlastností terminálu probýhá obvykle tak, že se nejdřív načte současné nastavení terminálu do struktury termios a uloží se někam bokem, zkopíruje se, kopie se upraví a použije se pro nové nastavení terminálu. Při skončení programu se nastaví původní struktura termios.

K získání aktuálního nastavení se používá funkce tcgetattr() a k nastavení funkce tcsetattr().

int tcgetattr(int fd, struct termios *termios_p);
int tcsetattr(int fd, int optional_actions, const struct termios *termios_p);

První argument je dekriptor souborového proudu, pro který se příznaky získávají nebo nastavují. Poslední je odkaz na strukturu termios. Funkce tcsetattr() má ještě jeden parametr, optional_actions, který může nabývat jedné z těchto hodnot:

optional_actionVýznam
TSCANOWzměny se nastaví ihned
TCSADRAINzměny se nastaví po dokončení aktuálního vstupu
TCSAFLUSHzruší se všechen doposud zadaný vstup a změny se nastaví ihned
TCSAFLUSH se hodí třeba při vypínání echa před zadáváním hesla. Ať si uživatelé zvyknou nezačít psát heslo dřív, než je echo vypnuté.

Pořád tu mluvím o nějakých příznacích, ale ještě jsem neřekl, jaké to vlastně jsou. Zmíním jenom tři nejzajímavější, pro položku c_lflag.

ECHO
Zapíná echo režim.
ISIG
Povoluje generování signálů pro kláv. zkratky CTRL+c atp.
ICANON
Zapíná kanonický režim.

Tyto tři příznaky v příkladu vypnu. Tj. vypnu echo, vypnu signály a vypnu kanonický režim.

V poli c_cc nastavím hodnoty min a time pro nekanonický režim na 1 a 0.

Teď už zbývá vysvětlit jen použití funkce atexit() z knihovny <stdlib.h>. Abych si byl jistý, že se po skončení programu obnoví původní nastavení terminálu, registroval jsem funkci obnovTerminal() pomocí atexit(). Ať už program skončí normálním návratem z funkce main(), nebo zavoláním exit() kdekoliv v programu, nebo když program ukončí uživatel stiskem CTRL+c, funkce obnovTerminal() bude zavolána. V příkladu je to tak trochu zbytečné, protože program po změně terminálu končí jen na jednom místě ( return EXIT_SUCCESS;) a ukončení pomocí CTRL+c je zakázáno.

  1. /*------------------------------------------------*/
  2. /* 50terminal/termios.c                           */
  3. #include <termios.h>
  4. #include <errno.h>
  5. #include <stdlib.h>
  6. #include <stdio.h>
  7.  
  8. static struct termios oldone;
  9.  
  10. static void obnovTerminal()
  11. {
  12.     printf("Obnovuji nastavení terminálu\n");
  13.     tcsetattr(fileno(stdin), TCSANOW, &oldone);
  14. }
  15.  
  16. int main(void)
  17. {
  18.     char ch;
  19.     struct termios newone;
  20.  
  21.     tcgetattr(fileno(stdin), &oldone);
  22.     /* pozor, c_cc pro kanonicky a nekanonicky rezim se muzou lisit,
  23.      * znamenaji pro kazdy rezim neco trochu jineho,
  24.      * nemeli by se zamenovat */
  25.     newone = oldone;
  26.     newone.c_lflag &= ~ECHO;
  27.     newone.c_lflag &= ~ISIG;
  28.     newone.c_lflag &= ~ICANON;
  29.     newone.c_cc[VMIN] = 1;
  30.     newone.c_cc[VTIME] = 0;
  31.     if (tcsetattr(fileno(stdin), TCSAFLUSH, &newone) != 0) {
  32.         perror("tcsetattr");
  33.         return EXIT_FAILURE;
  34.     }
  35.     /* nezapomenout na obnoveni nastaveni terminalu! */
  36.     atexit(obnovTerminal);
  37.  
  38.     printf("Zadávání znaků ukončíte klávesou Enter:\n");
  39.     do {
  40.         ch = getchar();
  41.         printf("ch = %c\n", ch);
  42.     } while (ch != '\0' && ch != '\n');
  43.  
  44.     return EXIT_SUCCESS;
  45. }
  46.  
  47. /*------------------------------------------------*/

Výstup z programu může vypadat třeba takto:

$ ./termios
Zadávání znaků ukončíte klávesou Enter:
ch = S
ch = a
ch = l
ch = l
ch = y
ch = x
ch = #
ch = #
ch =

Obnovuji nastavení terminálu

Všiměte si, že není vidět co píšu :-). Vidíte jen to, co vytiskl program.

Předposlední dva znaky ukazují můj pokus o ukončení programu pomocí Ctrl+c. Protože jsem ale vypnul generování signálů, k ukončení programu nedošlo. Místo toho se na obrazovku vypsal nějaký nesmysl, který jsem tady nahradil znaky #. Posledním zadaným znakem byl enter.

Knihovna termios.h poskytuje řadu funkcí a maker, které můžete využít pro přenositelnou (v rámci linuxů a unixů) práci s terminálem. Pokud vám stačí to, co jsem ukázal v tomto posledním příkladu, tak si s tím určitě vystačíte. Pokud budete chtít ale něco víc, například pracovat s barvami, pohybovat kurzorem po obrazovce atp., neobejdete se bez pracné práce s různými řídícími sekvencemi. Existuje ale daleko jednodušší způsob jak na to – knihovna <ncurses.h>. O tom bude příští kapitola.

Komentář Hlášení chyby
Created: 24.10.2014
Last updated: 20.11.2015
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..