Давайте временно прервем длившийся два выпуска подряд разговор об управлении памятью (тем более, что присланные мне примеры «подозрительных программ» требуют серьезного анализа) и вновь обратимся в этот раз к вопросам пользовательского интерфейса.

Как художник художнику

Один из последних полученных мной вопросов звучит так: «...Хотелось бы побольше узнать ... о ESC-sequences. Мне их смысл пришлось узнавать при помощи reverse engineering, т.к. никаких объяснений найти не удалось, а раскрасить вывод хотелось...». Фактически речь идет о приглашении к продолжению темы, которая затрагивалась нами в последнем номере прошлого года. Вновь возник вопрос о необходимости организации терминального ввода/вывода. Увы, я и сам писал программы, которые использовали какую-то привязку к конкретным терминалам. Однако в общей постановке вопрос в самом деле интересен — тем более, что иногда не ясно, откуда «ножки растут». Одним из примеров являются комбинации, используемые для русификации Linux.

Допустим, нам нужно запрограммировать вывод на терминальный экран. Откуда получить необходимую информацию? Наиболее правильный ответ заключается в том, чтобы... вообще ее не получать, воспользовавшись средствами типа ncurses. В этом случае у вас есть определенная гарантия того, что ваша программа будет работать не только на консольном терминале, но и, скажем, с любым эмулятором X-терминала (и допускать перенос).

Если этот подход нельзя использовать, нужно, конечно, обращаться к документации на данный тип терминала (именно на данный: в моей практике был случай терминала, который эмулировал другой терминал с... 26 ошибками). Еще один подход состоит в том, чтобы непосредственно использовать базу данных terminfo, если, конечно, он там есть. (К сожалению, автор процитированного выше письма не указал значение переменной TERM, поэтому далее использован вариант для linux.)

Например, наши коды (цвет!) можно извлечь из базы данных терминалов следующим образом (имя каталогов может быть другим, например, /usr/lib/terminfo):

infocmp usr/share/terminfo/l/linux

