执行stty raw后cin.peek()遇ESC键阻塞,如何用C++识别ESC与上箭头键?
问题:标准C++识别ESC/上箭头键时的阻塞异常
我们需要用标准C++识别用户按下的ESC键(0x1b)或上箭头键(序列0x1b 0x5b 0x41)。在控制台执行stty raw切换到原始模式后,计划用getchar()配合cin.peek()实现识别,但发现仅按下ESC键时cin.peek()会发生阻塞——而peek操作本应仅查看输入不等待。
以下是复现代码,该代码能正常识别上箭头键,但无法正确识别单独的ESC键:
#include <iostream> #include <iomanip> using namespace std; // ESC: 1b, UP: 1b 5b 41 int esc_or_up() { auto esc_key = 0x1b, bracket_key = 0x5B, a_key=0x41; auto buf = cin.rdbuf(); int ch = buf->sbumpc(); if (ch != esc_key) return ch; auto debug = [&]() { static int count = 0; cout << "\r\n" << count << ". ch == " << hex << setfill('0') << setw(2) << ch << ", in_avail: " << buf->in_avail() << ", gcount: " << cin.gcount() << "\r\n"; count++; }; debug(); // Determine Esc and Up key here for (size_t i = 0; i < 3; i++) { debug(); ch = cin.peek(); // Blocked if only ESC stroked if (ch == EOF) cout << "\r\nESC key stroked\r\n"; else if (ch == bracket_key) { ch = buf->snextc(); if (ch == a_key) cout << "\r\nUp arrow key stroked\r\n"; } } return 0; } int main(int argc, char *argv[]) { system("stty raw"); cout << "Note: stty raw executed, open a new console if you need\r\n"; cout << "Please stroke ESC or UP key\r\n"; esc_or_up(); cout << "\r\n"; return 0; }
运行结果截图:
解决方法
问题原因
在stty raw模式下,终端不会自动解析输入序列。当按下ESC键时,系统无法区分这是单独的ESC键,还是箭头键/功能键序列的起始字符,因此会等待一段时间(终端默认超时)来判断后续是否有输入,这就导致cin.peek()因等待输入而阻塞。此外,标准输入默认是阻塞模式,没有可用输入时peek()会一直等待。
修改后的代码
#include <iostream> #include <iomanip> #include <unistd.h> #include <fcntl.h> using namespace std; // ESC: 1b, UP: 1b 5b 41 int esc_or_up() { auto esc_key = 0x1b, bracket_key = 0x5B, a_key=0x41; auto buf = cin.rdbuf(); int ch = buf->sbumpc(); if (ch != esc_key) return ch; auto debug = [&]() { static int count = 0; cout << "\r\n" << count << ". ch == " << hex << setfill('0') << setw(2) << ch << ", in_avail: " << buf->in_avail() << ", gcount: " << cin.gcount() << "\r\n"; count++; }; debug(); // 检查是否有后续输入,无则判定为单独ESC键 if (buf->in_avail() == 0) { cout << "\r\nESC key stroked\r\n"; return 0; } // 读取后续字符判断是否为上箭头键 ch = buf->sbumpc(); if (ch == bracket_key) { ch = buf->sbumpc(); if (ch == a_key) { cout << "\r\nUp arrow key stroked\r\n"; } } return 0; } int main(int argc, char *argv[]) { // 保存终端原始配置,用于程序结束后恢复 system("stty -g > /tmp/stty_config"); system("stty raw"); // 将标准输入设置为非阻塞模式 int flags = fcntl(STDIN_FILENO, F_GETFL, 0); fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); cout << "Note: stty raw executed, run `stty $(cat /tmp/stty_config)` to restore terminal\r\n"; cout << "Please stroke ESC or UP key\r\n"; esc_or_up(); cout << "\r\n"; // 恢复终端原始状态和阻塞模式 system("stty $(cat /tmp/stty_config)"); fcntl(STDIN_FILENO, F_SETFL, flags); return 0; }
关键修改点
- 终端状态管理:保存终端原始配置,程序结束后恢复,避免终端陷入异常状态;
- 非阻塞输入设置:用
fcntl将标准输入设为非阻塞模式,确保peek()/sbumpc()不会因无输入而阻塞; - 逻辑简化:先通过
in_avail()检查缓冲区是否有后续字符,无则直接判定为ESC键,避免不必要的循环等待; - 序列判断优化:直接读取后续字符验证上箭头键序列,逻辑更清晰。
内容的提问来源于stack exchange,提问作者Keelung
相关产品推荐
相关产品推荐

