Pull to refresh

Comments 23

В первую очередь это нужно для автоматического тестирования программ, рисующих что-то на терминале с помощью curses, по моему мнению. Как иначе написать тесты для программы, которая ждёт, что пользователь нажмёт клавишу, и выводит результаты в определенное место экрана средствами curses?

В линуксе можно посылать символы в терминал через ioctl. Насчет читать оттуда символы и атрибуты — не интересовался, но теоретически тоже можно. Вот так, к примеру, можно «напечатать» хелловорлд:

#!/usr/bin/perl -w
use strict;

open(HC, "+</dev/tty");
my $s = "Hello, world!\n";

for(my $i=0; $i < length($s); ++$i)
{
   ioctl HC, 0x5412, substr( $s, $i, 1 );
}



Естественно, если хочется, чтобы введенные символы попали в другое приложение, придется запускать его как дочерний процесс.
До ROTE я довольно долго пробовал другие решения. Моё внимание также привлёк expect с virterm, но видимо у меня и у expect шарики в мозгах крутятся в разных направлениях.

По крайней мере, я не нашел других библиотек, кроме ROTE и libvterm, которые одновременно удовлетворяли бы следующим требованиям:
  • умели запускать дочерний процесс,
  • умели получать содержимое терминала,
  • умели передавать данные в дочерний процесс, имитируя нажатия клавиш,
  • самое главное: были бы достаточно просты в использовании.
Мне кажется, у вас переплетаются функции эмулятора терминала и оболочки (shell). Терминал должен отвечать только за отображение информации: это односторонний канал коммуникации который получает поток символов и, иногда, последовательности управления. Переместить курсор, там, или подсветить красненьким. Важно, что состояние курсора хранится в оболочке, а эмулятору терминала оно нужно для того, чтобы знать с какого места продолжать выводить данные. Большинство PTY используют ANSI для control sequences, но я бы все же не полагался на это, и доверял terminfo. Ncurses, например, использует terminfo.

По сути, эмулятор терминала — это пара устройств, с которыми можно обращаться как с файлами. Вот, например, как писать в терминал без всяких библиотек, just for fun.:

$ tty # In one session: find out this terminal's output device.
$ printf "$(tput setaf 1)Follow the $(tput setaf 7)white$(tput setaf 1) rabbit." > ttys006 # In another session: write to the first one.


Кстати, tput выводит правильную для вашего терминала последовательность, а не hard-coded ANSI.
Большинство PTY используют ANSI для control sequences, но я бы все же не полагался на это, и доверял terminfo.
В ROTE заявлена эмуляция VT100, который использует ANSI, так что пока остается жить с этим. Надо посмотреть, как обстоят дела в libvterm.

Вот, например, как писать в терминал без всяких библиотек, just for fun.
Насколько я понимаю, ROTE примерно так и делает и обрабатывает то, что поступает в файл терминала, применяя это к своим структурам данных, в которых хранится состояние терминала. Хочется по (x, y) ячейки получать её символ, цвет текста, цвет фона, это в тестах и используется.
Лучше всего взять новый libvterm, так как он ещё разрабатывается и если нужной функциональности нету, её вполне могут добавить. Пример кода, использующего новый libvterm именно для тестов: vterm.py, terminal.py и, собственно, код тестов. На luajit это можно переложить практически 1‐в‐1, т.к. здесь есть cffi и где‐то есть аналог pexpect для lua.
Тем не менее, ни один из известных мне эмуляторов терминала (ROTE и оба libvterm — это только библиотеки для создания оных) не занимается только отображением, а ещё и позволяет запуск: xterm -e my_command. Терминалы <A-Fn$gt; такого действительно не позволяют, забирая информацию о том, что нужно запускать, из /etc.

И не нужно путать «эмулятор терминала» и «устройство терминала». /dev/ttys006 — это устройство терминала, само по себе оно не имеет никакого отношения к эмулятору терминала. Эмулятор терминала обычно создаёт это устройство и читает из него, чтобы определить, что нужно отображать, но, к примеру, эмулятор терминала на libvterm (который новый, а не который из ROTE) вполне может обойтись без устройства /dev/…: можно просто скормить ему строку, зашитую в исполняемый файл, который использует libvterm. Или читать из pipe. Правда, часто программы изменяют своё поведение, если определяют, что один из потоков std* подключён не к устройству терминала (проверяют обычно stdout или stderr).

«Пары устройств» также нету. Терминал — это одно устройство терминала.
Спасибо за ответ. Что такое <A-FN$>?

Возможно, я неправильно понял man, но мое представление об эмуляторах терминала отсюда: linux.die.net/man/4/ptmx
Согласно описанию, нужно открыть /dev/ptmx, получить a file descriptor (первое устройство, в которое писать), потом сделать некие телодвижения и получить второе устройство, /dev/pts* — из которого читать.

Вы правы, теоретически можно обойтись без устройств. Но практически так никто делать не станет. Не говоря уже о ls, который обрезает control sequences, su и passwd не читают из pip'ов.
Спасибо за ответ. Что такое <A-FN$>?
Там $gt; — я просто случайно написал $ вместо амперсанда. Я имел ввиду эмуляторы терминала, создаваемые самим ядром при запуске (в X11 доступны через <C-A-Fn, в самих эмуляторах или после какого‐то <A-SysRq-…> без <C-).

