检查Linux终端缓冲区中的额外字符

时间:2011-12-23 21:42:29

标签: python ioctl getch termios

我尝试在Python中实现getch()函数,它还应返回特殊键(如F1-F12和箭头键)的字符列表。这些特殊键在序列中生成多个字符。因此getch()在阻塞模式下读取一个char,然后检查输入缓冲区中是否有额外的字符也可以获取它们。

我正在使用ioctl调用termios.FIONREAD来获取输入缓冲区中的字节数。它捕获堆叠在缓冲区中的非特殊按键,但是错过了特殊键中的额外符号。看起来有两个不同的缓冲区,如果有人能够解释这个问题会很好。

以下是交互式示例:

from time import sleep

def getch():
    import sys, tty, termios
    fd = sys.stdin.fileno()
    # save old terminal settings, because we are changing them
    old_settings = termios.tcgetattr(fd)
    try:
        # set terminal to "raw" mode, in which driver returns
        # one char at a time instead of one line at a time
        #
        # tty.setraw() is just a helper for tcsetattr() call, see
        # http://hg.python.org/cpython/file/c6880edaf6f3/Lib/tty.py
        tty.setraw(fd)
        ch = sys.stdin.read(1)

        # --- check if there are more characters in buffer
        from fcntl import ioctl
        from array import array

        sleep(1)
        buf = array('i', [0])
        ioctl(fd, termios.FIONREAD, buf)
        print "buf queue: %s," % buf[0],
        # ---

    finally:
        # restore terminal settings. Do this when all output is
        # finished - TCSADRAIN flag
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch

char = ''
while char != 'q':
  char = getch()
  print 'sym: %s, ord(%s)' % (char, ord(char))

注意中间的sleep(1)。如果在第二次到期之前按下一个键,输出将为:

buf queue: 0, sym: l, ord(108)

对于在一秒内输入的5个普通键(例如'asdfg'),输出为:

buf queue: 4, sym: a, ord(97)

但是对于单箭头键,输出:

buf queue: 0, sym: , ord(27)
buf queue: 0, sym: [, ord(91)
buf queue: 0, sym: D, ord(68)

这里有两个问题:

  1. 为什么丢弃普通按键队列中的4个符号?是因为切换到“原始”终端模式?如何在不以“原始”模式离开终端的情况下为后续getch()运行保留字符?

  2. 为什么单个特殊按键的ioctl缓冲区为空?这些字符来自后续getch()次运行的位置?如何检查?

1 个答案:

答案 0 :(得分:1)

我遇到了同样的问题。一些搜索产生了一个工作示例,最多读取4个字节(而不是1个字节)以允许特殊的转义序列并使用os.read(而不是file.read)。基于这些差异,我能够编写一个识别光标键事件的小键盘类:

#!/usr/bin/env python

import os
import select
import sys
import termios

class Keyboard:
  ESCAPE = 27
  LEFT = 1000
  RIGHT = 1001
  DOWN = 1002
  UP = 1003

  keylist = {
    '\x1b' : ESCAPE,
    '\x1b[A' : UP,
    '\x1b[B' : DOWN,
    '\x1b[C' : RIGHT,
    '\x1b[D' : LEFT,
  }

  def __init__(self):
    self.fd = sys.stdin.fileno()
    self.old = termios.tcgetattr(self.fd)
    self.new = termios.tcgetattr(self.fd)
    self.new[3] = self.new[3] & ~termios.ICANON & ~termios.ECHO
    self.new[6][termios.VMIN] = 1
    self.new[6][termios.VTIME] = 0
    termios.tcsetattr(self.fd, termios.TCSANOW, self.new)

  def __enter__(self):
    return self

  def __exit__(self, type, value, traceback):
    termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)

  def getFile(self):
    return self.fd

  def read(self):
    keys = os.read(self.fd, 4)
    if keys in Keyboard.keylist:
      return Keyboard.keylist[keys]
    else:
      return None

if __name__ == "__main__":
  with Keyboard() as keyboard:
    key = keyboard.read()
    while key != Keyboard.ESCAPE:
      print '%d' % key
      key = keyboard.read()

使用file.read(4),读取块。使用os.read(fd, 4)时,读数不会阻止。我不知道为什么会有所不同,并欢迎启蒙。