使用标准输入识别箭头键
Posted
技术标签:
【中文标题】使用标准输入识别箭头键【英文标题】:Recognizing arrow keys with stdin 【发布时间】:2011-05-07 00:20:53 【问题描述】:是否可以跨平台处理 C 或 OCaml 程序中的退格键和箭头键?
实际上,OCaml 解决方案会受到赞赏,但许多标准 unix 函数直接包装到相应的 API 调用中,因此移植 C 解决方案应该没有问题。
我要实现的是捕获箭头键以覆盖其在 shell 内的行为(通过重新设置最后一行或类似的操作)。我认为这件事落在实际程序之前,它不是由代码本身处理的,所以我不知道它是否可能。
该程序是在 Linux、OS X 和 Windows(在 cygwin 上)上编译的,所以我想在所有平台上都这样做..
【问题讨论】:
【参考方案1】:我最近做了一些非常相似的事情(尽管我的代码仅适用于 Linux)。您必须将标准输入设置为非规范模式才能读取箭头键按下。这应该适用于 OS X 和 Linux,并且可能适用于 Cygwin,尽管我不能肯定地说。
open Unix
let terminfo = tcgetattr stdin in
let newterminfo = terminfo with c_icanon = false; c_vmin = 0; c_vtime = 0 in
at_exit (fun _ -> tcsetattr stdin TCSAFLUSH terminfo); (* reset stdin when you quit*)
tcsetattr stdin TCSAFLUSH newterminfo;
当规范模式关闭时,您无需等待换行符即可从标准输入读取。 c_vmin 表示返回前要读取的最少字符数(您可能希望一次读取一个字符),c_vtime 是最长读取等待时间(以 0.1s 为单位)。
您可能还想将 c_echo
设置为 false,以便将箭头键按下打印到终端(但您必须手动打印其他所有内容。
大多数终端使用ANSI escape sequences 表示箭头键按下。如果您不带参数运行cat
并开始按箭头键,您可以看到使用的转义序列。它们通常是
up - "\033[A"
down - "\033[B"
left - "\033[D"
right - "\033[C"
其中 '\033' 是 esc
的 ascii 值
【讨论】:
我几乎成功地完成了这项工作,但我应该如何管理角色的手动回显?我问这个是因为标准输入然后被转发到一个解析器,它逐行解析并解释它。我应该放在中间并打印出每个字符?但是当用户按下 upkey 时,它应该将已经打印出来的字符替换为不同的字符.. 重新阅读您的问题后,我认为使用 readline/ledit 可能是您最好的选择。但是,如果您想手动执行操作,那么您将不得不手动控制解析器的标准输入。从标准输入读取,检查向上字符转义序列,就在解析器的标准输入。我在这里做类似的事情:github.com/aplusbi/reflow/blob/master/reflow.ml 在main
函数中,我将标准输入读取到字符串,然后将其写入file_descr。您可能最终也会这样做,但首先检查转义序列。
那些箭头字符实际上来自 ECMA 48 标准 man7.org/linux/man-pages/man4/console_codes.4.html 你可以看到 Linux 支持哪些 ECMA 48 的控制字符:看起来将光标向左移动 5 个字符你可以做 \027[ 5D
@aeroson 看来不是。 ESC 键对应 ASCII 码 27,即 33 八进制。以 0 开头的数字通常用于指定八进制,因此 Niki 的答案在指定 \033 时是正确的。 \027 将是八进制 23 = 十进制 23 = ASCII ETB(传输块结束),这对于 ECMA 48 可能没有特殊含义。【参考方案2】:
使用 ncurses 提取箭头键功能的序列,然后在您阅读 stdin 时查找它们。改用 libedit 或 readline 之类的东西可能更容易,并让它处理键处理。
【讨论】:
关于 ncurses 解决方案的进一步说明:在单个平台上,它是跨终端的,为您处理多毛的终端差异。此外,还有适用于 Windows 的版本,因此您也可以在 Windows 控制台中使用编辑功能。【参考方案3】:支持超出可打印字符行的键盘输入的标准方法是通过ncurses 库,它有一个Ocaml binding。另一个常见的可能性是 readline 库(最有名的 Bash 使用)。
如果您所做的只是逐行读取输入,但希望您的用户拥有一个像样的行编辑器,则无需在您的程序中包含任何支持。相反,只需告诉您的用户使用包装程序,例如 rlwrap(基于 readline)或 ledit。这些包装器确实提供了行版本和历史记录,您列出的这两个功能是您的要求。我建议您仅在您想要一些更高级的东西时才将输入处理构建到您的程序中,例如当用户按下 Tab 时特定于程序的完成。
【讨论】:
【参考方案4】:Backspace 是一个 ASCII 字符,将像任何其他字符一样放置在标准输入中。字符转义序列'\b'
是退格字符。
对于光标键,它们不与任何控制字符相关联,因此不要在标准输入流中生成数据。低级访问必然是特定于平台的,尽管有跨平台库可以抽象出平台差异。我相信你提到的所有平台都可以使用 ncurses。
【讨论】:
我很确定任何处于规范模式的终端都不会将退格字符放入标准输入。文本是缓冲的,所以如果你输入“abcd\b\n”,你会在你的程序中得到“abc\n”。 @Niki:糟糕,我大部分时间都在编写连接到哑终端仿真器软件的嵌入式系统。以上是关于使用标准输入识别箭头键的主要内容,如果未能解决你的问题,请参考以下文章
Golang 从标准输入读取,如何检测特殊键(回车、退格...等)
HTML输入类型编号 - 如何知道“步骤”功能触发更改事件?