Возможно, я неправильно понял man, но мое представление об эмуляторах терминала отсюда: linux.die.net/man/4/ptmx
Согласно описанию, нужно открыть /dev/ptmx, получить a file descriptor (первое устройство, в которое писать), потом сделать некие телодвижения и получить второе устройство, /dev/pts* — из которого читать.
Таким образом вы никак не получите эмулятор терминала. Эмулятор терминала — эта та программа, которая читает, а затем показывает обратно. То, из чего она читает эмулятором терминала не является и в man 4 ptmx нигде не написано, что это эмулятор. Терминал ≠ эмулятор терминала; обычно в man страницах в linux терминал = устройство терминала, не более.

Хотя в одном вы правы, а я нет — терминал — это два устройства, а не одно.

Вы правы, теоретически можно обойтись без устройств. Но практически так никто делать не станет. Не говоря уже о ls, который обрезает control sequences, su и passwd не читают из pip'ов.
Во‐первых ls не обрезает, а не выводит. Во‐вторых, ls можно заставить, так же как и grep и много других программ со схожим поведением (тот же pv (pipe viewer) и bash тоже можно — они ничего не делают с цветами, но поведение терминал/не терминал различается). В‐четвёртых, я сильно подозреваю, что так делали и делают, когда получают несанкционированный shell на сервере: pty создать там не особо нужно, и может быть просто невозможно из‐за отсутствия прав.
Несмотря на то, что я сумбурно изъясняюсь, я таки понимаю, что иметь устройства ввода-вывода еще недостаточно, чтобы быть эмулятором терминала. Но они — публичный интерфейс эмулятора, если проводить параллели с OOP. Все, что внутри — неважно для внешнего наблюдателя. Ну да, умеет он как-то преобразовывать ANSI и показывать его.

С ls та же самая история. Обрезает ли он ANSI, или не добавляет его с самого начала, результат один: если output не pty, вывод отличается.

Я в свободное время пытаюсь написать эмулятор терминала, и уверен, что в реальном мире, единственный способ обеспечить пользователям предсказуемое поведение — прикидаться PTY.

То же и с последовательностями управления. Хотелось бы иметь возможность написать свой terminfo, и забыть о совместимости с ANSI, но программисты идут самым простым путем, и существует вагон и маленькая тележка утилит, у которых захардкоджены эти последовательности.

Если позволите, я хотел бы попросить совет. В современно мире, не имея наследия visual terminals и таких ограничений ресурсов, нужно ли передавать управляющие последовательности in-bound, или стоит их вынести в отдельный поток? Извините за off-topic.
ROTE устанавливается простым ./configure && make && make install. Надо отследить, чтобы она установилась туда, где её увидит система сборки. Я использую для этого make install prefix=/usr
Обычно если есть configure, то корректный вариант никак не make install prefix=/usr, а ./configure --prefix=/usr.

Ну и я не советую вам использовать --prefix=/usr и, уж точно, никогда и никому не рекомендовать так делать. Если вам нравиться помойка в системе — это ваше право, нормальные люди же либо создают пакет (в данном случае — скорее используют имеющийся), либо ставят куда‐нибудь в /opt, откуда можно удалить практически одной командой (+ вычищение мусора из /etc, но /etc можно организовать так, что это это очищение сведётся к hg backout --commit {offending-revision}, да и мусор строго в двух известных местах — там, где определяется $PATH и другие переменные окружения (e.g. MANPATH) и там, где определяются пути до библиотек (в Gentoo это вообще можно иметь в одном месте: /etc/env.d)).
Можете обосновать, почему make install prefix=/usr не является корректным вариантом? Я понимаю, что можно указать на уровне configure, но это не делает автоматически первый вариант некорректным.

Дополнил инструкцию упоминанием про checkinstall. Впрочем, пока всё происходит на компьютере разработчика, вряд ли кому-то навредит файл /usr/lib/librote.so, а в серьёзном месте должен быть строгий сисадмин, который следит, чтобы такого не было.
Часто в проектах есть пути вроде /usr/share/zsh/{version}, которые прописываются прямо в бинарнике. Здесь /usr — это prefix, а в указанном каталоге находятся очень нужные функции. Если в библиотеке ROTE такого нет (скорее всего, действительно нет) — то вам повезло.

Но также, как и в случае с «вряд ли кому-то навредит файл /usr/lib/librote.so» нужно формировать правильные привычки. Указанная библиотека сейчас устанавливает /usr/lib/librote.so, а в следующей версии наследники напишут установку с /usr/lib/librote.so.0, /usr/lib/librote.so, /usr/share/doc/rote/README, /usr/share/terminfo/r/rote, /usr/share/man/man3/{много страниц для каждой из функций}.3, /usr/share/info/rote.info, /usr/bin/rote-dump и /etc/rote.conf. И хрен вы теперь что вычистите, особенно если нет make uninstall (cmake такого, к примеру, не предоставляет) или вы забыли, какую версию устанавливали, а make uninstall от новой версии не подходит.

