LXF92:Unix API

Материал из Linuxformat.

(Различия между версиями)
Перейти к: навигация, поиск
Строка 140: Строка 140:
[[Изображение:LXF92_api1.jpg|Рис. 1]]
[[Изображение:LXF92_api1.jpg|Рис. 1]]
-
В результате надпись “Press any key to continue...”, которую мы
+
В результате надпись ''“Press any key to continue...”'', которую мы
печатаем в окне '''stdscr''', выводится белым шрифтом на черном фоне.
печатаем в окне '''stdscr''', выводится белым шрифтом на черном фоне.
Строка 156: Строка 156:
С одной из функций ввода данных – '''getch()''', мы уже познакомились.
С одной из функций ввода данных – '''getch()''', мы уже познакомились.
Мы также знаем, что этой функции соответствует «оконная» функция
Мы также знаем, что этой функции соответствует «оконная» функция
-
wgetch(). Обе они позволяют считывать отдельные символы. В отличие
+
'''wgetch()'''. Обе они позволяют считывать отдельные символы. В отличие
-
от них, семейство функций getstr()/getnstr/wgetstr()/wgetnstr() позволя-
+
от них, семейство функций '''getstr()/getnstr/wgetstr()/wgetnstr()''' позволяет считывать целые строки. Буква '''n''' перед '''str''' в именах функций свидетельствует о том, что эти варианты функций позволяют указать максимальную длину строки-буфера и, тем самым, избежать его переполнения при вводе. Программа ''cursedinput'' (ее исходные тексты вы найдете
-
ет считывать целые строки. Буква n перед str в именах функций свиде-
+
в файле '''cursedinput.c''') позволяет пользователю вводить строку текста и, затем, распечатывает введенную строку.
-
тельствует о том, что эти варианты функций позволяют указать макси-
+
 
-
мальную длину строки-буфера и, тем самым, избежать его переполне-
+
<code>
-
ния при вводе. Программа cursedinput (ее исходные тексты вы найдете
+
-
в файле cursedinput.c) позволяет пользователю вводить строку текста
+
-
и, затем, распечатывает введенную строку.
+
#include <termios.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
Строка 203: Строка 200:
exit(EXIT_SUCCESS);
exit(EXIT_SUCCESS);
}
}
-
Поскольку в программе cursedinput пользователь
+
</code>
-
должен вводить данные, нам удобно сделать кур-
+
 
-
сор видимым, что мы и делаем с помощью вызова
+
Поскольку в программе ''cursedinput'' пользователь
-
curs_set(TRUE). Собственно ввод строки выполняется с
+
должен вводить данные, нам удобно сделать курсор видимым, что мы и делаем с помощью вызова
-
помощью wgetnstr(). Первый параметр функции – иден-
+
'''curs_set(TRUE)'''. Собственно ввод строки выполняется с
-
тификатор окна, в котором вводятся данные (то есть
+
помощью '''wgetnstr()'''. Первый параметр функции – идентификатор окна, в котором вводятся данные (то есть
отображаются вводимые символы и курсор), второй
отображаются вводимые символы и курсор), второй
-
параметр – строка-буфер, в которую записываются вве-
+
параметр – строка-буфер, в которую записываются введенные символы, а третьим аргументом является длина буфера. При работе с программой вы увидите, что
-
денные символы, а третьим аргументом является дли-
+
нельзя ввести число символов, превышающее '''MAX_NAME_LEN'''. Перед
-
на буфера. При работе с программой вы увидите, что
+
выводом строки ''“Press any key to continue...”'' мы снова прячем курсор.
-
нельзя ввести число символов, превышающее MAX_NAME_LEN. Перед
+
 
-
выводом строки “Press any key to continue...” мы снова прячем курсор.
+
Режим работы терминала, в котором была запущена наша программа (она наследует его от программы-родителя), может повлиять на
-
Режим работы терминала, в котором была запущена наша програм-
+
-
ма (она наследует его от программы-родителя), может повлиять на
+
поведение некоторых функций ввода данных. В одной из предыдущих
поведение некоторых функций ввода данных. В одной из предыдущих
статей мы уже упоминали о каноническом и неканоническом режиме
статей мы уже упоминали о каноническом и неканоническом режиме
-
работы терминала. В каноническом режиме терминал буферизует вво-
+
работы терминала. В каноническом режиме терминал буферизует вводимые данные и передает их программе только после того, как пользователь нажмет '''Enter'''. В неканоническом режиме вводимые символы
-
димые данные и передает их программе только после того, как поль-
+
передаются программе немедленно. Режим работы терминала можно изменить с помощью функций '''cbreak()''' и '''nocbreak()'''. В результате
-
зователь нажмет Enter. В неканоническом режиме вводимые символы
+
вызова '''cbreak()''' терминал переходит в режим, в котором введенные
-
передаются программе немедленно. Режим работы терминала мож-
+
символы предаются программе, не дожидаясь нажатия '''Enter''', а клавиша '''BackSpace''' игнорируется. Терминал выводится из режима '''cbreak()''' с
-
но изменить с помощью функций cbreak() и nocbreak(). В результате
+
помощью вызова функции '''nocbreak()'''.
-
вызова cbreak() терминал переходит в режим, в котором введенные
+
 
