Ввод текста. Взаимодействие с клавиатурой
Программная обработка скан-кодов и кодов клавиш
Исходный код, отвечающий за обработку скан-кодов и кодов клавиш, входит в состав многих программ, распространяемых согласно свободной лицензии. К ним относятся kbd (мы будем рассматривать версию 1.15.3) и console-tools (версия 0.2.3, например). Архивы с исходными текстами и kbd и console-tools содержат файл showkey.c , в котором реализуется механизм вывода скан-кодов и кодов (keycodes) клавиш, генерируемых когда пользователь нажимает кнопки на клавиатуре.
И kbd и console-tools являются приложениями с текстовым интерфейсом. В среде GNU/Linux такие программы почти всегда включают в свой состав стандартную функцию getopt_long() , осуществляющую синтаксический анализ аргументов командной строки. Достаточно подробные пояснения по поводу использования этой функции даны в работе [МитчеллОулдемСамьюэл2003, С. 31-34].
Применяется ли getopt_long() в kbd и console-tools ? Да. Код в файле src/showkey.c (из набора программ kbd-1.15.3 ) служит наглядным примером того, как это делается.
Версию пакета kbd , используемую в вашей системе, можно узнать, например, выполнив команду showkey —version . В ответ будет выведено сообщение, похожее на «showkey from kbd 1.15» .
Исходя из того, что одним из основных недостатков современной литературы о программировании является почти полное отсутствие примеров кода, решающего реальные, а не учебные задачи, остановимся на src/showkey.c подробнее.
/*— Начало файла src/showkey.c с комментариями —*/
/* Директивы препроцессора, начинающиеся со слова #include (в переводе с английского — «включить в состав») подключают заголовочные файлы (header files). Препроцессор — это один из элементов компилятора языка Си, осуществляющий предварительную обработку кода программы (с его помощью можно включать в состав программы внешние файлы, осуществлять макроподстановки и условные включения). Подробные сведения о препроцессоре Си указаны в отличной книге [КерниганРитчи2006, С. 101-104]. */
/* В системах GNU/Linux заголовочные файлы обычно хранятся в каталоге /usr/include . Иными словами, если в командной строке ввести «less /usr/include/stdio.h» , можно будет увидеть содержание заголовочного файла, ответственного за реализацию ввода/вывода в приложении. В английском языке словам «ввод» и «вывод» соответствуют «input» и «output». Таким образом, stdio.h расшифровывается как «Standard Input/Output Header file». */
/* Имена заголовочных файлов, находящихся в каталоге /usr/include (или его аналоге), заключаются в угловые скобки. В свою очередь, двойные кавычки применяются для выделения имён заголовочных файлов, расположенных в одном каталоге с кодом программы, которую предстоит компилировать (переводить с языка высокого уровня на язык понятный компьютеру, то есть — в машинный код). */
/* Имена переменных tmp , fd и oldkbmode говорят сами за себя. Tmp — сокращение от слова temporary (временный); fd — сокращение от file descriptor (дескриптор файла, являющийся ссылкой на «объект» открытого файла; подробнее см. [Вахалия2003, С. 338-340]); oldkbmode (old keyboard mode) служит для хранения названия режима, в котором находится драйвер клавиатуры (об этих режимах мы ещё поговорим). Сокращение int указывает на тип переменной (см. [КерниганРитчи2006, С. 50-54]). */
/* Структура termios используется функциями управления терминалом в Unix-подобных системах. Подробнее см. [Рочкинд2005, С. 226-228]. Структура — это совокупность нескольких переменных, часто различных типов, сгруппированных под единым именем для удобства обращения. [КернигаРитчи2006, с. 139] */
/* Область действия функции get_mode() ограничивается данным файлом исходного кода: от точки объявления до конца (на это указывает слово static перед именем функции). Иными словами, к функции get_mode() нельзя обращаться за пределами файла, в котором она объявлена. */
/* В свою очередь, тип void (в переводе с английского — «пустой») обозначает пустое множество значений. Он используется для указания типа возвращаемого функцией значения в случае если функция ничего не возвращает. */
/* Предшествующая строка кода указывает, что функция get_mode() не только не возвращает, но и не получает никаких значений на вход. */
/* *m является указателем и представляет из себя группу ячеек памяти, которые могут содержать адрес переменной типа char . */
/* KDGKMODE (Get Keyboard Mode) объявляется в файле /usr/include/kd.h и хранит название текущего режима работы драйвера клавиатуры. Нам не удалось найти документации, где пояснялось бы как расшифровывается «KD» . Вероятнее всего, «KD» — это Keyboard Driver . */
/* Функция perror() входит в стандартную библиотеку языка Си и определяется в заголовочном файле stdio.h . Она выводит содержимое своего аргумента, а также сообщение об ошибке. */
/* ioctl — системный вызов общего назначения, служащий для управления символьными устройствами всех типов. Его синтаксис описывается следующим образом: ioctl(fd, cmd, arg) , где fd — дескриптор файла; cmd — целое число, указывающее вызываемую команду; arg — дополнительный аргумент команды (обычно — адрес блока параметров). */
/* KDGKBMODE — это имя, которое в результате макроподстановки, осуществляемой препроцессором (подробнее см. [КерниганРитчи2006, С. 102-103]), заменяется на число, указывающее на команду, результатом которой является получение информации о текущем режиме работы драйвера клавиатуры. Упомянутая макроподстановка происходит благодаря содержимому файла /usr/include/linux/kd.h . */
/* Аргумент &oldkbmode содержит адрес переменной oldkbmode . Именно по этому адресу будет записан результат выполнения вызовы ioctl . K_RAW , K_XLATE , K_MEDIUMRAW , K_UNICODE — это имена, используемые для макроподстановки в файле /usr/include/linux/kd.h . */
/* Таким образом, вместо KDGKBMODE , K_RAW , K_XLATE , K_MEDIUMRAW и K_UNICODE подставляются числа, указанные через макроопределение в файле /usr/include/linux/kd.h : 0x4B44, 0x00, 0x01, 0x02, 0x03 соответственно. Именно поэтому переменная oldkbmode имеет тип int . */
/* KDSKBMODE (Set Keyboard Mode) содержит число, указывающее на команду установки режима работы драйвера клавиатуры. */
/* Конструкция еxit(выражение) эквивалентна конструкции return . Обычно она возравщает 0 если всё идёт хорошо, а 1 — когда произошла ошибка. */
/* Системный вызов tcsetattr задаёт атрибуты терминала, предварительно сохранённые в структуре old (копия структуры termios ). */
/* Атрибут attr_noreturn указывает на то, что функция никогда не возвращает значений. */
/* Функция clean_up() восстанавливает режим работы драйвера клавиатуры, исходя из данных, полученных с помощью системного вызова ioctl . */
/* Атрибут attr_unused указывает на то, что аргумент функции никак не обрабатывается. */
/* Функция usage() , выводящая информацию об аргументах, которые принимает программа, не нуждается в особых пояснениях. */
/* *short_opts — это указатель. Значение записанное в адрес, на который ссылается этот указатель, впоследствии не меняется, так как в начале строки с его объявлением стоит модификатор const . Строка, хранимая по адресу *short_opts , содержит возможные короткие опции, каждая из которых представлена одной буквой. */
/* long_opts[] (в переводе — «длинные опции», то есть содержащие более одной буквы в своём имени) представляет из себя массив структур. Каждая структура массива имеет следующие элементы:
< "имя опции", наличие_аргумента, флаг, значение>. */
/* Крайняя структура в массиве должна содержать только нули. */
/* Элемент «наличие_аргумента» может содержать либо числа (0, 1 ,2), либо имена имена для макроподстановки, указанные в файле /usr/include/getopt.h и соответствующие этим числам ( no_argument соответствует нулю; required_argument соответствует единице; optional_argument соответствует двойке). */
/* Если элемент «значение» содержит короткую опцию из строки, на которую указывает *short_opts , то элемент «флаг» , будучи установленным в NULL , позволяет обеспечить соответствие между короткой и длинной опцией (например, как показано ниже, короткая опция -h соответствует длинной опции —help ). Если же заменить элемент «флаг» на указатель, то он будет указывать на переменную, имя которой находится в элементе «значение» . */
/* Следующие три строки кода отвечают за локализацию интерфейса программы средствами GNU gettext . */
/* Пятый арумент функции getopt_long может содержать указатель на структуру в массиве long_opts[] . Подробнее см. man getopt_long (данная страница справочного руководства, помимо всего прочего, содержит пример исходного кода, где пятый аргумент является указателем, а не нулём, как в рассматриваемом нами файле). */
/* Как уже отмечалось выше, структура new — это копия структуры termios . */
/* Две строки кода, представленные далее, иллюстрируют приёмы работы с элементами структуры (локальными флагами копии структуры termios ). Флаги ICANON и ISIG устанавливаются в нуль, ECHO и ECHOCTL — в единицу (подробнее см. [КерниганРитчи2006, с. 160]). */
/* При флаге ICANON равном нулю, вводимые символы не составляются в строки вплоть до чтения (выполнения системного вызова read ). ISIG , будучи обнулённым, блокирует обработку управляющих символов (терминал перестаёт реагировать на управляющие последовательности, вызывающие появление сигналов). К этим символам относятся: INTR (Ctrl+C), QUIT (Ctrl+\), SUSP (Ctrl+z) и DSUSP (Ctrl+y) . Подробнее см. [ТаненбаумВудхалл2006, С. 270-277].*/
/* Обратите внимание, что рассматриваемый блок кода выполняется только при условии если установлена в 1 опция -a (print_ascii). Согласно документации ( man 3 termios ), при ECHOCTL == 1 и ECHO == 1 вводимые пользователем управляющие символы ASCII (за исключением TAB, NL, START и STOP) отображаются в виде ^X , где вместо X размещается символ из таблицы ASCII с кодом на 0x40 больше, чем у управляющего символа. Например, в ответ на символ возврата каретки (клавиша с надписью Enter) появляется ^M (шестнадцатеричный код M в таблице ASCII равен 0x4D , a «Enter» соответствует коду 0xD ). Также следует отметить, что ECHOCTL требует, чтобы в коде программы было определено (через #define в файле /usr/include/features.h) имя _BSD_SOURCE или _SVID_SOURCE (подробнее об этих именах см. http://www.aquaphoenix.com/ref/gnu_c_library/libc_12.html ). */
/* Следующая строка устанавливает все флаги ввода (IGNBRK, BRKINT, IGNPAR, PARMRK, INPCK, ISTRIP, INLCR, IGNCR, ICRNL, IUCLC, IXON, IXANY, IXOFF, IMAXBEL, IUTF8) в нуль. Подробнее см. /usr/include/bits/termios.h , а также комментарии по флагам режимов . */
/* Когда число символов в очереди будет равно значению, записанному в new.c_cc[VMIN] , или когда пройдёт количество десятых долей секунды, указанное в new.c_cc[VTIME] (код рассматриваемого файла отключает таймер так как в new.c_cc[VTIME] заносится нуль) будет выполнен системный вызов read . Подробнее см. [Рочкинд2005, С. 233-236]. */
/* Изменения в структуре termios , хранимые в структуре new , применяются системным вызовом tcsetattr() . Аргумент TCSAFLUSH говорит о том, что помимо применения изменений должны быть очищены буферы ввода/вывода терминала (подробнее см. раздел 3.1 документа Serial Programming Guide for POSIX Operating Systems и его не самый качественный, но всё же перевод на русский язык . */
/* Функция getfd() определена в файле getfd.c . */
/* По умолчанию showkey прекращает работу при отсутствии активности пользователя (нажатых клавиш) в течение 10 секунд. */
/* Системный вызов signal устанавливает реакцию программы showkey на сигналы (запросы на прерывание, осуществляемые на уровне процессов), имена которых определены в файле /usr/include/bits/signum.h (см. /usr/include/signal.h ). По сути речь идёт о назначении подпрограмм обработки для каждого из сигналов. Разработчики showkey сделали всё достаточно прямолинейно, назначив функцию die() обрабатывать все возможные сигналы, кроме того, что генерируется по истечении времени, заданного в качестве аргумента функции alarm() (см. выше). */
/* Очень подробные и наглядные материалы о сигналах Unix представлены в книге [НеметСнайдерСибассХейн2003, С. 68-71]. */
/* Как видно, чтение скан-кодов осуществляется с помощью системного вызова read , считывающего в бесконечном цикле байты из файла, представленного дескриптором fd (не забывайте, что в GNU/Linux устройства также являются файлами). */
/* Как отмечалось ранее (при обсуждении вывода программы getkeycodes ), отпускание клавиши результируется в скан-коде, старший бит которого равен единице (напомним, что речь идёт об особенностях набора скан-кодов Set 1 ). Иными словами, к скан-коду нажатой клавиши прибавляется шестнадцатеричное число 80 , которое в программах на языке Си записывается как 0x80 . */
/*— Конец файла src/showkey.c с комментариями —*/
Компилирировать представленный выше исходный код нужно вместе с файлом src/getfd.c , содержащем определение функции getfd() .
Проверить работоспособность получившегося исполняемого файла ( a.out ) лучше всего вне X сессии. Например, в виртуальном терминале, доступном по нажатию ++ .
Разумеется, для удобства, было бы хорошо иметь возможность управлять длительностью временного промежутка, который программа showkey ожидает ввода пользователя перед тем, как завершить свою работу (в kbd-1.15.3 этот промежуток времени равен 10 секундам). В рамках проекта console-tools данное пожелание реализовано (см. файл kbdtools/showkey.c в архиве). Комментарии по поводу использования некоторых приложений, входящих в состав console-tools см. в статье «VGA console basics and Linux console-tools» .
Источник
C ++ обрабатывает нажатие клавиш в многопоточной программе в Linux
Я пишу программу, которая захватывает видео с 4 камер одновременно, поэтому у меня есть 4 потока для управления каждой камерой. В каждом потоке я хочу, чтобы запись продолжалась до тех пор, пока я не нажму клавишу, и эта клавиша соответствует «q» или чему-то еще.
Для дескриптора нажатия клавиш я ищу в Интернете и нашел такой метод:
И в моем классе VideoCapture у меня есть такой код (не полный):
Когда программа запускается, как только я нажимаю клавишу ‘q’ 4 раза, программа завершает работу и управление возвращается обратно в оболочку. Но в некоторых определенных обстоятельствах (я точно не знаю, это происходит не каждый раз) это приводит к проблеме, то есть, когда я иду и набираю команды в оболочке, символы, которые я печатаю, не отображаются. Когда я нажимаю ввод, команды отправляются.
Я ищу эту проблему и нашел это: https://askubuntu.com/a/172747 , что указывает на то, что мои атрибуты терминала не были сброшены должным образом. Но в коде дескриптора клавиш я заметил, что это две строки кода
сбросил атрибуты терминала. Поэтому мне интересно, имеет ли это отношение к многопоточности. Я новичок в многопоточном программировании и не могу решить это сам, так может кто-нибудь мне помочь? Любые предложения высоко ценятся.
Решение
У вас есть несколько потоков, пытающихся изменить параметры (статического) терминала одновременно:
Без этого ваши терминальные атрибуты являются абсолютно случайными.
Исправления заключаются в том, чтобы либо защитить это с помощью мьютекса, либо если вы создаете другой поток для чтения с клавиатуры и устанавливаете флаг, когда нажимается «q»; что ваши другие темы могут читать, вы можете сделать что-то вроде
Это будет означать, что вам нужно всего лишь нажать q для остановки всех потоков сбора кадров.
Источник