简体   繁体   中英

How do I accept input from arrow keys, or accept directional input?

This may be an xy problem, but I'm trying to to build a kernel based text editor, similar to vim or nano , and I know how to use the escape chars to clear the screen, then reprint, I can have it accept characters, but I'm not sure how to get it to accept arrow inputs for navigation. I thought there were ASCII values for them, but apparently not. Is there a way to use the arrows, or do I have to make a navigation mode and insert mode like vim ?

I've also briefly played with curses , but that was prohibitive because, as I understood, a whole new window had to be opened for it and this is not compatible with the vision of a single terminal window that I had.

Edit: curses was also prohibitive because it cleared the window, which I didn't want.

curses is exactly what you want. In fact I believe vim implements its interface with curses.

Try to put the following code into a file called test_curses.py :

import curses

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

Now open a terminal (not IDLE! a real terminal!) and run it via:

python test_curses.py

You should see that the terminal was cleared and an Hello World!!! writing appeared. Press any key and the program will stop, restoring the old terminal contents.

Note that the curses library isn't as easy and "user-friendly" as you may be accustomed to. I suggest reading the tutorial (unfortunately for the C language, but the python interface is mostly the same)

I wound up using the code from this question , and modifying the __init__ statement so that it accepted up to 3 characters in a list.

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

This allowed me to get arrow keys as well as all other characters and escape sequences from the keyboard for the editor. It made the "Getch" class not strictly a get char clone because it returns a string, but it wound up being much more useful.

The Python package click used for building commandline clients also comes with an implementation that allows you to get the key press events:

import click

key = click.getchar()

It returns the key representation as Unicode character and "things like arrow keys will show up in the platform's native escape format.".

This is the example taken straight from the click documentation on 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 :(')

To Perform desired Action on Arrow key or Any other key as it pressed

# 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) 

Then open terminal and run key_event_handler.py


This program is mainly to capture key pressed and get ascii of key pressed, This program can also be used for non-Blocking I/O in multy threaded aplications

I know that I'm late to the party, but I really liked click package mentioned by @elbaschid . I don't know why he wasn't upvoted - maybe because his example doesn't show how to handle specifically cursor keys.

Here is my $0.02 on that:

#!/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 ]) +'"' )

This handles cursor keys and as a bonus prints py-string representation of any keyboard shortcut it doesn't yet recognize. For example Ctrl-s is "\\x13" . You can later use it inside another

elif c == ??

I've tried to add edit to @elbaschid answer but it was rejected ¯\\_(ツ)_/¯. Please give him credit if you also like my answer

Awesome library for quick command-line prototyping.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM