Работа с терминалом; пример простого проекта
Терминал
Терминал — это устройство ввода и вывода байтов
- raw mode (необработанный режим): «просто байты»
- cooked mode (обработанный режим):
- Управление со стороны ОС (сигналы с клавиатуры),
stty / stty -a
- Преобразование B/B
Игрища с переводом строки (^M / ^J)
Примитивное редактирование В/В (^U / ^W / ^H)
- Управление со стороны ОС (сигналы с клавиатуры),
Управление выводом со стороны приложения, ESC-последовательности
- Немного истории, AKA терминал = клавиатура + принтер; подчёркивание и двойная печать и прочая античность
- Изменение атрибутов текста
xterm:
$ echo -e '\e[44;36m'; cat; echo -e '\e[m'
В конце команды — Enter + ^D
- Позиционирование курсора
xterm:
$ echo -e '\e[2J\e[10;10HЭто 10:10\e[2;20HЭто 2:20\e[20;5HЭто 20:5'
- дополнительные устройства ввода (например, мышь) — имитация ввода с клавиатуры (обычно включается особой ESC-последовательностью)
xterm:
$ echo -e '\e[?1002h'; cat; echo -e '\e[?1002l' …
или 1003
База информации о терминалах Terminfo
- Унификация управляющих последовательностей
infocmp
tput
Проблемы с неправильным $TERM
Неожиданный бонус Tektronix_4010:
если вы используете xterm, просто скачайте этот файл и скажите ему cat:
$ cat map.tek.xterm
Хуже не будет
Для пользователей xterm в дистрибутивах линейки ALT (возможно, и для других тоже) — сценарий на shell, который показывает все .tek-файлы в заданном каталоге (по умолчанию — в документации по xterm).
Curses
(кому интересно) Учебник на русском (по старому curses, но зато простой)
(кому интересно) Учебник на английском
(кому интересно) Учебник от автора ncurses (изначально — от самого ESRа ☺)
NCurses — свободная (на сегодняшний день, главная) реализация Curses
- Curses: библиотека работы с произвольным терминалом
- Независимость от типа терминала
- Экран = матрица символов (а не телетайп)
Важно — функции вывода заполняют эту воображаемую матрицу (а не настоящий экран), экран обновляется отдельной командой.
Простой пример:
Сборка: cc Hello.c -lcurses -o Hello
Названия devel-пакетов в дистрибутивах ALT: libncurses-devel и libncursesw-devel
initscr() / endwin() — инициализация и останов движка curses
move() — перемещение курсора, LINES/COLS — размер экрана
addstr() — аналог puts()
(никогда больше не используем -lcurses)
Пример с поддержкой русского и окном:
1 #include <ncurses.h>
2 #include <locale.h>
3
4 #define DX 7
5
6 void main() {
7 WINDOW *win;
8
9 setlocale(LC_ALL, "");
10
11 initscr();
12 noecho();
13 cbreak();
14 printw("Окно:");
15 refresh();
16
17 win = newwin(LINES-2*DX, COLS-2*DX, DX, DX);
18 keypad(win, TRUE);
19 scrollok (win, TRUE);
20 for(int c = wgetch(win); c != 27; c = wgetch(win))
21 wprintw(win, "Key: %d, Name: %s\n", c, keyname(c));
22 delwin(win);
23 endwin();
24 }
Программа должна установить локаль ("" — системная по умолчанию)
Сборка — с libncursesw: cc Win.c -lncursesw -o Win
- Мы использовали отдельное окно, в котором NCurses сам поддерживает скроллинг
Выводится «код клавиши» (режим keypad) и её название
NCurses обновляет экран перед вводом; поскольку из основного окна мы не вводим, нужен refresh()
Поэкспериментируйте: попробуйте закомментировать refresh() или setlocale(), вместо noecho() вызвать echo(), вместо cbreak() — nocbreak(), а в keypad() и scrollok() указать FALSE. Что делают эти функции?
Созданное окно надо удалять (при этом вызывается free())
В виртуалке практикума есть man-страницы на всё))
Пример с рамкой:
1 #include <curses.h>
2 #include <locale.h>
3
4 #define DX 7
5 #define DY 3
6
7 int main(int argc, char *argv[]) {
8 WINDOW *frame, *win;
9 int c = 0;
10
11 setlocale(LC_ALL, "");
12 initscr();
13 noecho();
14 cbreak();
15 printw("Окно:");
16 refresh();
17
18 frame = newwin(LINES - 2*DY, COLS - 2*DX, DY, DX);
19 box(frame, 0, 0);
20 mvwaddstr(frame, 0, (int)((COLS - 2*DX - 5) / 2), "Рамка");
21 wrefresh(frame);
22
23 win = newwin(LINES - 2*DY - 2, COLS - 2*DX-2, DY+1, DX+1);
24 keypad(win, TRUE);
25 scrollok (win, TRUE);
26 while((c = wgetch(win)) != 27)
27 wprintw(win, "%d: %s\n", c, keyname(c));
28 delwin(win);
29 delwin(frame);
30 endwin();
31 return 0;
32 }
box() рисует рамку внутри окна — если начнётся скроллинг, рамку надо будет перерисовывать
Мы сделали наложенные окна (можно было и вложенные, но это сложнее)
Примитивный Makefile
- Make (очень коротко): цели и рецепты
Синтаксис: обязательный символ табуляции в рецептах
- Задача — запуск команд пересборки, когда это надо
- Цели могут быть, а могут не быть файлами
- Пример:
- Использование:
1 $ make 2 cc Window.c -o Window -lncursesw 3 $ ls 4 a.out Hello.c~ Makefile~ win.c Window.c WindowEx.c 5 Hello.c Makefile simple.c Window Window.c~ 6 $ make 7 make: «Window» не требует обновления. 8 $ make clean 9 rm -f *~ *.o Window a.out 10 $ ls 11 Hello.c Makefile simple.c win.c Window.c WindowEx.c 12 $ make 13 cc Window.c -o Window -lncursesw 14 $ ls 15 Hello.c Makefile simple.c win.c Window Window.c WindowEx.c 16
Tab hell. Again.
- Что такое символ табуляции (привет, пишущая машинка!)
- Как распознать табуляцию
hexdump -C файл
vim :set list
mcedit
Если ничего не помогает, в Makefile можно переопределить символ, с которого начинаются строчки с «рецептами» (но, к сожалению, не в пробел ☹)
.RECIPEPREFIX=: Window: Window.c : cc Window.c -o Window -lncursesw clean: : rm -f *~ *.o Window a.out
Д/З
Предполагается, что на спецкурс вы записались
Сделать в репозитории подкаталог, совпадающий с именем данной страницы (01_TerminalProject, и поместить в него решение следующей задачи.
С помощью интернета и здравого смысла написать на ncurses программу Show.c, которая постранично просматривает файл (слишком длинные строки усекаются или переносятся — как вам удобнее).
- Имя файла передавать параметром командной строки.
- Файл должен показываться в окне, а в первой строке экрана должно содержаться его имя.
Весь файл допустимо хранить в памяти, например, одним куском или в виде списка строк.
При нажатии пробела файл прокручивается дальше (если есть такая возможность), при нажатии ESC программа завершается.
Имеет смысл очищать окно при помощи werase(), а потом всё заново на нём выводить.
- Многобайтовую кодировку можно не поддерживать
Исполняемый файл должен называться Show
Сделать Makefile и добиться работоспособности make (обратите внимание на использование табуляций и/или RECIPEPREFIX
- Не забыть опубликовать решение в репозитории!
Дополнительно, для тех, кому стало интересно:
- При нажатии стрелки вправо выводить строки файла, начиная со следующей колонки (так можно посмотреть урезанные длинные строки; начала строк при этом не видны). Стрелку влево обработать так же.
Реализовать обработку PgUp, PgDown и стрелок вверх/вниз — прокрутка на один экран вперёд/назад, на одну строку вперёд-назад, остальные клавиши игнорировать
- … (далее везде)
Минимальный вариант…
…А потом я сделал поддержку wide characters и цвет)