Технология VIXTERM:
что это такое и как она работает?

О целях и задачах проекта.

Существует широко распространенное мнение, что Unix-подобные системы не обладают столь привычным для большинства пользователей "дружественным интерфейсом". Такое утверждение излишне примитивно и далеко от истины - но, тем не менее, не вполне безосновательно. Действительно, Unix - семейство операционных систем, созданных когда-то программистами и для программистов, и первоначально они применялись в основном в областях, лежащих далеко от сферы интересов подавляющего большинства пользователей ПК. Традиционно, для многих Unix-пользователей самый привычный и дружественный интерфейс - это командная строка любимой оболочки в совокупности с набором собственоручно разработанных мини-утилит для решения повседневных задач. Даже когда у рабочих станций на основе Unix появились первые графические интерфейсы на основе X, они были весьма утилитарными инструментами, лишенными многих "украшений", столь привычных для пользователей Windows и Macintosh.
Однако, времена меняются. Широкие массы "простых пользователей" начинают проявлять интерес к Unix-системам, и ошеломляющий успех Linux - дополнительное тому подтверждение. Не в последнюю очередь благодаря этому представление о пользовательском интерфейсе рабочих станций начинает мигрировать по направлению к тем стандартам, которые давно и прочно установились на персональных компьютерах, и лично меня это только радует. Впрочем, последнее замечание автора не следует воспринимать как очередной выпад в бесконечных и бессмысленных спорах о вкусах. Я совсем не утверждаю, что т.н. "дружественный" пользовательский интерфейс (под которым я понимаю интерфейс, широко использующий [псевдо]графические элементы управления, экранные окна, развитый набор клавиатурных команд и команд мыши и т.п.) будет заведомо "лучше" или "удобнее", чем "традиционная" командно-ориентированная консоль. Такая постановка вопроса мне представляется бессмысленной - у обоих способов взаимодействия человека с компьютером имеются как свои достоинства, так и свои недостатки. Тем не менее никто, наверное, не станет спорить с тем, что в современной Unix-системе, ориентированный на массового пользователя, программы с "дружественным" интерфейсом должны быть представлены не менее полно, чем утилиты "командной строки". Заметим, что развитые технологии межпрограммного взаимодействия позволяют достаточно легко оснастить "традиционную" утилиту Unix вполне "дружественной" оболочкой - конечно, если имеются технологические средства, позволяющие такую оболочку создать.
В настоящее время графические интерфейсы для X Windowing System прогрессируют очень быстро. Появились такие мощные пакеты, как KDE и Gnome, и набор разработанных или адаптированных для них приложений также растет. Однако, не следует забывать и про текстовую консоль, которая, несмотря на распространение графических дисплеев, сохраняет свою актуальность в качестве рабочего инструмента. Она дешевле, проще в сопровождении, для некоторых задач даже "интуитивней", чем графические дисплеи. Последние совершенно не требуются для многих видов деятельности (например, для работы банковского кассира или оператора системы резервирования билетов) - текстового терминала вполне достаточно. Поэтому вопрос об "терминальных" интерфейсных технологиях для разработки пользовательских приложений не стоит снимать с повестки дня.
Что для этого предлагает большинство Unix-систем? Обычно - программную эмуляцию архаичных терминальных протоколов (например, ANSI, VT102 или Avatar) и программные "надстройки" над ними, подобные библиотекам curses (3). На мой взгляд, для создания нормального полноэкранного приложения этого мало. Даже памятные всем DOS-программистам BIOS-сервисы 10h и 16h обеспечивали намного более развитые возможности для работы с дисплеем и клавиатурой. Стандартные средства для работы с мышью из консольных приложений в Unix-системах не предусмотрены вообще, и в каждой системе этот вопрос решается по своему (в Linux, например, популярен пакет gpm). То же самое можно сказать про интернационализацию дисплея/клавиатуры. Как следствие всего перечисленного, в Unix-системах мало распространены консольные программы, сравнимые по качеству интерфейсного дизайна с такими классическими DOS-хитами, как Norton Commander, Norton Utilities, PC Tools, dBase III, FoxBase... в общем, список можно продолжать долго. Даже текстовые редакторы под DOS, в своей массе "приятнее" по интерфейсу, чем Unix-овские (хотя при этом обычно уступают по мощности - особенно таким монстрам, как emacs).
Все перечисленные проблемы обычно можно разрешить в рамках каждой конкретной системы. Так, в Linux имеются "обходные пути" для прямой работы с видеодисплеем ("/dev/vcs*"), с клавиатурой (RAW-режим и многочисленные нестандартные ioctl'ы) и с мышью (можно работать через gpm, но нетрудно и напрямую). Однако, приложение, которое пытается самостоятельно справиться с этими проблемами, теряет такое принципиальное достоинство, как переносимость.
VIXTERM - это попытка предложить универсальное решение этой проблемы. Это - клиент-серверная система, в которой решение большей части аппаратно- и системно-зависимых проблем консольного ввода-вывода перекладывается на отдельную программу-сервер. Может возникнуть вопрос: не слишком ли усложненный подход предлагается для решения такой простой задачи, как работа с текстовой консолью? В действительности эта задача не столь уж проста. Можно привести следующие аргументы в пользу клиент-серверной архитектуры системы:

Резюмируя, можно сказать, что vixterm система - это некое подобие X Windowing System, но ориентированное на обычную текстовую консоль. Предлагаемая технологией vixterm рабочая модель представляется более мощной и гибкой, чем при использовании библиотек curses/ncurses, а ее использование избавляет приложение от необходимости вникать во множество рутинных технических деталей, и, тем самым, дает ему возможность сосредоточиться на сути решаемой задачи.


Техника реализации.

Работа vixterm-сервера состоит в последовательной обработке запросов, поступающих от клиентов. Логическая структура запроса проста. Это пакет данных, который начинается с кода операции (однобайтового), следующего за ним блока аргументов (размер которого однозначно определяется операцией) и (для многих операций) блока данных, размер которого определяется на основе аргументов. Некоторые запросы (в основном те, идентификаторы которых начинаются с VT_GET) выдают блок данных в ответ, и клиент (клиенты) должны прочитать его прежде, чем смогут продолжать работу с сервером. Выполнение каждой операции генерирует код завершения, который клиент может прочесть с помощью отдельного запроса VT_GET_ERROR. Код VT_ErrNone означает успешное выполнение запроса, остальные - разнообразные ошибки.

Работа с экраном. Большая часть запросов обеспечивает выполнение разнообразных операций экранного вывода. vixterm предлагает очень простую концепцию работы с видеодисплеем. Экран vixterm-устройства - это прямоугольную матрица символов. Предполагается, что минимальная разрядность кода символа - 8 бит (в будущем набор операций может быть расширен для поддержки 16-битовых символов Unicode). С каждой символьной позицией ассоциирован аттрибут (также, как минимум, 8-битовый), интерпретация которого определяется сервером. (Это может быть цвет символа и/или фона, шрифт/начертание, специальные эффекты (например, мерцание) и т. п.). Совокупность символа и ассоциированного с ним аттрибута мы будем называть символьной позицией (или ячейкой).
Набор аттрибутов, используемый конечным приложением, должен быть адаптируемым. Текущую кодовую страницу и аттрибутную модель сервера можно узнать с помощью специальных запросов (VT_GET_CPAGE и VT_GET_AMDL). Приложение не должно предполагать, что аттрибуты всегда интерпретируются так же, как на стандартной CGA (EGA, VGA...) текстовой консоли. Столь же неправильно делать какие-либо априорные предположения относительно текущей кодовой страницы (сервер гарантирует лишь то, что коды 32..126 соответствуют стандарту ASCII).
В настоящее время сервер поддерживает следующие запросы (символические идентификаторы которых даны в круглых скобках):

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

Работа с клавиатурой. Как уже говорилось выше, она происходит в т.н. RAW-режиме, т.е. на уровне скэн-кодов. Реакцией на нажатия клавиш являются клавиатурные запросы двух родов: стандартные и расширенные (extended). Стандартные запросы генерируются в ответ на большинство клавиш и клавиатурных комбинаций, имеющих общепринятые ASCII-коды. К этой категории относятся алфавитно-цифровые клавиши и все терминально-независимые управляющие ([Esc], [Enter], [BackSpace], [Tab]). Все клавиши-модификаторы (оба [Shift], [CapsLock], [NumLock]) и последовательности типа [Ctrl]+<буква> также работают как ожидается. Если одновременно нажата клавиша [Alt], в коде символа устанавливается 7-ой бит.

Клавиши, не имеющие "стандартных" ASCII-кодов, генерируют т.н. расширенные (extended) запросы. На стандартной PC-клавиатуре это: стрелки, [Home]/[End], [PgUp]/[PgDn], [Insert]/[Delete], функциональные от [F1] до [F12]. Для всех перечисленных определен свой набор кодов. В дополнение, каждое extended-сообщение клавиатуры содержит информацию о регистровых клавишах, нажатых одновременно (отслеживаются правый/левый [Shift], [Ctrl] и [Alt]). Помимо этого, специальный код генерируется при переключении режимов CapsLock/NumLock/ScrollLock. В каждом сообщении клавиатуры содержится "счетчик повторов", который желательно учитывать при его обработке.

Наконец, некоторые клавиатурные комбинации зарезервированы за сервером и никогда не передаются клиентам. Так, комбинация [Ctrl]+[Alt]+[F#] позволяет переключиться на виртуальную консоль с номером #. [Ctrl]+[Alt]+[Break] посылает сигнал терминального прерывания (SIGTERM) всем процессам, входящим в одну терминальную группу с сервером (обычно, клиентам). Комбинация [Ctrl]+[Alt]+[Esc] служит для принудительного завершения работы сервера.

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

Работа с мышью. Сервер работает со всеми поддерживаемыми ядром видами bus-мышей и всеми основными serial-протоколами (Microsoft, Microsoft Inport, Logitech, Mouse Systems). Приложение получает сообщения о событиях мыши, под которыми понимаются перемещения (относительные) и/или изменение состояния кнопок. Заметим, что сервер ничего не знает о взаимосвязи между курсором мыши и самим устройством. Передвигать курсор в ответ на перемещения мыши (если это требуется) - ответственность приложения.

Большая часть параметров мыши может настраиваться, включая пороги чувствительности/ ускорения, подмену кнопок, ориентацию. Помимо всего прочего, сервер пытается обеспечить корректное разделение мыши между различными виртуальными консолями (это особо актуально для bus-мышей).

Взаимодействие с клиентами. Работа vixterm-приложения обычно начинается с вызова библиотечной функции VT_Init и завершается обращением к функции VT_Term). При инициализации происходит автоматический запуск сервера для текущего терминального устройства (если в данный момент он не был активен). Когда клиент подключается к серверу, он регистрируется (VT_ENTER), а когда завершает работу - дерегистрируется (VT_LEAVE). Когда подключенных клиентов не остается, сервер сам завершает работу.

Большая часть настроек сервера доступна для конфигурации. Параметры конфигурации могут передаваться серверу двумя способами. При явном запуске сервера (что нетипично) они задаются строками-аргументами, имеющими вид "PARAM=VALUE". Каждая такая строка устанавливает значение VALUE для параметра PARAM. Однако, поскольку в большинстве случаев сервер запускается неявно, есть возможность передавать параметры через окружение (при этом к ним добавляется префикс "VXTM_"). При запуске vixterm-программ из оболочки параметры конфигурации обычно задаются локально. Например, если программа "vt_edit" запускается (из bash или других sh-оболочек) таким образом:

VXTM_MouseSample=300 VXTM_ShowStats=true vt_edit

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

Детальный список параметров сервера приведен ниже. Можно изменить даже рабочий каталог vixterm-системы (по умолчанию /var/VXTM). В нем создаются программные каналы для связи с клиентами и содержится ссылка на сам сервер (по умолчанию /var/VXTM/.srvbin).

Стандартная vixterm-библиотека. Обычно приложениям не нужно иметь дело с низкоуровневыми деталями vixterm-системы, поскольку они используют для работы средства стандартной библиотеки (обычно libvxtm.so). Помимо упомянутых выше функций VT_Init и VT_Term, обеспечивающих инициализацию и завершение клиента, библиотека предоставляет множество функций, обеспечивающих взаимодействие с сервером. Большая их часть являются тривиальными C-оболочками для соответствующих запросов серверу, и обеспечивают формирование запроса, передачу его серверу и (если требуется) получение ответа. Управление связью с сервером осуществляется через структуру типа VTX (дескриптор канала связи), которая обычно является первым параметром функции. Большинство функций возвращают в качестве параметра код завершения запроса (типа VT_Error). Возможна буферизация передаваемых и принимаемых данных для дополнительной эффективности. Для последовательной выборки событий, поступающих от сервера, служат функции VT_GetEvent и VT_GetEventWait.

Некоторые функции библиотеки не столь тривиальны. В тех случаях, когда соответствующая операция по каким-либо причинам не поддерживается сервером, они могут попытаться реализовать ее "своими силами". Прежде всего это относится к функциям, реализующим оконные операции: сдвиг окна (VT_Shift), открытие (VT_WinOpen), закрытие (VT_WinClose) и перемещение (VT_WinReloc) открытых окон в собственном стеке приложения. Наконец, в библиотеке предусмотрено несколько функций для прямого обмена "сырыми" данными с сервером.


Библиотека классов Limelite.

Хотя библиотека libvxtm.so предоставляет полный набор средств, обеспечивающих базовый уровень взаимодействия прикладной программы с сервером, попытаться написать что-либо содержательное исключительно на ее основе - тяжкий труд. Для создания пользовательских программ значительно удобнее и проще использовать такой инструмент, как Limelite.

Limelite - это библиотека классов, написанная на C++. Она дает возможность создавать пользовательские программы, основываясь не на низкоуровневых примитивах vixterm, а на более мощных программных компонентах. Limelite позволяет реализовать интерфейс, опирающийся на использование диалоговых окон, в которых используются уже готовые элементы управления - кнопки различного типа, простые и многоколоночные списки, древовидные структуры данных, поля и окна текстового редактирования и пр. Все эти элементы будут автоматически поддерживать ввод как с клавиатуры, так и с мыши. Объектно-ориентированная идеология библиотеки дает широкие возможности для интеграции компонент окна и организации интеллектуального взаимодействия между ними. Мощность и гибкость Limelite сочетаются с относительной простотой использования: можно создать сложное диалоговое окно, написав всего две-три строки исходного кода!

Идеология и основные принципы работы системы. Основное понятие Limelite - взаимодействующий с пользователем элемент диалога (он же актор диалога, или просто актор). Все подобные объекты - потомки класса LLActor. Принципиальными атрибутами актора являются его размеры, внешний вид, реакция на сообщения, получаемые от клавиатуры и мыши и реакция на внешние события.

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

Простой пример интегратора - класс LLArray. Он может содержать произвольное количество акторов-клиентов, автоматически обеспечивая их "выстраивание" по горизонтали и/или по вертикали. При использовании LLArray нет необходимости самостоятельно вычислять экранные координаты для каждого из клиентов - это будет сделано автоматически на основе их размеров (которые в Limelite всегда известны). Дополнительно можно вставить между клиентами LLArray линии-разделители, а для каждого из клиентов задать тип выравнивания - по одной из границ, или по центру. Другой, не менее употребительный интегратор - LLArbit - обеспечивает более свободную схему размещения клиентов, чем LLArray, позволяя явно задать для каждого элемента координаты (либо относительно рабочей области интегратора, либо относительно соседа).

Наконец, существует специальная подразновидность интеграторов, которые всегда содержат один и только один клиент. Они называются модификаторами, т.к. часто их единственная цель - модифицировать внешний вид клиента и/или его поведение. Простейший, и один из самых употребительных модификаторов - LLFrame - позволяет заключить клиент в рамку (возможно, имеющую заголовок, который будет подсвечиваться, когда клиент активируется). Преимущества, которые дает иерархический подход к построению объектов диалога, очевидны - готовый и отлаженный элемент диалога всегда можно сделать одним из элементов более сложной структуры, независимо от того, является ли он простым терминальным актором, или нет. Возможность работы с элементами диалога как с "черными ящиками", не задумываясь об их внутренней структуре - одна из ключевых особенностей Limelite, выгодно отличающая эту систему от многих аналогичных (например, Turbo Vision).

Обработка ввода. Как уже упоминалось выше, акторы Limelite способны получать управление от клавиатуры и мыши. Обычно сообщения от клавиатуры адресуются только одному элементу диалога, который называется активным (в других системах принято говорить, что он "имеет фокус ввода"). В ответ на некоторые клавиатурные команды (чаще всего, это [Tab]/[Shift]+[Tab]) элемент может передать фокус ввода предыдущему или следующему элементу в окне. Некоторые элементы могут проигнорировать попытку активировать их - всегда (например, статические текстовые поля LLInfo) или в отдельных случаях (например, списки без активного элемента).

Существует возможность обработать некоторые команды клавиатуры (акселераторы) глобально, т.е. передать информацию о них всем доступным элементам диалога. Команды-акселераторы, в основном, обрабатываются кнопками, элементами меню и другими родственными объектами (класс LLItem и его подклассы). В принципе, все клавиатурные команды, которые не обрабатываются активным элементом, могут быть переданы другим в качестве акселераторов.

В отличие от клавиатурных команд, обработка команд мыши не связана с активным элементом. Limelite поддерживает курсор мыши, и сообщения о перемещениях мыши и нажатии/отжатии ее кнопок посылаются тому элементу, на котором он находится (если курсор переходит от одного элемента к другому, соответствующие извещения посылаются обоим элементам). В некоторых ситуациях актор "захватывает" мышь, ограничивая перемещение курсора областью, которую он занимает на экране. Для многих акторов (списков LLList, деревьев LLTree, редакторов LLEditor) "захват" мыши совмещается с т.н. "сенсорной прокруткой": когда курсор мыши пытается пересечь границы области захвата, вместо этого содержимое самого элемента прокручивается в противоположном направлении. Все акторы, которые допускают скроллинг своего содержимого, являются потомками класса LLSBClient. Это дает возможность снабдить их "рамкой" из вертикального и/или горизонтального скроллеров, с помощью специального модификатора LLSBFrame. Скроллеры показывают, какая "часть" содержимого клиента видима на экране, и посылают клиенту сообщения в ответ на щелчки мышью по области прокрутки или концевым стрелкам. Помимо этого, классы-потомки LLSBClient обычно поддерживают отдельный "режим прокрутки", который задействуется/отменяется по нажатию [ScrollLock].

Некоторые команды клавиатуры имеют специальную интерпретацию для менеджера диалогового окна. Так, нажатие на [Alt]+[F12] немедленно закрывает текущее окно диалога с выдачей соответствующего кода ошибки. Комбинация [Alt]+[F11] позволяет задействовать режим перемещения окна и/или изменения его размеров. В этом режиме окно можно перемещать (как курсорными клавишами, так и с помощью мыши), возвращение в нормальный режим работы осуществляется по [Enter]/[Esc].

События. Для того, чтобы акторы диалога могли взаимодействовать между собой, предусмотрен механизм событий. В отличие от многих других систем, события Limelite - это явления относительно высокого уровня. Не всякое нажатие на клавишу или движение мыши генерирует событие - они предназначены для информирования о более содержательных действиях, таких, как нажатие на экранную кнопку, перемещение активного элемента списка или изменение текста в окне редактирования. Все события - объекты, производные от класса LLEvent. В принципе, любое событие может быть обработано любым элементом (элементами) диалога, для чего он должен "подписаться" на данное событие при инициализации. Нередко события обрабатываются тем же актором диалога, который их инициирует - например, события LLEventListSelect, порождаемые списками LLList при смене выбранного элемента. Можно организовать глобальную обработку событий на уровне окна диалога. Реакцией на некоторые события может стать завершение работы диалогового окна. Например, это справедливо для события LLEventCancel, которое генерируется многими акторами диалога в ответ на нажатие клавиши [Esc].

Буфера обмена. Наличие буфера обмена (Clipboard)- возможность, привычная для пользователей многих оконных систем, но в Limelite он присутствует не в единственном экземпляре. Поддерживаются до восьми независимых буферов обмена (с номерами 0..7), при этом буфер #0 считается рабочим по умолчанию. Размер каждого из буферов не лимитирован. Любой элемент диалога может записать блок текста в любой из буферов, или прочитать его содержимое. В первую очередь эта возможность актуальна для редакторов.

Текстовый редактор. Текстовый редактор Limelite (т.е. класс LLEditor) - один из самых мощных и развитых акторов диалога, реализованных в системе. Он позволяет как просматривать блоки текста, так и редактировать их. Редактор работает с "виртуальным" буфером редактирования (класс EditBuf), который физически может быть реализован разными способами: как простой линейный массив символов (для редактирования небольших по объему текстов - например, полей строкового ввода), как сбалансированное двоичное дерево буферов разного размера (для реализации полномасштабного текстового редактора, способного эффективно работать с текстами размером в мегабайты), как с областью памяти, отображенной для чтения через mmap (для просмотра файлов без необходимости их явной загрузки в память) и т.п. Один буфер может быть отображен одновременно в нескольких различных окнах (при этом механизм событий обеспечивает их синхронное обновление). Редактор поддерживает выделение последовательного фрагмента текста (своего для каждого окна), и до 8 различных маркеров-"закладок", отмечающих позиции в тексте. Поддерживается очень большой набор клавиатурных команд, позволяющих перемещаться по тексту; вводить текст в режиме вставки/замены; удалять символы/слова/строки текста, перемещать их и выделять; копировать, удалять и перемещать выделенные фрагменты; взаимодействовать с буферами обмена и т.п. Набор команд редактирования ориентирован на пользователей DOS-редакторов (таких, как Wordstar, встроенные редакторы NC и Turbo-систем). Однако, присутствует и такая возможность, характерная для многих Unix-редакторов, как задание необязательного "числового аргумента" команды (для большинства команд он задает число повторений). Предусмотрены возможности легкого расширения редактора путем подключения средств для чтения/записи буферов и их фрагментов, поиска строк и регулярных выражений, форматирования абзацев и т.п. Присутствует также режим просмотра, возможность выделения фрагментов текста с помощью мыши, "сенсорный скроллинг" для окна редактирования, поддержка линеек прокрутки, обеспечиваемая модификатором LLSBFrame. Редактор поддерживает полный набор 8-битовых символов и может использоваться для просмотра/редактирования двоичных данных. Предусмотрено несколько режимов визуализации управляющих символов. В окончательной версии редактора, возможно, будет присутствовать элементы динамической перекодировки символов при вводе с клавиатуры/выводе на экран.

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


На главную страницу