Суть в том, что 1. вы считаете автора достаточно порядочным, чтобы он устанавливал именно туда, куда вы говорите. И 2. тем не менее, вы не знаете, что именно устанавливается. Или, с правильными привычками вам не нужно знать, что именно устанавливается.

Также с правильными привычками вам не нужно знать, захардкоден ли где‐либо prefix. Скажите честно, вы выполняли анализ скрипта установки перед тем, как заключить, что он устанавливает только /usr/lib/librote.so? А анализ кода с целью убедиться, что prefix в коде нигде не используется? Если нет, то то, что вы делаете есть подбрасывания кубика с целью угадывания «загадит систему — не загадит», «будет работать корректно — будет работать некорректно — не будет работать вовсе».
А если да, но анализ не был частью более общего вроде «а не подсунули ли мне malware» (или «а не сделал ли автор глупых ошибок при написании сборочных скриптов, которые испортят мне систему ненамеренно»), то вы потратили кучу времени на то, на что время можно было не тратить.
Лучше ставить в виртуалке, если есть обоснованные подозрения на malware. В данном случае их нет, так как пакет 10 лет лежит на SF. Разумеется, сверку контрольных сумм никто не отменял.
Во‐первых, этот комментарий не про то. Во‐вторых, я не говорил про метод анализа. Что куда устанавливается и работает ли библиотека, если вы не давали ей корректный prefix вплоть до установки можно проверить и глядя на скрипт, и на виртуалке. Только если вы этого не делали, а используете свой метод, то подставляете свою систему под проблемы даже если вы установили не malware. А если вы делали анализ, но анализировали только корректность и результат (установленные файлы) установки именно с помощью ваших методов при указанных настройках, то вы потратили время на менее тривиальный (метод указания prefix при make prefix=… install нестандартный) анализ в то время, как могли этого избежать.
Так вы отвечали на первую часть комментария, а не на вторую. То есть, обосновывали некорректность make prefix=… install. Достаточно одного взгляда на Makefile, чтобы понять, что кроме prefix ничего менять не надо. Сколько времени занимает прийти к такому умозаключению, не влияет на корректность. Таким образом, некорректность метода make install prefix=/usr не доказана.

К слову, лично у меня от файлов configure волосы на голове дыбом и разбираться с ними и тем более помнить, какие у них опции, заняло бы куда больше времени, чем заглянуть в Makefile и увидеть там prefix=/usr/local.
Вы не поняли. Вам не нужно заглядывать в Makefile. Вам нужно, по крайней мере, в тот самый файл configure и убедиться, что некорректное значение prefix ни в одном из мест, где configure его сохраняет (даже у rote configure сохраняет prefix не только в Makefile), не влияет фатально на работу библиотеки.

Часто configure сохраняет prefix (точнее, не он сам, а какая‐то производная вроде libdir=$prefix/lib) в config.h, информация о prefix из которого используется для определения каталога, откуда нужно загружать необходимые ресурсы.

При использовании стандартного способа с ./configure --prefix вам нужно прочитать ./configure --help. При использовании вашего способа вам нужно прочитать configure.ac и неопределённое количество файлов с исходным кодом (или, по крайней мере, определить «на живую», куда был сохранён prefix (здесь — три файла) и где это «куда» используется). Только так вы можете проверить корректность make prefix=. Прочитав Makefile вы не можете проверить корректность вашего метода, потому что Makefile не является единственным файлом, в который этот prefix используется.
Спасибо, вы правы. Выставлять prefix при make install чревато ошибками. Заменил инструкции и тестирующие скрипты.
Я выразился как‐то длинно. Кратко будет: вы не можете проверить корректность использования make prefix=… install, прочитав Makefile, так как Makefile не является единственным файлом, в котором configure сохраняет prefix.
И ещё: попытайтесь использовать ваш способ с Vim. Убедитесь, что он нифига не поддерживает подсветку синтаксиса, так как не может найти свои файлы.

./configure --prefix=… работает всегда, когда используется configure, сгенерированный с помощью autotools (других configure я, к счастью, не видел). make prefix=… install есть опасная привычка, которая гарантированно ведёт к некорректному результату в случае проектов с ресурсами, импортируемыми из $prefix/share (примеры: Vim, zsh, fish).
По поводу libvterm: во‐первых, вы указали репозиторий с исходным кодом, модифицированным для проекта NeoVim. Правильный URL: www.leonerd.org.uk/code/libvterm/. Во‐вторых, этот libvterm не основан на ROTE (или основан, но автор не признаётся). На ROTE официально основан sourceforge.net/projects/libvterm/.
И последний, кстати, тоже мёртв, просто пролежал меньше (2 vs 8) лет. В репозитории всего 11 ревизий, бо́льшая часть из них связана с исправлением ошибок. Судя по всему, раньше репозиторий был в другом месте и менялся активнее, но историю не импортировали; первая ревизия — 2010, последняя — 2013.

На странице rote.sourceforge.net/ явным образом ссылаются на второй проект (на SF).
Sign up to leave a comment.

Articles