45

I'm new to Python, and I just made a game and a menu in Python. Question is, that using (raw_)input() requires me to press enter after every keypress, I'd like to make it so that pressing down-arrow will instantly select the next menu item, or move down in the game. At the moment, it requires me to like type "down" and then hit enter. I also did quite a lot of research, but I would prefer not to download huge modules (e.g. pygame) just to achieve a single keyDown() method. So are there any easier ways, which I just couldn't find?

Edit: Just found out that msvcrt.getch() would do the trick. It's not keyDown(), but it works. However, I'm not sure how to use it either, it seems quite weird, any help here? This is what I got at the moment:

from msvcrt import getch
while True:
    key = getch()
    print(key)

However, it keeps giving me all these nonsense bytes, for example, down-arrow is this:

b'\xe0'
b'P'

And I have no idea how to use them, I've tried to compare with chr() and even use ord() but can't really do any comparisons. What I'm trying to do is basically this:

from msvcrt import getch
while True:
    key = getch()
    if key == escape:
        break
    elif key == downarrow:
        movedown()
    elif key == 'a':
        ...

And so on... Any help?

Kresten
  • 100
  • 2
  • 9
  • Not a duplicate of that. This is about keydown events, not single character input. – Deestan Aug 29 '12 at 10:48
  • 10
    I can haz cross-platform solution?? `msvcrt` is not available on mac/linux distributions of Python – cat Jan 11 '16 at 13:54

7 Answers7

65

