Как получить коды стрелок и т.п. в программе с терминалом под *nix
Навеяно вот этим вопросом...
Как написать для терминала в linux (unix) программу на Си (или С++), которая будет вызывать функцию getchar()
и получать вместе с кодами обычных символов еще какие-нибудь коды при нажатии на клавиатуре стрелок и функциональных клавиш (F1, F2 ... F12 и т.п.)?
Ответы (1 шт):
Как вызывать с такой целью именно getchar()
, откровенно говоря, не знаю (если только воспользоваться #define
?).
Можно написать ее аналог, скажем get_fchar()
(с тем же прототипом), которая для стрелок и т.п. будет возвращать код больший 255.
При нажатии на стрелку и т.п. в терминале, клавиатура посылает на stdin программы несколько символов (так называемую esc-последовательность), которые мы будем искать в заранее составленной таблице. Если нашли, то вернем индекс строки таблицы плюс 255 (это и будет желаемый код функциональной клавиши). Если же прочитанная последовательность отсутствует в таблице, то мы будем возвращать коды составляющих ее символов в ходе последующих вызовов функции.
Понятно, что для полноценного использования такой функции в программе типа текстового редактора надо переводить терминал в raw -echo
режим и не забывать, что в нем вывод '\n'
не переводит автоматически курсор в начало строки.
Например, вот:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct f_code {
const char *seq;
const char *code;
};
static struct f_code
def_seq[] =
{
{"\x1b[A", "UP"},
{"\x1b[B", "DOWN"},
{"\x1b[C", "RIGHT"},
{"\x1b[D", "LEFT"},
{"\x1b[E", "CENTER"},
{"\x1b[H", "HOME"},
{"\x1b[F", "END"},
{"\x1bOP", "F1"},
{"\x1bOQ", "F2"},
{"\x1bOR", "F3"},
{"\x1bOS", "F4"},
{"\x1b[15~", "F5"},
{"\x1b[17~", "F6"},
{"\x1b[18~", "F7"},
{"\x1b[19~", "F8"},
{"\x1b[2~", "INS"},
{"\x1b[3~", "DEL"},
{"\x1b[5~", "PG_UP"},
{"\x1b[6~", "PG_DOWN"},
{"\x1b[20~", "F9"},
{"\x1b[21~", "F10"}, // ??? mapped to terminal emulator action in my Linux Mint 19.3
{"\x1b[23~", "F11"}, // ??? gnome-terminal TERM=xterm-256color, can't to see esc-sequence
{"\x1b[24~", "F12"},
{0, 0}
},
*fseq = def_seq;
struct f_code *
set_fctab (struct f_code *p)
{
struct f_code *r = fseq;
fseq = p;
return r;
}
struct f_code *
def_fctab ()
{
fseq = def_seq;
return fseq;
}
/*
search `s` in fseq[].seq
returns:
index + 256 if complete match
0 if not matched
n > 0 number of uncompeleted matches
*/
int
match_seq (char *s)
{
int n = 0;
for (int i = 0; fseq[i].seq; i++) {
int j = 0;
const char *t = fseq[i].seq;
while (s[j] && t[j] && s[j] == t[j])
j++;
if (s[j] == 0) {
if (t[j] == 0)
return i + 256;
else
n++;
}
}
return n;
}
// well, ^@ can't be into the sequence
int
imatch_seq (int *s)
{
int n = 0;
for (int i = 0; fseq[i].seq; i++) {
int j = 0;
const char *t = fseq[i].seq;
while (s[j] && t[j] && s[j] == t[j])
j++;
if (s[j] == 0) {
if (t[j] == 0)
return i + 256;
else
n++;
}
}
return n;
}
const char *
get_fcode (int i)
{
if (i < 256 || i - 256 > sizeof(fseq) / sizeof(fseq[0]) - 1) {
static char buf[2];
buf[0] = i;
return (const char *)buf;
}
return fseq[i - 256].code;
}
// Returns 8-bit char or functional key as 256 + code
int
get_fchar()
{
static int buf[8];
static int b_cnt = 0;
static int b_pos = 0;
static int rchar = 0, has_rchar = 0;
int c;
ret_queue:
if (b_cnt) {
c = buf[b_pos++];
b_cnt--;
if (!b_cnt)
b_pos = 0; // ready for store new sequence
return c;
}
c = has_rchar ? rchar : getchar();
has_rchar = 0;
if (c != 0x1b)
return c;
buf[b_cnt++] = c; // ^[
buf[b_cnt] = 0;
int rc;
for (;;) {
buf[b_cnt++] = getchar();
buf[b_cnt] = 0;
rc = imatch_seq(buf);
if (rc > 255)
break;
if (rc == 0) {
rchar = buf[--b_cnt];
has_rchar = 1;
goto ret_queue;
}
}
b_cnt = 0;
b_pos = 0;
has_rchar = 0;
return rc;
}
int
main (int ac, char *av[])
{
for (int j = 0; fseq[j].seq; j++) {
const char *str = fseq[j].seq;
printf("%-10s ", fseq[j].code);
for (int i = 0; str[i]; i++)
if (str[i] < ' ')
putchar('^'), putchar('@' + str[i]);
else
putchar(str[i]);
puts("");
}
puts("\ntest get_fchar() (^D for end)");
int c;
while ((c = get_fchar()) != EOF) {
if (c > 255)
puts(get_fcode(c));
else
printf("'%c'\n", c);
if (c == 4)
break;
}
system("stty sane"); // for auto restore after `stty raw -echo; ./a.out`
return puts("\nEnd") == EOF;
}
В main пример использования get_fchar()
с печатью названий функциональных клавиш и т.п.
Также обратите внимание на функции set_fctab()
(она позволяет сменить таблицу кодов клавиш (точнее, esc-последовательностей)) и def_fctab()
(устанавливает таблицу по-умолчанию).
Кстати, все эти функции можно использовать для преобразования любых клавиатурных последовательностей в коды (при установке соответствующей таблицы). Функция match_seq()
может помочь при подобной обработке вводимого текста.