Gre*_*ica 7 c select posix ansi-escape termios
我的最终目标是区分我在键盘上按下Esc(ASCII 27) 和我按下→键盘上的键(转换为 的序列27 91 67)。我正在使用termios将我的终端置于非规范模式。
我想我明白有两种选择:
我正在尝试做后者。为此,我试图用它select来查看是否stdin为空。
select似乎总是返回 0(超时到期)。这似乎很奇怪,原因有两个:
1返回,因为它会在27有 a91和 a 后67立即看到这些事情都没有发生,所以恐怕我只是不理解select或标准输入/输出,就像我想象的那样。
为什么select在我的示例中不返回除 0 之外的任何内容?是否可以检查是否stdin为空?其他图书馆如何处理这个问题?
我在 MacOS High Sierra 和 Ubuntu 16 上运行它,结果相同。
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
int main() {
// put terminal into non-canonical mode
struct termios old;
struct termios new;
int fd = 0; // stdin
tcgetattr(fd, &old);
memcpy(&new, &old, sizeof(old));
new.c_lflag &= ~(ICANON | ECHO);
tcsetattr(fd, TCSANOW, &new);
// loop: get keypress and display (exit via 'x')
char key;
printf("Enter a key to see the ASCII value; press x to exit.\n");
while (1) {
key = getchar();
// check if ESC
if (key == 27) {
fd_set set;
struct timeval timeout;
FD_ZERO(&set);
FD_SET(STDIN_FILENO, &set);
timeout.tv_sec = 0;
timeout.tv_usec = 0;
int selret = select(1, &set, NULL, NULL, &timeout);
printf("selret=%i\n", selret);
if (selret == 1) {
// input available
printf("possible sequence\n");
} else if (selret == -1) {
// error
printf("err=%s\n", strerror(errno));
} else {
// just esc key
printf("esc key standalone\n");
}
}
printf("%i\n", (int)key);
if (key == 'x') { break; }
}
// set terminal back to canonical
tcsetattr(fd, TCSANOW, &old);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
gns-mac1:sandbox gns$ ./seltest
Enter a key to see the ASCII value; press x to exit.
selret=0
esc key standalone
27
selret=0
esc key standalone
27
91
67
120
Run Code Online (Sandbox Code Playgroud)
我认为问题在于您正在使用getchar()标准 I/O 库 \xe2\x80\x94 中的 \xe2\x80\x94 函数,您需要在其中使用文件描述符 I/O ( read())。
这是对您的代码的直接改编(在运行 macOS High Sierra 10.13.2 的 MacBook Pro 上测试),它产生了您和我想要的答案。
\n\n#include <stdio.h>\n#include <string.h>\n#include <termios.h>\n#include <sys/select.h>\n#include <sys/time.h>\n#include <unistd.h>\n#include <errno.h>\n\nenum { ESC_KEY = 27 };\nenum { EOF_KEY = 4 };\n\nint main(void)\n{\n // put terminal into non-canonical mode\n struct termios old;\n struct termios new;\n int fd = 0; // stdin\n tcgetattr(fd, &old);\n //memcpy(&new, &old, sizeof(old));\n new = old;\n new.c_lflag &= ~(ICANON | ECHO);\n tcsetattr(fd, TCSANOW, &new);\n\n // loop: get keypress and display (exit via \'x\')\n //int key;\n printf("Enter a key to see the ASCII value; press x to exit.\\n");\n while (1)\n {\n char key;\n if (read(STDIN_FILENO, &key, 1) != 1)\n {\n fprintf(stderr, "read error or EOF\\n");\n break;\n }\n if (key == EOF_KEY)\n {\n fprintf(stderr, "%d (control-D or EOF)\\n", key);\n break;\n }\n\n // check if ESC\n if (key == 27)\n {\n fd_set set;\n struct timeval timeout;\n FD_ZERO(&set);\n FD_SET(STDIN_FILENO, &set);\n timeout.tv_sec = 0;\n timeout.tv_usec = 0;\n int selret = select(1, &set, NULL, NULL, &timeout);\n printf("selret=%i\\n", selret);\n if (selret == 1)\n printf("Got ESC: possible sequence\\n");\n else if (selret == -1)\n printf("error %d: %s\\n", errno, strerror(errno));\n else\n printf("esc key standalone\\n");\n }\n else \n printf("%i\\n", (int)key);\n if (key == \'x\')\n break;\n }\n\n // set terminal back to canonical\n tcsetattr(fd, TCSANOW, &old);\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n示例输出(程序esc29):
$ ./esc29 # 27 isn\'t a 2-digit prime\nEnter a key to see the ASCII value; press x to exit.\n115\n100\n97\n115\n100\nselret=1\nGot ESC: possible sequence\n91\n68\nselret=1\nGot ESC: possible sequence\n91\n67\nselret=0\nesc key standalone\nselret=0\nesc key standalone\nselret=0\nesc key standalone\n100\n100\n4 (control-D or EOF)\n$\nRun Code Online (Sandbox Code Playgroud)\n\n我按下左/右箭头键并报告了“可能的序列”;我按下触摸条上的 ESC 并得到“ESC 键独立”。其他字符似乎是合理的,当按下 control-D 时,代码会被破坏。
\n\n此代码一次最多读取 4 个字符,并处理收到的字符。有两个嵌套循环,因此我使用goto end_loops;(两次!)从内部循环中跳出这两个循环。我还使用该atexit()函数来完成大部分可以完成的操作,以确保即使程序没有通过程序退出,终端属性也重置为正常状态main()。(我们可以争论代码是否也应该使用at_quick_exit()函数 \xe2\x80\x94 它是 C11 的功能,但不是 POSIX 的功能。)
如果代码读取多个字符,它会扫描它们,寻找ESC(转义)。如果它找到一个并且还剩下任何数据,那么它会报告转义序列(可能是功能键序列)。如果找不到更多字符,它将select()像以前一样使用来决定 ESC 序列中是否还有更多字符,或者这是否是一个独立的 ESC。实际上,计算机比人类快得多,因此它要么读取单个字符,要么读取完整的序列。我使用长度为 4 的数组,因为我认为它比键盘生成的最长按键序列长;我很乐意将其增加到 8(或任何其他更大的数字)。唯一的缺点是缓冲区必须可用,万一读取多个字符(例如,因为程序在输入累积时正在计算),则需要读取字符。功能键或箭头键的 ESC 也有可能是适合缓冲区 \xe2\x80\x94 的最后一个字符,在这种情况下,需要额外读取。祝你好运,通过这个编写为 \xe2\x80\x94 的程序来证明你的打字速度不够快。您需要在某处添加睡眠代码,以允许字符在读取之前累积。
因此,这主要展示了一些额外的技术,但它可以作为思考处理的替代方法。
\n\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/select.h>\n#include <sys/time.h>\n#include <termios.h>\n#include <unistd.h>\n\nenum { ESC_KEY = 27 };\nenum { EOF_KEY = 4 };\n\n/* These two need to be set in main() but accessed from reset_tty() */\nstatic int fd = STDIN_FILENO;\nstatic struct termios old;\n\n// set terminal back to canonical\nstatic void reset_tty(void)\n{\n tcsetattr(fd, TCSANOW, &old);\n}\n\nint main(void)\n{\n struct termios new;\n tcgetattr(fd, &old);\n new = old;\n new.c_lflag &= ~(ICANON | ECHO);\n tcsetattr(fd, TCSANOW, &new);\n atexit(reset_tty); // Ensure the terminal is reset whenever possible\n\n printf("Enter a key to see the ASCII value; press x to exit.\\n");\n char keys[4];\n int nbytes;\n while ((nbytes = read(fd, keys, sizeof(keys))) > 0)\n {\n for (int i = 0; i < nbytes; i++)\n {\n char key = keys[i];\n if (key == EOF_KEY)\n {\n fprintf(stderr, "%d (control-D or EOF)\\n", key);\n goto end_loops;\n }\n else if (key == ESC_KEY && nbytes > i + 1)\n {\n printf("Got ESC sequence:");\n for (int j = i; j < nbytes; j++)\n printf("%4d", keys[j]);\n putchar(\'\\n\');\n break;\n }\n else if (key == ESC_KEY)\n {\n fd_set set;\n struct timeval timeout;\n FD_ZERO(&set);\n FD_SET(fd, &set);\n timeout.tv_sec = 0;\n timeout.tv_usec = 0;\n int selret = select(1, &set, NULL, NULL, &timeout);\n printf("selret=%i\\n", selret);\n if (selret == 1)\n printf("Got ESC: possible sequence\\n");\n else if (selret == -1)\n printf("error %d: %s\\n", errno, strerror(errno));\n else\n printf("esc key standalone\\n");\n }\n else \n printf("%i\\n", (int)key);\n if (key == \'x\')\n goto end_loops;\n }\n }\n\nend_loops:\n return 0;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n示例输出(程序esc67):
$ ./esc67\nEnter a key to see the ASCII value; press x to exit.\n65\n90\n97\n122\nselret=0\nesc key standalone\nGot ESC sequence: 27 91 65\nGot ESC sequence: 27 91 66\nGot ESC sequence: 27 91 67\nGot ESC sequence: 27 91 68\nGot ESC sequence: 27 79 80\nselret=0\nesc key standalone\n97\nGot ESC sequence: 27 91 67\n97\nGot ESC sequence: 27 91 67\n120\n$\nRun Code Online (Sandbox Code Playgroud)\n