如何接受方向键输入或方向输入?

7 投票
5 回答
24918 浏览
提问于 2025-04-18 00:37

这可能是个xy问题,但我想做一个基于内核的文本编辑器,类似于vimnano。我知道怎么用转义字符来清屏,然后重新打印内容,我也能让它接受字符输入,但我不太确定怎么让它接受方向键的输入来进行导航。我原以为方向键有ASCII值,但显然不是这样。有没有办法使用方向键,还是说我必须像vim那样做一个导航模式和插入模式?

我也简单尝试过curses,但那对我来说有点麻烦,因为据我了解,它需要打开一个全新的窗口,而这和我想要的单一终端窗口不兼容。

请注意,curses不适用,因为它会清空窗口,而我不想这样。

5 个回答

2

在按下箭头键或其他任何键时执行所需的操作

# key_event_handler.py

import sys
import select
import pty
import os
import time
import fcntl
import tty
import termios
def __select( iwtd, owtd, ewtd, timeout=None):

   '''This is a wrapper around select.select() that ignores signals. If
   select.select raises a select.error exception and errno is an EINTR
   error then it is ignored. Mainly this is used to ignore sigwinch
   (terminal resize). '''

   # if select() is interrupted by a signal (errno==EINTR) then
   # we loop back and enter the select() again.
   if timeout is not None:
       end_time = time.time() + timeout
   while True:
       try:
           return select.select(iwtd, owtd, ewtd, timeout)
       except select.error:
           err = sys.exc_info()[1]
           if err.args[0] == errno.EINTR:
               # if we loop back we have to subtract the
               # amount of time we already waited.
               if timeout is not None:
                   timeout = end_time - time.time()
                   if timeout < 0:
                       return([], [], [])
           else:
               # something else caused the select.error, so
               # this actually is an exception.
               raise

STDIN_FILENO=pty.STDIN_FILENO
STDOUT_FILENO=pty.STDOUT_FILENO
string_type=bytes
sys.stdout.write(string_type())
sys.stdout.flush()
buffer = string_type()
mode = tty.tcgetattr(STDIN_FILENO)
tty.setraw(STDIN_FILENO)
try:
    while True:
        r, w, e = __select([STDIN_FILENO], [], [],timeout=1)
        if STDIN_FILENO in r:
            #It accepts all keys from keyboard 
            data=os.read(STDIN_FILENO, 1)
            #Bellow line returns ASCII value of a charector
            ascii_value=ord(data[0])
            ##########################################################################
            ##                      Your code goes here                             ## 
            ##                                                                      ##
            # Do some action here by matching the ASCII value                        #
            # you can handle your program by making use of special keys like         #
            # Backspace, Ctrl, Ctrl+A,Ctrl+B, Ctrl+C, ...Ctrl+Z, Esc,F1, ...,F12 ....#
            # Tab,Enter,Arrow keys,Alphabetic and Numeric keys are also supported    #  
            ##########################################################################
            #                                                                        #
            #To Print use bellow line rather than print or sys.stdout.write(data)    #
            #os.write(STDOUT_FILENO,data)                                            #
            ##                                                                       #
            ##########################################################################


finally:
    tty.tcsetattr(STDIN_FILENO, tty.TCSAFLUSH, mode) 

然后打开终端,运行 key_event_handler.py


这个程序主要是用来捕捉按下的键,并获取该键的ASCII码。这个程序也可以用于多线程应用中的非阻塞输入/输出。

3

Python的一个包叫做click,它主要用来创建命令行客户端。这个包还提供了一种方法,可以让你获取按键事件:

import click

key = click.getchar()

它会把按下的键转换成Unicode字符,像箭头键这样的按键会以平台本身的转义格式显示出来。

下面这个例子直接来自于click的文档,讲的是getchar的用法:

import click

click.echo('Continue? [yn] ', nl=False)
c = click.getchar()
click.echo()
if c == 'y':
    click.echo('We will go on')
elif c == 'n':
    click.echo('Abort!')
else:
    click.echo('Invalid input :(')
4

我知道我来得有点晚,但我真的很喜欢click这个包,它是@elbaschid提到的。我不知道为什么他的回答没有被点赞——可能是因为他的例子没有具体展示如何处理光标键。

这是我对这个话题的看法:

#!/usr/bin/python

import click

printable = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'

while True:
    click.echo('Continue? [yn] ', nl=False)
    c = click.getchar()
    click.echo()
    if c == 'y':
        click.echo('We will go on')
    elif c == 'n':
        click.echo('Abort!')
        break
    elif c == '\x1b[D':
        click.echo('Left arrow <-')
    elif c == '\x1b[C':
        click.echo('Right arrow ->')
    else:
        click.echo('Invalid input :(')
        click.echo('You pressed: "' + ''.join([ '\\'+hex(ord(i))[1:] if i not in printable else i for i in c ]) +'"' )

这个代码可以处理光标键,另外还会打印出任何它还不认识的键盘快捷键的py字符串表示。例如,Ctrl-s会显示为"\x13"。你可以在其他地方使用这个表示。

elif c == ??

我试着给@elbaschid的回答添加一些编辑,但被拒绝了¯\_(ツ)_/¯。如果你也喜欢我的回答,请给他一些认可。

这是一个很棒的库,适合快速进行命令行原型开发。

10

curses 正是你所需要的。实际上,我相信 vim 就是用 curses 这个接口来实现的。

试着把下面的代码放到一个叫 test_curses.py 的文件里:

import curses

screen = curses.initscr()
screen.addstr("Hello World!!!")
screen.refresh()
screen.getch()
curses.endwin()

现在打开一个 终端(不是 IDLE!要用真正的终端!)然后运行这个文件:

python test_curses.py

你应该会看到 终端 被清空了,然后出现了 Hello World!!! 的字样。按下任意键,程序就会停止,终端的内容会恢复到之前的样子。

需要注意的是,curses 库并不像你想象的那么简单和“用户友好”。我建议你阅读一下这个 教程(虽然是针对 C 语言的,但 Python 的接口大致是一样的)。

4

我最后用了这个问题里的代码,并修改了__init__语句,让它可以接受最多3个字符的列表。

import sys

class _Getch:
   """Gets a single character from standard input.  Does not echo to the
screen."""
   def __init__(self):
      self.impl = _GetchUnix()

   def __call__(self):# return self.impl()
      charlist = []
      counter = 0
      for i in range(3):
         try:charlist.append(self.impl())
         except:pass
         if charlist[i] not in [chr(27),chr(91)]:#TODO sort out escape vs arrow duh use len()
            break
         if len(charlist) > 1:
            if charlist == [chr(27),chr(27)]:
               break
      if len(charlist) == 3:
         if charlist[2] == 'a'
            return 'u-arr'
         if charlist[2] == 'b'
            return 'd-arr'
         if charlist[2] == 'c'
            return 'r-arr'
         if charlist[2] == 'd'
            return 'l-arr'
      if len(charlist == 2):
         if charlist == [chr(27),chr(27)]
            return chr(27)
      if len(charlist == 1)
         return charlist[0]
      return ''

class _GetchUnix:
   def __init__(self):
      import tty, sys

   def __call__(self):
      import sys, tty, termios
      fd = sys.stdin.fileno()
      old_settings = termios.tcgetattr(fd)
      try:
         tty.setraw(sys.stdin.fileno())
         ch = sys.stdin.read(1)
      finally:
         termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
      return ch

这样一来,我就能从键盘获取箭头键以及其他所有字符和转义序列,方便编辑器使用。这个“Getch”类不再只是简单的get char克隆,因为它返回的是一个字符串,但这样反而更有用了。

撰写回答