-
символы предаются программе, не дожидаясь нажатия Enter, а клави-
+
Работая с программой ''cursedinput'', вы, конечно, заметили, что
-
ша BackSpace игнорируется. Терминал выводится из режима cbreak() с
+
функция '''wgetnstr()''' допускает редактирование вводимой строки с
-
помощью вызова функции nocbreak().
+
помощью клавиши '''BackSpace'''. Поведение этой функции не зависит от
-
Работая с программой cursedinput, вы, конечно, заметили, что
+
режима '''cbreak()/nocbreak()''', но поведение других функций, в частности, '''getch()''', зависит. В режиме '''nocbreak()''' функция '''getch()''' возвращает
-
функция wgetnstr() допускает редактирование вводимой строки с
+
-
помощью клавиши BackSpace. Поведение этой функции не зависит от
+
-
режима cbreak()/nocbreak(), но поведение других функций, в частно-
+
-
сти, getch(), зависит. В режиме nocbreak() функция getch() возвращает
+
управление программе только после того, как пользователь нажмет
управление программе только после того, как пользователь нажмет
-
Enter. Все наши программы (как и большинство программ ncurses)
+
'''Enter'''. Все наши программы (как и большинство программ ''ncurses'')
-
устанавливают режим cbreak().
+
устанавливают режим '''cbreak()'''.
-
Функция для ввода пароля
+
 
-
Чтобы лучше изучить возможности ввода текста в библиотеке ncurses,
+
===Функция для ввода пароля===
-
напишем функцию, предназначенную для ввода пароля. Вместо вво-
+
 
-
димых символов наша функция будет печатать на
+
Чтобы лучше изучить возможности ввода текста в библиотеке ''ncurses'',
 +
напишем функцию, предназначенную для ввода пароля. Вместо вводимых символов наша функция будет печатать на
экране звездочки (Рис. 2). Cтроку можно будет
экране звездочки (Рис. 2). Cтроку можно будет
-
редактировать с помощью клавиши BackSpace. Мы
+
редактировать с помощью клавиши '''BackSpace'''. Мы
-
перепишем программу cursedinput так, чтобы вмес-
+
перепишем программу ''cursedinput'' так, чтобы вместо своего имени пользователь вводил пароль, который затем сверяется с константой, заданной в программе, и в зависимости от того, совпадут они или
-
то своего имени пользователь вводил пароль, кото-
+
-
рый затем сверяется с константой, заданной в про-
+
-
грамме, и в зависимости от того, совпадут они или
+
нет, будет выводиться сообщение о предоставлении
нет, будет выводиться сообщение о предоставлении
-
доступа или отказе в нем. Исходный текст програм-
+
доступа или отказе в нем. Исходный текст программы вы найдете в файле '''cursedpassword.c'''. Мы приводим только фрагмент, изменившийся по сравнению с программой ''cursedinput''.
-
мы вы найдете в файле cursedpassword.c. Мы при-
+
 
-
водим только фрагмент, изменившийся по сравнению с программой
+
[[Изображение:LXF92_api2.jpg|Рис. 2]]
-
cursedinput.
+
 
 +
<code>
keypad(wnd, TRUE);
keypad(wnd, TRUE);
wprintw(wnd, “Enter password...\n”);
wprintw(wnd, “Enter password...\n”);
Строка 259: Строка 249:
else
else
wprintw(wnd, “ACCESS DENIED!”);
wprintw(wnd, “ACCESS DENIED!”);
-
Определенная нами функция get_password() принимает три пара-
+
</code>
-
метра – идентификатор окна, в котором выполняется ввод, адрес
+
 