Figured it out by testing all the stuff by myself. Couldn't find any topics about it tho, so I'll just leave the solution here. This might not be the only or even the best solution, but it works for my purposes (within getch's limits) and is better than nothing.

Note: proper keyDown() which would recognize all the keys and actual key presses, is still valued.

Solution: using ord()-function to first turn the getch() into an integer (I guess they're virtual key codes, but not too sure) works fine, and then comparing the result to the actual number representing the wanted key. Also, if I needed to, I could add an extra chr() around the number returned so that it would convert it to a character. However, I'm using mostly down arrow, esc, etc. so converting those to a character would be stupid. Here's the final code:

from msvcrt import getch
while True:
    key = ord(getch())
    if key == 27: #ESC
        break
    elif key == 13: #Enter
        select()
    elif key == 224: #Special keys (arrows, f keys, ins, del, etc.)
        key = ord(getch())
        if key == 80: #Down arrow
            moveDown()
        elif key == 72: #Up arrow
            moveUp()

Also if someone else needs to, you can easily find out the keycodes from google, or by using python and just pressing the key:

from msvcrt import getch
while True:
    print(ord(getch()))
Kresten
  • 100
  • 2
  • 9
  • 1
    I am using the above code, but my code simply blocks at getch(), and nothing happens then. any help ? – Anum Sheraz May 09 '16 at 18:48
  • 4
    @AnumSheraz The above method only works when you run the code from command prompt. – Moondra May 27 '17 at 15:32
  • 1
    if you cast a char to bytes first you can compare directly with keypressed 'keypressed == bytes('q', 'utf-8')' checks if q was pressed. It will work for special keys like enter or esc but you need to know the codes for those (esc is '\x1b' for example) – Xitcod13 May 24 '18 at 03:13
10

See the MSDN getch docs. Specifically:

The _getch and_getwch functions read a single character from the console without echoing the character. None of these functions can be used to read CTRL+C. When reading a function key or an arrow key, each function must be called twice; the first call returns 0 or 0xE0, and the second call returns the actual key code.

The Python function returns a character. you can use ord() to get an integer value you can test, for example keycode = ord(msvcrt.getch()).

So if you read an 0x00 or 0xE0, read it a second time to get the key code for an arrow or function key. From experimentation, 0x00 precedes F1-F10 (0x3B-0x44) and 0xE0 precedes arrow keys and Ins/Del/Home/End/PageUp/PageDown.

Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
  • Well I figured it by now, but can't post the final solution. But this + ord() + char() –  Aug 29 '12 at 13:26
4

I really did not want to post this as a comment because I would need to comment all answers and the original question.

All of the answers seem to rely on MSVCRT Microsoft Visual C Runtime. If you would like to avoid that dependency :

In case you want cross platform support, using the library here:

https://pypi.org/project/getkey/#files

https://github.com/kcsaff/getkey

Can allow for a more elegant solution.

Code example:

from getkey import getkey, keys
key = getkey()
if key == keys.UP:
  ...  # Handle the UP key
elif key == keys.DOWN:
  ...  # Handle the DOWN key
elif key == 'a':
  ...  # Handle the `a` key
elif key == 'Y':
  ...  # Handle `shift-y`
else:
  # Handle other text characters
  buffer += key
  print(buffer)
Community
  • 1
  • 1
Ken
  • 2,518
  • 2
  • 27
  • 35
  • Installing a special library ('getkey') is a very ineffective solution when already an built-in module (msvcrt) works just fine! – Apostolos Aug 31 '20 at 09:29
  • 2
    @Apostolos as he said in his solution, msvcrt is only available on Windows, getkey is cross platform. – InxaneNinja Sep 03 '20 at 20:40
  • I see what you mean. But think: Since every platform has **its own** built-in "getkey" module (as 'msvcrt' in Windows), installing an extra "getkey" module will **always** be useless! :) (Simple logic) – Apostolos Sep 05 '20 at 08:28
  • 2
    @Apostolos Try Ctrl + Keys with mscvrt, also look at how much more readable the code is and now think past a one box wonder. The code I posted can be installed and utilized on all platforms and not rely on anything else - completely portable, and one stop maintainability - It just works, try that with code built on Windows msvcrt. If you rely on the platform, you are bound to the platform and your code is platform specific and maintaining unique versions for each platform is not very wise or efficient - though it does make for busy work. – Ken Sep 27 '21 at 22:02
  • That's fine. The essence is to **do well the job you have to do**. I personally, have built hundreads of PY files, and I 'mscvrt' always covers my totally. Otherwise, I wouldn't have any problem using some external module, as I often do with other subjects. – Apostolos Sep 29 '21 at 10:08
2
from msvcrt import getch

pos = [0, 0]

def fright():
    global pos
    pos[0] += 1

def fleft():
    global pos 
    pos[0] -= 1

def fup():
    global pos
    pos[1] += 1

def fdown():
    global pos
    pos[1] -= 1

while True:
    print'Distance from zero: ', pos
    key = ord(getch())
    if key == 27: #ESC
        break
    elif key == 13: #Enter
        print('selected')
    elif key == 32: #Space
        print('jump')
    elif key == 224: #Special keys (arrows, f keys, ins, del, etc.)
        key = ord(getch())
        if key == 80: #Down arrow
            print('down')
            fdown
        elif key == 72: #Up arrow
            print('up')
            fup()
        elif key == 75: #Left arrow
            print('left')
            fleft()
        elif key == 77: #Right arrow
            print('right')
            fright()
Tommy L
  • 140
  • 11
  • 3
    Please add some explanation to your answer to explain your code. – AJ123 Nov 03 '17 at 16:49
  • Why you have to do another key = ord(getch()) when you press arrows keys? In the first time, you get a 224, but it gets right away the correct code for that key. How? – luturol Feb 05 '20 at 16:47
  • @luturol- because some keys return 2 bytes (arrow keys, function keys, etc). A second getch is required to see WHICH arrow key or function key was pressed..... – Robatron Dec 15 '21 at 03:44
1

I was also trying to achieve this. From above codes, what I understood was that you can call getch() function multiple times in order to get both bytes getting from the function. So the ord() function is not necessary if you are just looking to use with byte objects.

while True :
    if m.kbhit() :
        k = m.getch()
        if b'\r' == k :
            break
        elif k == b'\x08'or k == b'\x1b':
            # b'\x08' => BACKSPACE
            # b'\x1b' => ESC
            pass
        elif k == b'\xe0' or k == b'\x00':
            k = m.getch()
            if k in [b'H',b'M',b'K',b'P',b'S',b'\x08']:
                # b'H' => UP ARROW
                # b'M' => RIGHT ARROW
                # b'K' => LEFT ARROW
                # b'P' => DOWN ARROW
                # b'S' => DELETE
                pass
            else:
                print(k.decode(),end='')
        else:
            print(k.decode(),end='')

This code will work print any key until enter key is pressed in CMD or IDE (I was using VS CODE) You can customize inside the if for specific keys if needed

1

It's really late now but I made a quick script which works for Windows, Mac and Linux, simply by using each command line:

import os, platform

def close():
    if platform.system() == "Windows":
        print("Press any key to exit . . .")
        os.system("pause>nul")
        exit()
    
    elif platform.system() == "Linux":
        os.system("read -n1 -r -p \"Press any key to exit . . .\" key")
        exit()
    
    elif platform.system() == "Darwin":
        print("Press any key to exit . . .")
        os.system("read -n 1 -s -p \"\"")
        exit()
    
    else:
        exit()

It uses only inbuilt functions, and should work for all three (although I've only tested Windows and Linux...).

Amos Lobel
  • 11
  • 1
  • This is a terrible solution. This is not even using inbuilt functions. You are opening a shell subprocess and running linux commands just for detecting a keypress – Joel G Mathew Jul 17 '21 at 21:42
-1

I suggest keyboard module by BoppreH. This example shows how to detect keypresses outside of python console in a non blocking way. When you press the f key it calls a function that prints some text . To end the execution of the script press the q key. For more info and special key codes click here

import keyboard  

def my_function():
    print("button pressed")

def my_quit():
    global mybool
    mybool = False

keyboard.add_hotkey('f', my_function)
keyboard.add_hotkey('q', my_quit)

mybool = True

while mybool:
    keyboard.read_key()

If you want to pause the execution of the script intsead of add_hotkey() use keyboard.wait('esc')

mustafa candan
  • 567
  • 5
  • 16
  • Please consider adding some explanation. Code-only answers are discouraged. Also, for my edification: isn't keyboard a module to add global hot-keys to your system (that is, to react to those keystrokes wherever they are made)? It is not very convenient to not be able to type neither 'a' nor 'q' in no other application, email, browser, etc. – chrslg Dec 07 '22 at 10:11
  • More explanation added. Yes you are right it detect key presses in all your system. The keys are just an example and can be changed as liked. – mustafa candan Dec 07 '22 at 18:53