Журнал LinuxFormat - перейти на главную

LXF92:Unix API

Материал из Linuxformat
Версия от 13:07, 19 ноября 2008; Crazy Rebel (обсуждение | вклад)

(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск


Unix API Настоящее программирование для Unix – без прикрас и библиотек-«оберток»

С окнами на «ты»

ЧАСТЬ 12 В заключительной статье цикла о программировании для Unix Андрей Боровский расскажет об использовании цветов и поддержке мыши... в консоли!

М

           ы продолжаем знакомство с ncurses. В прошлый раз мы
           научились создавать окна. На этом уроке мы рассмотрим
           другие важные возможности ncurses, такие, как управле-

ние цветом и поддержка мыши. Управление цветом Принципы работы с цветом в ncurses могут оказаться неожиданны- ми для тех, кто привык работать с цветами в растровых графических системах (и для тех, кто имеет опыт работы с текстовым режимом DOS/Windows). Библиотека ncurses инициализирует восемь базовых цветов: черный, красный, зеленый, желтый, синий (blue), ярко-крас- ный (magenta), голубой (cyan) и белый (базовыми называются цвета с обычным уровнем яркости). Поскольку к каждому базовому цве- ту можно применить атрибут повышенной яркости A_BOLD, всего мы получаем 16 цветов (в результате применения атрибута A_BOLD к черному цвету получается темно-серый цвет). Базовым цветам соответствуют константы COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN и COLOR_WHITE (для черного, красного, зеленого, желтого, синего, ярко-красного, голубого и белого цветов соответственно). Следует отметить, что фактические цвета в окне терминала зависят, прежде всего, от настроек самого терминала. Например, базовый желтый цвет (COLOR_YELLLOW) будет выглядеть скорее как коричневый, а для того, чтобы он стал собственно желтым, ему необходимо придать атрибут повышенной яркости. Библиотека ncurses позволяет опреде- лять собственные цвета с помощью функции init_color, но эта воз- можность поддерживается не всеми консолями. Позволяет ли консоль определять собственные цвета, можно выяснить с помощью функции can_change_color(). Цвета в ncurses объединяются в пары – цвет сим- волов (foreground) и цвет фона (background). Перед тем как печатать цветной текст, необходимо определить соответствующую цветовую пару и установить ее в качестве атрибута текста (так же, как устанавли- вается атрибут мигания или подчеркивания). Изменить цвет фона или символов независимо друг от друга нельзя, необходимо определять новую пару. Система управления цветами ncurses инициализирует две переменные – COLORS (количество базовых цветов) и COLOR_PAIRS (максимальное количество цветовых пар, которые можно определить одновременно). При работе с терминалом konsole эти переменные при- нимают значения 8 и 64 соответственно.

    Рассмотрим управление цветом на примере программы cursedcolors

(на диске – файл cursedcolors.c)

#include <termios.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <stdlib.h>
#include <curses.h>
void sig_winch(int signo)
{
   struct winsize size;
   ioctl(fileno(stdout), TIOCGWINSZ, (char *) &size);
   resizeterm(size.ws_row, size.ws_col);
}
int main(int argc, char ** argv)
{
   WINDOW * wnd;
   WINDOW * subwnd;
   initscr();
   signal(SIGWINCH, sig_winch);
   curs_set(FALSE);
   start_color();
   refresh();
   init_pair(1, COLOR_BLUE, COLOR_GREEN);
   init_pair(2, COLOR_YELLOW, COLOR_BLUE);
   wnd = newwin(5, 18, 2, 4);
   wattron(wnd, COLOR_PAIR(1));
   box(wnd, ‘|’, ‘-’);
   subwnd = derwin(wnd, 3, 16, 1, 1);
   wbkgd(subwnd, COLOR_PAIR(2));
   wattron(subwnd, A_BOLD);
   wprintw(subwnd, “Hello, brave new curses world!\n”);
   wrefresh(subwnd);
   wrefresh(wnd);
   delwin(subwnd);
   delwin(wnd);
   wmove(stdscr, 8, 1);
   printw(“Press any key to continue...”);
   refresh();
   getch();
   endwin();
   exit(EXIT_SUCCESS);
}
    Эта программа основана на программе cursedwindows из преды-

дущей статьи, так что многие ее части должны быть вам знакомы. Функция start_color() инициализирует управление цветом ncurses. Остальные функции, связанные с цветом, можно использовать только после вызова start_color(). Новые цветовые пары создаются с помо- щью функции init_pair(). Первым параметром init_pair() должен быть один из допустимых номеров пары (от 1 до COLOR_PAIRS-1). Вторым параметром функции init_pair() должна быть константа, обознача- ющая базовый цвет символа, а третьим – константа, обозначающая базовый цвет фона. Цветовая пара с номером 0 определена в ncurses как «белый на черном», и изменить ее нельзя. Мы создаем две пары цветов – «синие символы на зеленом фоне» под номером 1 и желтые символы на синем фоне (любимое сочетание цветов небезызвестного Питера Нортона) под номером 2. Номер цветовой пары служит ее иден- тификатором. Для того чтобы сделать выбранную цветовую пару атри- бутом выводимого текста, необходимо установить с помощью функции attron/wattron атрибут COLOR_PAIR(X), где X – номер цветовой пары. Атрибут COLOR_PAIR(X) можно комбинировать с другими, например, с атрибутом A_BOLD, который влияет на яркость цвета символов (но не на яркость цвета фона). Для того чтобы изменить яркость фона, необ- ходимо скомбинировать этот атрибут с A_REVERSE.

   Вызов функции
wattron(wnd, COLOR_PAIR(1));

устанавливает цвет фона и символов (цветовую пару 1) для «внешне- го» окна wnd, содержащего рамку. Теперь функция box() напечатает символы рамки с учетом заданных атрибутов цвета. Функция wbkgd() позволяет нам заполнить структуру данных, соответствующую массиву символов окна, различными атрибутами текста. Вызов

wbkgd(subwnd, COLOR_PAIR(2));

заполняет окно subwnd фоновым цветом из цветовой пары 2 и уста- навливает соответствующий цвет символов в окне. Помимо атрибута COLOR_PAIR(), этой функции можно передавать все те же комбина- ции атрибутов, что и wattron(). Атрибуты затем будут применены к тексту, выводимому в окне по умолчанию. Для того чтобы сделать цвет шрифта в окне subwnd ярким, мы вызываем функцию wattron() с атрибутом A_BOLD. Заметьте, что в функции wattron(), вызванной для окна subwnd, мы не указываем цветовую пару, поскольку в этом нет необходимости. Функция wbkgd() уже заполнила символьный массив окна subwnd нужными атрибутами цвета, и нам остается только указать атрибут яркости. В принципе, мы могли бы обойтись и без wattron(), если бы вызов wbkgd() выглядел так:

wbkgd(subwnd, COLOR_PAIR(2)|A_BOLD);
   Теперь мы можем распечатать текст в окне, что и делается с помо-

щью функции wprintw(). Для того чтобы символы, напечатанные в окне, стали видимыми, необходимо вызвать функцию wrefresh(). Теперь окно с текстом и обрамляющая его рамка сияют разными цветами (Рис. 1). Вы могли заметить, что если в обычном режиме окно терминала было, например, белым, то во время работы программы cursedcolors оно ста- новится черным. Это происходит потому, что по умолчанию при ини- циализации цвета окно stdscr заполняется атрибутами цветовой пары 0, как если бы была вызвана функция

wbkgd(stdscr, COLOR_PAIR(0));
   В результате надпись “Press any key to continue...”, которую мы

печатаем в окне stdscr, выводится белым шрифтом на черном фоне.

   При работе с цветом в ncurses следует помнить о том, что массивы

данных окон хранят только номера цветовых пар, применяемых к каж- дой ячейке, а не сами значения цветов. Из этого следует, что цвета уже напечатанного текста зависят от определения цветовых пар. Допустим, вы определили цветовую пару 1 как «желтый на синем» и напечатали какой-нибудь текст, используя эту пару в качестве атрибута. Если затем вы переопределите цветовую пару 1 как «красный на белом», цвета шрифта и фона в уже напечатанном тексте изменятся соответственно новому определению цветовой пары. Ввод данных в окнах С одной из функций ввода данных – getch(), мы уже познакомились. Мы также знаем, что этой функции соответствует «оконная» функция wgetch(). Обе они позволяют считывать отдельные символы. В отличие от них, семейство функций getstr()/getnstr/wgetstr()/wgetnstr() позволя- ет считывать целые строки. Буква n перед str в именах функций свиде- тельствует о том, что эти варианты функций позволяют указать макси- мальную длину строки-буфера и, тем самым, избежать его переполне- ния при вводе. Программа cursedinput (ее исходные тексты вы найдете в файле cursedinput.c) позволяет пользователю вводить строку текста и, затем, распечатывает введенную строку.

#include <termios.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <stdlib.h>
#include <curses.h>
#define MAX_NAME_LEN 15
void sig_winch(int signo)
{
   struct winsize size;
   ioctl(fileno(stdout), TIOCGWINSZ, (char *) &size);
   resizeterm(size.ws_row, size.ws_col);
}
int main(int argc, char ** argv)
{
   WINDOW * wnd;
   char name[MAX_NAME_LEN + 1];
   initscr();
   signal(SIGWINCH, sig_winch);
   curs_set(TRUE);
   start_color();
   refresh();
   init_pair(1, COLOR_YELLOW, COLOR_BLUE);
   wnd = newwin(5, 23, 2, 2);
   wbkgd(wnd, COLOR_PAIR(1));
   wattron(wnd, A_BOLD);
   wprintw(wnd, “Enter your name...\n”);
   wgetnstr(wnd, name, MAX_NAME_LEN);
   name[MAX_NAME_LEN] = 0;
   wprintw(wnd, “Hello, %s!”, name);
   wrefresh(wnd);
   delwin(wnd);
   curs_set(FALSE);
   move(8, 4);
   printw(“Press any key to continue...”);
   refresh();
   getch();
   endwin();
   exit(EXIT_SUCCESS);
}
   Поскольку в программе cursedinput пользователь

должен вводить данные, нам удобно сделать кур- сор видимым, что мы и делаем с помощью вызова curs_set(TRUE). Собственно ввод строки выполняется с помощью wgetnstr(). Первый параметр функции – иден- тификатор окна, в котором вводятся данные (то есть отображаются вводимые символы и курсор), второй параметр – строка-буфер, в которую записываются вве- денные символы, а третьим аргументом является дли- на буфера. При работе с программой вы увидите, что нельзя ввести число символов, превышающее MAX_NAME_LEN. Перед выводом строки “Press any key to continue...” мы снова прячем курсор.

   Режим работы терминала, в котором была запущена наша програм-

ма (она наследует его от программы-родителя), может повлиять на поведение некоторых функций ввода данных. В одной из предыдущих статей мы уже упоминали о каноническом и неканоническом режиме работы терминала. В каноническом режиме терминал буферизует вво- димые данные и передает их программе только после того, как поль- зователь нажмет Enter. В неканоническом режиме вводимые символы передаются программе немедленно. Режим работы терминала мож- но изменить с помощью функций cbreak() и nocbreak(). В результате вызова cbreak() терминал переходит в режим, в котором введенные символы предаются программе, не дожидаясь нажатия Enter, а клави- ша BackSpace игнорируется. Терминал выводится из режима cbreak() с помощью вызова функции nocbreak().

   Работая с программой cursedinput, вы, конечно, заметили, что

функция wgetnstr() допускает редактирование вводимой строки с помощью клавиши BackSpace. Поведение этой функции не зависит от режима cbreak()/nocbreak(), но поведение других функций, в частно- сти, getch(), зависит. В режиме nocbreak() функция getch() возвращает управление программе только после того, как пользователь нажмет Enter. Все наши программы (как и большинство программ ncurses) устанавливают режим cbreak(). Функция для ввода пароля Чтобы лучше изучить возможности ввода текста в библиотеке ncurses, напишем функцию, предназначенную для ввода пароля. Вместо вво- димых символов наша функция будет печатать на экране звездочки (Рис. 2). Cтроку можно будет редактировать с помощью клавиши BackSpace. Мы перепишем программу cursedinput так, чтобы вмес- то своего имени пользователь вводил пароль, кото- рый затем сверяется с константой, заданной в про- грамме, и в зависимости от того, совпадут они или нет, будет выводиться сообщение о предоставлении доступа или отказе в нем. Исходный текст програм- мы вы найдете в файле cursedpassword.c. Мы при- водим только фрагмент, изменившийся по сравнению с программой cursedinput.

keypad(wnd, TRUE);
wprintw(wnd, “Enter password...\n”);
 get_password(wnd, password, MAX_LEN);
 wattron(wnd, A_BLINK);
 if (strcmp(password, RIGHT_PASSWORD) == 0)
 wprintw(wnd, “ACCESS GRANTED!”);
 else
 wprintw(wnd, “ACCESS DENIED!”);
     Определенная нами функция get_password() принимает три пара-

метра – идентификатор окна, в котором выполняется ввод, адрес буфера, в который записываются вводимые символы и число, ука- зывающее длину буфера (вместе с завершающим нулем). Прототип функции strcmp(), которую мы используем для сравнения пере- данной пользователем строки и пароля, находится в файле string.h. Рассмотрим теперь саму функцию get_password():

void get_password(WINDOW * win, char * password, int max_len)
{
    int i = 0;
    int ch;
    while (((ch = wgetch(win)) != 10) && (i < max_len-1)) {
      if (ch == KEY_BACKSPACE) {
        int x, y;
        if (i==0) continue;
        getyx(win, y, x);
        mvwaddch(win, y, x-1, ‘ ‘);
        wrefresh(win);
        wmove(win, y, x-1);
        i--;
        continue;
      }
      password[i++] = ch;
      wechochar(win, ‘*’);
    }
    password[i] = 0;
    wechochar(win, ‘\n’);
}
    Функция считывает символы из входного потока с помощью функ-

ции wgetch() до тех пор, пока пользователь не нажмет Enter, или пока длина введенной строки не сравняется с максимально допустимой. Для вывода отдельных символов в окно можно применить функцию waddch(), однако мы используем функцию wechochar(), которая экви- валентна вызову waddch() с последующим вызовом wrefresh(). Самая сложная часть функции get_password() связана с обработкой нажатия клавиши BackSpace. Прежде всего, необходимо получить код этой специальной клавиши. По умолчанию при нажатии на специальные клавиши, такие как стрелки, клавиши F1-F12 или BackSpace, терминал генерирует последовательность кодов, представляющих так называе- мую Esc-последовательность клавиши. Для того чтобы заменить Esc- последовательность одним специальным кодом, необходимо вызвать функцию keypad() с ненулевым вторым параметром (что мы и делаем в главной функции программы). Первым параметром keypad() должен быть идентификатор окна (в нашем случае – wnd).

    Вызов keypad() с ненулевым вторым параметром приводит к тому,

что клавиши F1-F12 генерируют коды KEY_F1-KEY_F12, клавиши со стрелками – коды KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, а клавиша BackSpace – код KEY_BACKSPACE. Описание других кодов специальных клавиш и событий вы найдете на странице man функции getch(). Получив в потоке ввода код KEY_BACKSPACE, мы должны выполнить несколько операций, прежде всего – стереть только что напечатанную звездочку. Для этого нужно получить текущие координаты курсора, сдвинуть его на одну позицию влево и напечатать пробел. Затем курсор снова нужно сдвинуть на одну позицию влево. Получить текущие координаты курсо- ра в окне можно с помощью макроса getyx(). Его первым параметром является идентификатор окна, вторым – переменная, в которой макрос сохранит значение строки курсора, третьим – переменная, в которой будет сохранен столбец курсора. Именно потому, что getyx() – макрос, мы передаем для получения значений переменные, а не указатели на них. Функция mvwaddch() сочетает перемещение курсора и вывод сим- вола. Первый параметр функции – идентификатор окна. За ним следуют новые координаты курсора – строка и столбец. Последним параметром функции является символ, который нужно напечатать. После того, как мы привели в порядок экран, мы уменьшаем на единицу счетчик вве- денных символов (переменная i). Если переменная i равна нулю, никаких действий не выполняется. Наша функция get_password() будет работать правильно только в режиме cbreak(). Следует отметить, что функция не будет корректно работать с клавишей BackSpace, если при вводе пароля произошел перенос строки. Окна и мыши Еще одной полезной возможностью, которую ncurses предостав- ляет программистам, является поддержка мыши в окне терминала. Рассмотрим программу cursedmouse (на диске – файл cursedmouse.c), которая регистрирует щелчки левой кнопкой мыши, сделанные пользо- вателем в окне терминала, и распечатывает координаты курсора мыши в момент щелчка. Ради простоты мы не создаем в этой программе никаких окон (кроме окна stdscr, которое создается автоматически).

#include <termios.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <stdlib.h>
#include <curses.h>
void sig_winch(int signo)
{
   struct winsize size;
   ioctl(fileno(stdout), TIOCGWINSZ, (char *) &size);
   resizeterm(size.ws_row, size.ws_col);
   nodelay(stdscr, 1);
   while (wgetch(stdscr) != ERR);
   nodelay(stdscr, 0);
}
int main(int argc, char ** argv)
{
  initscr();
  signal(SIGWINCH, sig_winch);
  keypad(stdscr, 1);
  mousemask(BUTTON1_CLICKED, NULL);
  move(2,2);
  printw(“Press the left mouse button to test mouse\n”);
  printw(“Press any key to quit...\n”);
  refresh();
  while (wgetch(stdscr) == KEY_MOUSE) {
     MEVENT event;
     getmouse(&event);
     move(0, 0);
     printw(“Mouse button pressed at %i, %i\n”, event.x, event.y);
     refresh();
     move(event.y, event.x);
  }
  endwin();
  exit(EXIT_SUCCESS);
}
   Поддержка мыши в ncurses инициализируется с помощью функции

mousemask(). Первым параметром этой функции должна быть маска событий мыши, которые следует обрабатывать в программе, вторым параметром может быть указатель на переменную, в которой функция сохранит прежнюю маску событий, или NULL, если прежняя маска нам не нужна. Каждому событию мыши в ncurses соответствует своя кон- станта. Если мы хотим обрабатывать несколько событий мыши, при вызове функции mousemask() мы должны объединить соответствую- щие константы операцией «ИЛИ» (|). Повторный вызов mousemask() приведет к установке новой маски событий (вызов mousemask() с пер- вым аргументом, равным 0, отключает поддержку мыши).

   Рассмотрим некоторые константы, определяющие события мыши.

Константа BUTTON1_CLICKED соответствует щелчку левой кнопкой мыши (точнее говоря – щелчку первой кнопкой; будет ли пер- вая кнопка левой кнопкой мыши, зависит от настроек). Константа BUTTON2_PRESSED указывает, что программа должна реагировать на нажатие пользователем второй (обычно – правой) кнопки мыши. Константа REPORT_MOUSE_POSITION указывает, что мы хотим отсле- живать движение указателя мыши, а константа ALL_MOUSE_EVENTS заставляет программу реагировать на все события мыши. Более пол- ное описание констант событий вы найдете на man-странице функ- ции mousemask(3x). В качестве результирующего значения функция mousemask() возвращает маску из выбранных нами событий, которые фактически могут быть обработаны. Если функция возвращает 0, зна- чит, работа с мышью в консоли не поддерживается.

   Каждый раз, когда в системе происходит одно из «наблюдаемых»

событий мыши, в потоке ввода программы появляется специальный символ KEY_MOUSE. Точнее говоря, по умолчанию, в потоке ввода программы Linux появляется Esc-последовательность, соответствую- щая этому символу, так что в программе cursedmouse мы тоже должны вызвать функцию keypad() с ненулевым вторым параметром.

    После того, как мы считали из потока ввода специальный символ

KEY_MOUSE, мы можем получить более подробную информацию о вызвавшем его событии мыши. Делается это с помощью функции getmouse(). Аргументом функции getmouse() должен быть указатель на структуру MEVENT. Определение структуры MEVENT выглядит сле- дующим образом:

typedef struct {
short id; /* идентификатор для различения нескольких устройств */
int x, y, z;  /* координаты указателя в момент события */
mmask_t bstate; /* маска событий */
} MEVENT;
    Координаты указателя возвращаются в формате строка (y), стол-

бец (x). Поле bstate содержит один-единственный бит, соответствую- щий константе события.

    В программе cursedmouse мы считываем поступающие во входной

поток символы в цикле. Если во входном потоке появляется символ KEY_MOUSE, мы, с помощью функции getmouse(), определяем коор- динаты указателя мыши в момент возникновения события и распеча- тываем их в левом верхнем углу экрана, а затем переводим курсор туда, куда указывала мышь в момент возникновения события. Появление в потоке ввода символа, отличного от KEY_MOUSE, приводит к заверше- нию программы.

    Осталось обратить внимание читателя на обработку сигнала

SIGWINCH в программе cursedmouse. Изменение размеров экрана при включенной поддержке мыши приведет к появлению в потоке ввода символов Esc-последовательности специального символа KEY_ RESIZE (это еще один способ предупредить программу о том, что размеры экрана изменились). В программе cursedmouse появление в потоке ввода каких-либо кодов, отличных от KEY_MOUSE, приводит к завершению программы. Для того чтобы избежать этого, в обра- ботчике сигнала SIGWINCH мы опустошаем поток ввода с помощью функции flushinp(). Естественно, этот способ спасения программы от досрочного завершения годится далеко не всегда, ведь в момент изменения размеров окна терминала поток ввода может содержать важную информацию. Все это лишний раз демонстрирует, насколь- ко нетривиальной является обработка изменения размеров экрана в программах ncurses.

    На этом я заканчиваю (честное слово!) серию статей, посвященную

Unix API. Я благодарю вас за внимание, проявленное к этой серии, и надеюсь, что с помощью моих статей вы получили некоторое общее представление о низкоуровневом программировании в Linux/Unix, а самое главное, смогли ответить на вопрос – нужно ли вам все это. LXF

Персональные инструменты
купить
подписаться
Яндекс.Метрика