В данном случае linux значение переменной окружения TERM (и имя каталога l, соответственно, от первой буквы в linux). Среди всякой всячины (см. man terminfo) обнаруживаем описание, например, параметров setab и setaf setab=E[4%p1%dm, setaf=E[3% p1%dm. Из этого следует, что комбинации вида ESC [4Nm и ESC [3Nm, где N — номер цвета, меняют цвет подложки и изображения. Тривиальная проверка показывает, например, что ESC [32m делает изображение зеленым:

	echo -e ?33[32m?

В принципе, можно менять и саму палитру цветов. Для работы с экраном не обязательно надо использовать Си. Существует достаточно стандартная программа tput (см. описание), которая позволяет нам проделывать разнообразные операции из shell-скриптов. Например, вышеприведенный echo можно заменить на вызов tput:

tput setaf 2

и этот вариант уже не зависит (если, конечно, цвет вообще есть) от типа терминала. Впрочем, это тоже все «reverse engineering».

Что еще может выступать в качестве источника информации? Естественно, стандарты. Например, в различных описаниях русификации Linux можно встретить комбинации вида: echo -ne «33(K». Данная комбинация всегда объявляется «магической». Соответствующие ссылки можно отыскать, например, в библиотеке Мошкова.

Что это за магическая комбинация? Вы можете обнаружить, что она возникла достаточно давно. Начало данной комбинации (?33(?)появляется в стандарте ISO2022 и предназначено для установки конкретного шрифта из разрешенных в таблицу G0. Стандарт вводит 4 таблицы (G0-умолчание), устанавливаемые ESC-комбинациями с ?(?, ?)?, ?*? и ?+?. В частности, на месте ?K? может быть ?B? - ASCII или ?J? (очевидно?).

Они всегда где-то рядом

В одном из первых номеров было показано, что программа установки RedHat Linux способна выполнять массу различных функций в зависимости от выданного для нее названия. Один из простейших вариантов задания имени заключается в создании ссылок любой природы. Ясно, что ссылки в принципе не нужны. Если посмотреть описание системных вызовов exec, то видно, что имя можно задать абсолютно произвольное. В некоторых вариантах shell встроенная команда exec имеет явную опцию для задания имени программы (проверьте, не установлен ли у вас параметр -a, например). Кем помимо uncpio может прикинуться «инсталяшка» RedHat? Имея источники программы (misc/src/install) это можно явно посмотреть и обнаружить, что в этот список, как минимум, входят: setconsole, install, mount, mkdir, mknod, cat, ls, ln, rm, chmod, lsmod, mkswap, swapon, uncpio, insmod, modprobe, rmmod, depmod. А если иcточников нет? Тогда нужно как-то проверить код программы или ссылки на данную программу. Бинарный код для install2 можно найти в RedHat/instimage/usr/bin/install2. Соответственно выполняя команды типа:

ls -l | fgrep install2
strings install2 | fgrep -i usage
strings install2 | (sed -e /
uncpio/q; sed -e 5q) | tail -40l

В последней команде явно использовано то, что нам известно название одной из функций - uncpio — и мы пытаемся получить соседние строки для того чтобы проверить наличие среди них команд. Часть из них мы найдем.

Какие еще функции способна выполнять команда exec? Поскольку exec подменяет процесс текущего экземпляра shell на новый код, то ясно, что exec должен был бы быть последней командой в командном файле (и это бы означало экономное использование количества процессов пользователя, которое ограничено), так как просто некому делать что-либо дальше. Легко проверить, что это не всегда так. Почему? Во-первых, как уже замечалось в предыдущих номерах, shell порождает некоторое количество процессов (и не только по явному &). Естественно, часть веточек имеет право выполнить подмену shell на что-то другое. Во-вторых, exec может выполнять еще одну функцию в скрипте - подмену файлов для стандартного ввода или вывода. В этом случае команда имеет достаточно забавную форму, т.к. в ней не задается никакой программы, а только переназначения для файлов. Таким образом, например, достаточно часто начинаются скрипты для старта X Window.

Аналогии

Зависимость от имени должна быть явно задана в самой программе. А может ли проявляться эта зависимость сама собой без вмешательства программиста? В некоторых случаях это возможно. Например, если мы рассматриваем X-программы. В отличие от Windows в X Window не зафиксирован интерфейс управления окнами. Никто не мешает иметь много менеджеров экранов (или «оболочек», которые, конечно, могут не только экраном заниматься - twm, mwm, fvwm, GNOME, KDE, CDE и т.п.) на компьютере - главное не пытаться запускать их одновременно на одном сервере (ругаются очень...), это связано с тем, что программа управления экраном обычный X-клиент, просто несколько более активно использующий возможности X-протокола. Кто рисует рамочку вокруг окна запущенного нами приложения? Легко заметить, что вид этой рамочки зависит от того, какая программа управления экраном запущена, т.е. все вокруг окна нарисовано не программой, а именно менеджером экрана. Все, заметим, в частности, имя программы над ее окном. Откуда менеджер узнает эту информацию?

На самом деле, на экране достаточно много окон (далее «окошек» — для отличия от окон приложений). Каждая кнопочка, стрелочка на экране является элементом интерфейса. В классическом варианте интерфейса элементы основываются либо на виджетах (widget имеет в основании окошко), либо на гаджетах (gadget оного не имеет). В частности, рамочка вокруг окна каждого приложения состоит из ряда окошек. Приложение доверяет менеджеру экрана управление только частью своих окон (например, основное и какой-то диалог), с остальными окошками оно справляется само. Каждое окно имеет набор свойств. Если попробовать вызвать команду xprop и (когда появиться перекрестие) указать на какое-либо окно приложения, то обнаружится, что среди свойств можно найти, в частности, и имя программы. Когда программа стартует, она пытается «представиться».

Свойство, которое используется для «представления» программы будет содержать два имени. Одно из них имя программы, а второе имя «класса» программы. Класс программы обычно фиксируется программистом (хотя GNOME-программы имеют особый ключ —class для установки класса), а вот «имя» программы может меняться. Исторически сложилось так, что «класс» программы пишется с большой буквы, а «имя» - с маленькой. Это может быть либо само имя программы (или, соответственно, ссылки), либо то, что вы зададите на ключе -n или -name (они не эквивалентны, сравните с -title) для «классических» X-программ или -name для GNOME, так что создавать ссылки не обязательно. Впрочем, мы говорим здесь о классике (например, twm, mwm, CDE) и обсуждение GNOME пока отложим.

Ну, ладно, именовать-то мы можем. А зачем? Только для галочки сверху? Маловато будет.

Прежде, чем ответить на этот вопрос перейдем к понятию «ресурсов». Механизм, который лежит в основании ресурсов таков, что под ресурсом можно понимать все что угодно, от обычных графических понятий типа цвет или шрифт до диапазона отображаемых температур или допустимых параметров работы моделируемого реактора. Единственное, что нам хотелось бы, так это возможность текстового отображения его значения (хотя бы и в виде имени файла). Ясно, что за «всеядность» и универсальность стандартного механизма ресурсов (например, почти любой элемент меню любой программы, которая написана с использованием библиотеки Xt, может быть без перекомпиляции превращен в иконку и т.п.) мы платим ресурсами процессора. Поэтому и существуют альтернативные построения над X Window, но мы не будем их здесь обсуждать (кроме того, это, конечно, было связано с политикой OSF в отношении лицензирования Motif, несколько лет назад стоимость была порядка 20 тыс. долл. за источники). Ресурсы «подвязаны» к каждому элементу интерфейса. При этом некоторые из элементов могут иметь одинаковые по названию ресурсы (но не обязательно с одинаковыми значениями). Ресурсы также имеют свои классы. Например, класс Foreground может включать цвета всех элементов отличных от подложки и распадаться на отдельные элементики описывающие цвет шрифта, цвет курсора, цвет маркера и т.п., в частности, один из элементиков может иметь имя foreground.

Есть и еще одно отношение элемент<->класс. Такое отношение существует для элементов интерфейса. Например, можно говорить о классах «нажимаемая кнопочка» или «диалог выбора файла» безотносительно к тому, для чего используется данная кнопочка, какой тип файла выбирается. Информация о ресурсах сохраняется либо в файле (типа .Xdefaults), либо непосредственно у X-сервера (типа свойства RESOURCE_MANAGER основного окна, его можно посмотреть либо через xprop -root, который покажет все свойства окна, либо при помощи xrdb -edit tmp.tmp, где tmp.tmp - файл для результата). Если вы используете CDE, то среди свойств основного окна вы обнаружите и список файлов, которые участвуют в построении базы ресурсов. Сам файл состоит из записей вида:

имя_ресурса : значение ресурса

Важно, что возможно задание шаблона ресурса для имени его, что-либо в стиле: *Foreground: red - все отличное от подложки по умолчанию у всех программ отображать красным. «Классические» программы, кроме того, позволяют явно указывать ресурс:

xclock -xrm «*foreground: red».

Поскольку в формировании базы ресурсов используется много файлов (см. для классов «обычных» X-программ /usr/lib/X11/app-defaults или /usr/dt/app-defaults/C для CDE) и последними используются пользовательские, то, очевидно, при использовании шаблонов возможна ситуация, когда один и тот же ресурс может соответствовать нескольким шаблонам. Например, ресурсу a.b.c.d соответствуют и *d, и a*d, и A*c.d (A считаем классом для a), и a*b*d. Что будет использовано? Слегка огрубляя, можно сказать, что используется более подробное и точное описание (правда, что считать более подробным и точным?) - в данном случае последнее.

Не станем пока углубляться в эти дебри и остановимся на самом простом - на зависимости поведения программы от имени. Можно также рассматривать вопрос о поведении менеджера экрана по отношению к приложению. Что требуется для подобного рода изменений в поведении? Дело в том, что в первой позиции имени ресурса пишется имя программы, для которой мы хотели бы определить ресурс. Если хочется определить что-либо единое для всех, в первой позиции должна быть *. Аналогично, в описании ресурсов для менеджера экрана можно использовать комбинацию типа Mwm*client*resource: .... — задание поведения менеджера экрана mwm по отношению к конкретному клиенту (типа цветов рамки, вида иконки, меню окна и т.п. для клиента xterm). Заметим, что в системных файлах обычно используется ссылка на класс программы, что позволяет нам его «перебить» в своих пользовательских настройках, например, вместо Dtwm*...: .... написать dtwm*....:.... Обратите внимание на то, что ошибка в имени ресурсов, как правило, никак не диагностируется, если только в результате не произойдет совпадение с именем другого ресурса и это не будет замечено. Поэтому орфографическая ошибка часто не обнаруживается.

Теперь становится ясно, что для изменения поведения мы должны иметь в файлах описания ресурсов несовпадающие значения для различных экземпляров программ. Попробуем теперь представить пример файла ресурсов, который переопределял бы поведение программы (или менеджера экрана по отношению к ней):

qclock*analog: false
Mwm*qclock*clientDecoration: none

При использовании mwm и запуске программы xclock через линк или с ключем -name qclock мы получим запуск в цифровом (кварцевые часы!) виде и без внешней рамочки. Первое обеспечивается ресурсами xclock, второе - mwm.

Свои вопросы автору вы можете направлять по электронной почте по адресу oblakov@bigfoot.com

Поделитесь материалом с коллегами и друзьями