40

I am trying to find the values that my local system assigns to the arrow keys, specifically in Python. I am using the following script to do this:

import sys,tty,termios
class _Getch:       
    def __call__(self):
            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

def get():
    inkey = _Getch()
    while(1):
            k=inkey()
            if k!='':break
    print 'you pressed', ord(k)

def main():
    for i in range(0,25):
        get()

if __name__=='__main__':
    main()

Then I ran the script, and hit UP DOWN RIGHT LEFT, which gave me this output:

$ python getchar.py 
you pressed 27
you pressed 91
you pressed 65
you pressed 27
you pressed 91
you pressed 66
you pressed 27
you pressed 91
you pressed 67
you pressed 27
you pressed 91
you pressed 68

This is anomalous because it suggests that the arrow keys are registered as some form of triple (27-91-6x) on my system, as each press of an arrow key takes up three instances of get(). By comparison, pressing a,b,c and CTRL-C gives:

you pressed 97
you pressed 98
you pressed 99
you pressed 3

Can anyone explain to me why the values of my arrow-keys seem to be stored as triples? Why is this is so? Is this the same across all platforms? (I'm using Debian Linux.) If not, how should I go about storing the values of the arrow-keys?

The end goal here is in that I'm trying to write a program which needs to correctly recognize arrow-keys and perform a function depending on which arrow-key was pressed.

Zero Piraeus
  • 56,143
  • 27
  • 150
  • 160
Newb
  • 2,810
  • 3
  • 21
  • 35
  • I would suggest [ANSI escape code](http://en.wikipedia.org/wiki/ANSI_escape_code) even though that lacks a clear section on escape codes that are sent by the keyboard; their format is the same as those sent from programs to the terminal. – Dan D. Mar 14 '14 at 06:10
  • This seems relevant: http://stackoverflow.com/questions/4130048/recognizing-arrow-keys-with-stdin – Newb Mar 14 '14 at 06:33
  • Why do you have a `_Getch` class with a `__call__` method instead of just a `getch` function? – user2357112 Mar 15 '14 at 04:13
  • @user2357112 because I need to call an instance of it? It doesn't work as a function. – Newb Mar 16 '14 at 01:58
  • 2
    It works perfectly well as a function. Just turn the `__call__` method into a top-level `getch` function, remove the `self` parameter, and call `getch()` instead of creating an `inkey` instance and calling `inkey()`. Even if you need to pass it around as an object, you can do that with a `getch` function directly. – user2357112 Mar 16 '14 at 02:05
  • It looks like the original source you pulled this from needed to do some initialization work to determine what implementation to use. You've removed the OS-detection work and all the stuff needed to support it, so a class is no longer necessary. – user2357112 Mar 16 '14 at 02:07
  • @user2357112 I did exactly that, and it didn't work for me; unless I made a mistake somewhere. – Newb Mar 16 '14 at 02:12
  • You made a mistake somewhere. I don't know what the mistake was, but it should've worked. – user2357112 Mar 16 '14 at 02:16
  • @user2357112 You're right. I got it to work. – Newb Mar 16 '14 at 22:34

3 Answers3

48

I think I figured it out.

I learned from here that each arrow key is represented by a unique ANSI escape code. Then I learned that the ANSI escape codes vary by system and application: in my terminal, hitting cat and pressing the up-arrow gives ^[[A, in C it seems to be \033[A, etc. The latter part, the [A, remains the same, but the code for the preceding Escape can be in hex(beginning with an x), octal (beginning with a 0), or decimal(no lead in number).

Then I opened the python console, and plugged in the triples I had previously received, trying to find their character values. As it turned out, chr(27) gave \x1b, chr(91) gave [, and calling chr on 65,66,67,68 returned A,B,C,D respectively. Then it was clear: \x1b was the escape-code!

Then I noted that an arrow key, in ANSI represented as a triple, is of course represented as three characters, so I needed to amend my code so as to read in three characters at a time. Here is the result:

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

def get():
        inkey = _Getch()
        while(1):
                k=inkey()
                if k!='':break
        if k=='\x1b[A':
                print "up"
        elif k=='\x1b[B':
                print "down"
        elif k=='\x1b[C':
                print "right"
        elif k=='\x1b[D':
                print "left"
        else:
                print "not an arrow key!"

def main():
        for i in range(0,20):
                get()

if __name__=='__main__':
        main()
Community
  • 1
  • 1
Newb
  • 2,810
  • 3
  • 21
  • 35
  • Good call with using a loop in main, that allows you to pass multiple ascii values (the escape and following chars) to your getch() function. It would be more efficient if you put the `for` loop in the _Getch function though. – jfa Apr 02 '14 at 22:13
  • Can this be extended to be interrupted by ctrl+c (and other signals)? – ThorSummoner Jan 23 '15 at 04:29
  • @ThorSummoner yes. You can use the get() function, just add another `elif`, and watch for signals like ctrl+c. However, there are easier ways to do this. – Newb Jan 23 '15 at 09:47
  • @newb I'm all for easy, What would you suggest that is easier? – ThorSummoner Jan 23 '15 at 18:26
  • @ThorSummoner IIRC you can do this sort of keystroke monitoring very easily with the curses library if you're just working in a terminal. – Newb Jan 24 '15 at 03:05
  • 1
    @ThorSummoner in `raw` mode the control characters for ctrl c are treated as character input. To keep the "default" behavior of ctrl+c one needs to set cbreak mode (`tty.setcbreak(...)`) instead. For ctrl+c works in my experience, but I can't say about other control sequences. – h7r Apr 18 '15 at 15:06
  • 1
    Sooo.. here's a follow-up question: Can you tell the difference between the ESC _key_ (which produces a single `\x27`) and the Escape sequence _code_ (also `\x27`) that is expecting further characters? –  Mar 15 '17 at 00:06
8

I am using Mac and I used the following code and it worked well: I got the values for my arrow keys as 0,1,2,3 (Up, Down, Left, Right): Always good to remember code 27 for ESC key too. Best regards!

while True:
    key = cv2.waitKey(1) & 0xFF

    # if the 'ESC' key is pressed, Quit
    if key == 27:
        quit()
    if key == 0:
        print "up"
    elif key == 1:
        print "down"
    elif key == 2:
        print "left"
    elif key == 3:
        print "right"
    # 255 is what the console returns when there is no key press...
    elif key != 255:
        print(key)
Mauricio Alo
  • 359
  • 4
  • 6
  • 2
    This is good for openCV scripts, but without cv2 window, `key` might get an infinite `255`. – Amarth Gûl Feb 21 '19 at 23:32
  • On Ubuntu, the values are `UP ARROW = 82, DOWN ARROW = 84, LEFT ARROW = 81, RIGHT ARROW = 83`. Quite sure this would apply to other OS'es too. – Nav Feb 13 '23 at 18:16
0

here is a small variant of the solution which will read normal characters and arrow keys at the same time.

import sys
import select
import tty
import termios

def isData():
    return select.select([sys.stdin], [], [], 0) == ([sys.stdin], [], [])

old_settings = termios.tcgetattr(sys.stdin)
try:
    tty.setcbreak(sys.stdin.fileno())

 
    while 1:
        if isData():
            k = sys.stdin.read(1)
            if k == 'q':  
                break
            elif k=='\x1b':
                kk = sys.stdin.read(2)
                if kk == 'OA':
                    print("up")
                elif kk=='OB':
                    print("down")
                elif kk=='OC':
                    print("right")
                elif kk=='OD':
                    print("left")
                else:
                    print("-->", kk.encode('unicode_escape'))
            else:
                print("char pressed: ", k)
                    
finally:
    termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
tong
  • 1
  • Users find it difficult to understand code only answers with no explanation. Please add some description explaining what is does and how it solves the problem or add comments in the source code at appropriate places. – Azhar Khan Feb 09 '23 at 05:10