-
буфера, в который записываются вводимые символы и число, ука-
+
Определенная нами функция '''get_password()''' принимает три параметра – идентификатор окна, в котором выполняется ввод, адрес буфера, в который записываются вводимые символы и число, указывающее длину буфера (вместе с завершающим нулем). Прототип
-
зывающее длину буфера (вместе с завершающим нулем). Прототип
+
функции '''strcmp()''', которую мы используем для сравнения переданной пользователем строки и пароля, находится в файле '''string.h'''.
-
функции strcmp(), которую мы используем для сравнения пере-
+
Рассмотрим теперь саму функцию '''get_password()''':
-
данной пользователем строки и пароля, находится в файле string.h.
+
 
-
Рассмотрим теперь саму функцию get_password():
+
<code>
void get_password(WINDOW * win, char * password, int max_len)
void get_password(WINDOW * win, char * password, int max_len)
{
{
Строка 287: Строка 277:
wechochar(win, ‘\n’);
wechochar(win, ‘\n’);
}
}
-
Функция считывает символы из входного потока с помощью функ-
+
</code>
-
ции wgetch() до тех пор, пока пользователь не нажмет Enter, или пока
+
 
 +
Функция считывает символы из входного потока с помощью функции '''wgetch()''' до тех пор, пока пользователь не нажмет Enter, или пока
длина введенной строки не сравняется с максимально допустимой.
длина введенной строки не сравняется с максимально допустимой.
Для вывода отдельных символов в окно можно применить функцию
Для вывода отдельных символов в окно можно применить функцию
-
waddch(), однако мы используем функцию wechochar(), которая экви-
+
'''waddch()''', однако мы используем функцию '''wechochar()''', которая эквивалентна вызову '''waddch()''' с последующим вызовом '''wrefresh()'''. Самая
-
валентна вызову waddch() с последующим вызовом wrefresh(). Самая
+
сложная часть функции '''get_password()''' связана с обработкой нажатия
-
сложная часть функции get_password() связана с обработкой нажатия
+
клавиши '''BackSpace'''. Прежде всего, необходимо получить код этой
-
клавиши BackSpace. Прежде всего, необходимо получить код этой
+
специальной клавиши. По умолчанию при нажатии на специальные
специальной клавиши. По умолчанию при нажатии на специальные
-
клавиши, такие как стрелки, клавиши F1-F12 или BackSpace, терминал
+
клавиши, такие как стрелки, клавиши '''F1-F12''' или '''BackSpace''', терминал
-
генерирует последовательность кодов, представляющих так называе-
+
генерирует последовательность кодов, представляющих так называемую Esc-последовательность клавиши. Для того чтобы заменить Esc-последовательность одним специальным кодом, необходимо вызвать
-
мую Esc-последовательность клавиши. Для того чтобы заменить Esc-
+
функцию '''keypad()''' с ненулевым вторым параметром (что мы и делаем
-
последовательность одним специальным кодом, необходимо вызвать
+
в главной функции программы). Первым параметром '''keypad()''' должен
-
функцию keypad() с ненулевым вторым параметром (что мы и делаем
+
быть идентификатор окна (в нашем случае – '''wnd''').
-
в главной функции программы). Первым параметром keypad() должен
+
 
-
быть идентификатор окна (в нашем случае – wnd).
+
Вызов '''keypad()''' с ненулевым вторым параметром приводит к тому,
-
Вызов keypad() с ненулевым вторым параметром приводит к тому,
+
что клавиши '''F1-F12''' генерируют коды '''KEY_F1-KEY_F12''', клавиши со
-
что клавиши F1-F12 генерируют коды KEY_F1-KEY_F12, клавиши со
+
стрелками – коды '''KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT''', а клавиша
-
стрелками – коды KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, а клавиша
+
'''BackSpace''' – код '''KEY_BACKSPACE'''. Описание других кодов специальных
-
BackSpace – код KEY_BACKSPACE. Описание других кодов специальных
+
клавиш и событий вы найдете на странице ''man'' функции '''getch('''). Получив
-
клавиш и событий вы найдете на странице man функции getch(). Получив
+
в потоке ввода код '''KEY_BACKSPACE''', мы должны выполнить несколько
-
в потоке ввода код KEY_BACKSPACE, мы должны выполнить несколько
+
операций, прежде всего – стереть только что напечатанную звездочку.
операций, прежде всего – стереть только что напечатанную звездочку.
Для этого нужно получить текущие координаты курсора, сдвинуть его
Для этого нужно получить текущие координаты курсора, сдвинуть его
на одну позицию влево и напечатать пробел. Затем курсор снова нужно
на одну позицию влево и напечатать пробел. Затем курсор снова нужно
сдвинуть на одну позицию влево. Получить текущие координаты курсо-
сдвинуть на одну позицию влево. Получить текущие координаты курсо-
-
ра в окне можно с помощью макроса getyx(). Его первым параметром
+
ра в окне можно с помощью макроса '''getyx()''. Его первым параметром
является идентификатор окна, вторым – переменная, в которой макрос
является идентификатор окна, вторым – переменная, в которой макрос
сохранит значение строки курсора, третьим – переменная, в которой
сохранит значение строки курсора, третьим – переменная, в которой
-
будет сохранен столбец курсора. Именно потому, что getyx() – макрос,
+
будет сохранен столбец курсора. Именно потому, что '''getyx()''' – макрос,
мы передаем для получения значений переменные, а не указатели на
мы передаем для получения значений переменные, а не указатели на
-
них. Функция mvwaddch() сочетает перемещение курсора и вывод сим-
+
них. Функция '''mvwaddch()''' сочетает перемещение курсора и вывод символа. Первый параметр функции – идентификатор окна. За ним следуют
-
вола. Первый параметр функции – идентификатор окна. За ним следуют
+
новые координаты курсора – строка и столбец. Последним параметром
новые координаты курсора – строка и столбец. Последним параметром
функции является символ, который нужно напечатать. После того, как
функции является символ, который нужно напечатать. После того, как
-
мы привели в порядок экран, мы уменьшаем на единицу счетчик вве-
+
мы привели в порядок экран, мы уменьшаем на единицу счетчик введенных символов (переменная '''i'''). Если переменная '''i''' равна нулю, никаких
-
денных символов (переменная i). Если переменная i равна нулю, никаких
+
действий не выполняется. Наша функция '''get_password()''' будет работать
-
действий не выполняется. Наша функция get_password() будет работать
+
правильно только в режиме '''cbreak()'''. Следует отметить, что функция не
-
правильно только в режиме cbreak(). Следует отметить, что функция не
+
будет корректно работать с клавишей '''BackSpace''', если при вводе пароля
-
будет корректно работать с клавишей BackSpace, если при вводе пароля
+
произошел перенос строки.
произошел перенос строки.
-
Окна и мыши
+
 
-
Еще одной полезной возможностью, которую ncurses предостав-
+
===Окна и мыши===
-
ляет программистам, является поддержка мыши в окне терминала.
+
 
-
Рассмотрим программу cursedmouse (на диске – файл cursedmouse.c),
+
Еще одной полезной возможностью, которую ''ncurses'' предоставляет программистам, является поддержка мыши в окне терминала.
-
которая регистрирует щелчки левой кнопкой мыши, сделанные пользо-
+
Рассмотрим программу ''cursedmouse'' (на диске – файл '''cursedmouse.c'''),
-
вателем в окне терминала, и распечатывает координаты курсора мыши
+
которая регистрирует щелчки левой кнопкой мыши, сделанные пользователем в окне терминала, и распечатывает координаты курсора мыши в момент щелчка. Ради простоты мы не создаем в этой программе
-
в момент щелчка. Ради простоты мы не создаем в этой программе
+
никаких окон (кроме окна '''stdscr''', которое создается автоматически).
-
никаких окон (кроме окна stdscr, которое создается автоматически).
+
 
 +
<code>
#include <termios.h>
#include <termios.h>
#include <sys/ioctl.h>
#include <sys/ioctl.h>
Строка 371: Строка 359:
exit(EXIT_SUCCESS);
exit(EXIT_SUCCESS);
}
}
-
Поддержка мыши в ncurses инициализируется с помощью функции
+
</code>
-
mousemask(). Первым параметром этой функции должна быть маска
+
 
 +
Поддержка мыши в ncurses инициализируется с помощью функции
 +
'''mousemask()'''. Первым параметром этой функции должна быть маска
событий мыши, которые следует обрабатывать в программе, вторым
событий мыши, которые следует обрабатывать в программе, вторым
параметром может быть указатель на переменную, в которой функция
параметром может быть указатель на переменную, в которой функция
-
сохранит прежнюю маску событий, или NULL, если прежняя маска нам
+
сохранит прежнюю маску событий, или '''NULL''', если прежняя маска нам
-
не нужна. Каждому событию мыши в ncurses соответствует своя кон-
+
не нужна. Каждому событию мыши в ''ncurses'' соответствует своя константа. Если мы хотим обрабатывать несколько событий мыши, при вызове функции '''mousemask()''' мы должны объединить соответствующие константы операцией «'''ИЛИ'''» ('''|'''). Повторный вызов '''mousemask()''' приведет к установке новой маски событий (вызов '''mousemask()''' с первым аргументом, равным 0, отключает поддержку мыши).
-
станта. Если мы хотим обрабатывать несколько событий мыши, при
+
 
-
вызове функции mousemask() мы должны объединить соответствую-
+
Рассмотрим некоторые константы, определяющие события мыши.
-
щие константы операцией «ИЛИ» (|). Повторный вызов mousemask()
+
Константа '''BUTTON1_CLICKED''' соответствует щелчку левой кнопкой
-
приведет к установке новой маски событий (вызов mousemask() с пер-
+
мыши (точнее говоря – щелчку первой кнопкой; будет ли первая кнопка левой кнопкой мыши, зависит от настроек). Константа
-
вым аргументом, равным 0, отключает поддержку мыши).
+
'''BUTTON2_PRESSED''' указывает, что программа должна реагировать
-
Рассмотрим некоторые константы, определяющие события мыши.
+
-
Константа BUTTON1_CLICKED соответствует щелчку левой кнопкой
+
-
мыши (точнее говоря – щелчку первой кнопкой; будет ли пер-
+
-
вая кнопка левой кнопкой мыши, зависит от настроек). Константа
+
-
BUTTON2_PRESSED указывает, что программа должна реагировать
+
на нажатие пользователем второй (обычно – правой) кнопки мыши.
на нажатие пользователем второй (обычно – правой) кнопки мыши.
-
Константа REPORT_MOUSE_POSITION указывает, что мы хотим отсле-
+
Константа '''REPORT_MOUSE_POSITION''' указывает, что мы хотим отслеживать движение указателя мыши, а константа '''ALL_MOUSE_EVENTS''' заставляет программу реагировать на все события мыши. Более полное описание констант событий вы найдете на ''man''-странице функции '''mousemask(3x)'''. В качестве результирующего значения функция
-
живать движение указателя мыши, а константа ALL_MOUSE_EVENTS
+
'''mousemask()''' возвращает маску из выбранных нами событий, которые
-
заставляет программу реагировать на все события мыши. Более пол-
+
фактически могут быть обработаны. Если функция возвращает 0, значит, работа с мышью в консоли не поддерживается.
-
ное описание констант событий вы найдете на man-странице функ-
+
 
-
ции mousemask(3x). В качестве результирующего значения функция
+
Каждый раз, когда в системе происходит одно из «наблюдаемых»
-
mousemask() возвращает маску из выбранных нами событий, которые
+
-
фактически могут быть обработаны. Если функция возвращает 0, зна-
+
-
чит, работа с мышью в консоли не поддерживается.
+
-
Каждый раз, когда в системе происходит одно из «наблюдаемых»
+
событий мыши, в потоке ввода программы появляется специальный
событий мыши, в потоке ввода программы появляется специальный
-
символ KEY_MOUSE. Точнее говоря, по умолчанию, в потоке ввода
+
символ '''KEY_MOUSE'''. Точнее говоря, по умолчанию, в потоке ввода
-
программы Linux появляется Esc-последовательность, соответствую-
+
программы Linux появляется Esc-последовательность, соответствующая этому символу, так что в программе ''cursedmouse'' мы тоже должны вызвать функцию '''keypad()''' с ненулевым вторым параметром.
-
щая этому символу, так что в программе cursedmouse мы тоже должны
+
 
-
вызвать функцию keypad() с ненулевым вторым параметром.
+
После того, как мы считали из потока ввода специальный символ
-
После того, как мы считали из потока ввода специальный символ
+
'''KEY_MOUSE''', мы можем получить более подробную информацию о
-
KEY_MOUSE, мы можем получить более подробную информацию о
+
вызвавшем его событии мыши. Делается это с помощью функции
вызвавшем его событии мыши. Делается это с помощью функции
-
getmouse(). Аргументом функции getmouse() должен быть указатель
+
'''getmouse()'''. Аргументом функции '''getmouse()''' должен быть указатель
-
на структуру MEVENT. Определение структуры MEVENT выглядит сле-
+
на структуру '''MEVENT'''. Определение структуры '''MEVENT''' выглядит следующим образом:
-
дующим образом:
+
 
 +
<code>
typedef struct {
typedef struct {
short id; /* идентификатор для различения нескольких устройств */
short id; /* идентификатор для различения нескольких устройств */
Строка 413: Строка 394:
mmask_t bstate; /* маска событий */
mmask_t bstate; /* маска событий */
} MEVENT;
} MEVENT;
-
Координаты указателя возвращаются в формате строка (y), стол-
+
</code>
-
бец (x). Поле bstate содержит один-единственный бит, соответствую-
+
 
-
щий константе события.
+
Координаты указателя возвращаются в формате строка ('''y'''), столбец ('''x'''). Поле '''bstate''' содержит один-единственный бит, соответствующий константе события.
-
В программе cursedmouse мы считываем поступающие во входной
+
 
 +
В программе ''cursedmouse'' мы считываем поступающие во входной
поток символы в цикле. Если во входном потоке появляется символ
поток символы в цикле. Если во входном потоке появляется символ
-
KEY_MOUSE, мы, с помощью функции getmouse(), определяем коор-
+
'''KEY_MOUSE''', мы, с помощью функции '''getmouse()''', определяем координаты указателя мыши в момент возникновения события и распечатываем их в левом верхнем углу экрана, а затем переводим курсор туда,
-
динаты указателя мыши в момент возникновения события и распеча-
+
-
тываем их в левом верхнем углу экрана, а затем переводим курсор туда,
+
куда указывала мышь в момент возникновения события. Появление в
куда указывала мышь в момент возникновения события. Появление в
-
потоке ввода символа, отличного от KEY_MOUSE, приводит к заверше-
+
потоке ввода символа, отличного от '''KEY_MOUSE''', приводит к завершению программы.
-
нию программы.
+
 
-
Осталось обратить внимание читателя на обработку сигнала
+
Осталось обратить внимание читателя на обработку сигнала
-
SIGWINCH в программе cursedmouse. Изменение размеров экрана
+
'''SIGWINCH''' в программе ''cursedmouse''. Изменение размеров экрана
при включенной поддержке мыши приведет к появлению в потоке
при включенной поддержке мыши приведет к появлению в потоке
-
ввода символов Esc-последовательности специального символа KEY_
+
ввода символов Esc-последовательности специального символа '''KEY_RESIZE''' (это еще один способ предупредить программу о том, что
-
RESIZE (это еще один способ предупредить программу о том, что
+
размеры экрана изменились). В программе ''cursedmouse'' появление в
-
размеры экрана изменились). В программе cursedmouse появление в
+
потоке ввода каких-либо кодов, отличных от '''KEY_MOUSE''', приводит
-
потоке ввода каких-либо кодов, отличных от KEY_MOUSE, приводит
+
к завершению программы. Для того чтобы избежать этого, в обработчике сигнала '''SIGWINCH''' мы опустошаем поток ввода с помощью
-
к завершению программы. Для того чтобы избежать этого, в обра-
+
функции '''flushinp()'''. Естественно, этот способ спасения программы
-
ботчике сигнала SIGWINCH мы опустошаем поток ввода с помощью
+
-
функции flushinp(). Естественно, этот способ спасения программы
+
от досрочного завершения годится далеко не всегда, ведь в момент
от досрочного завершения годится далеко не всегда, ведь в момент
изменения размеров окна терминала поток ввода может содержать
изменения размеров окна терминала поток ввода может содержать
-
важную информацию. Все это лишний раз демонстрирует, насколь-
+
важную информацию. Все это лишний раз демонстрирует, насколько нетривиальной является обработка изменения размеров экрана в
-
ко нетривиальной является обработка изменения размеров экрана в
+
программах ''ncurses''.
-
программах ncurses.
+
 
-
На этом я заканчиваю (честное слово!) серию статей, посвященную
+
На этом я заканчиваю (честное слово!) серию статей, посвященную
Unix API. Я благодарю вас за внимание, проявленное к этой серии, и
Unix API. Я благодарю вас за внимание, проявленное к этой серии, и
надеюсь, что с помощью моих статей вы получили некоторое общее
надеюсь, что с помощью моих статей вы получили некоторое общее
представление о низкоуровневом программировании в Linux/Unix, а
представление о низкоуровневом программировании в Linux/Unix, а
самое главное, смогли ответить на вопрос – нужно ли вам все это. '''LXF'''
самое главное, смогли ответить на вопрос – нужно ли вам все это. '''LXF'''

Версия 10:57, 19 ноября 2008


Программирование для UNIX

Листинги и текст на сайте автора

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));

Рис. 1

В результате надпись “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.

Рис. 2

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

Личные инструменты
  • Купить электронную версию
  • Подписаться на бумажную версию