Welcome, guest | Sign In | My Account | Store | Cart
7

A small utility class to read single characters from standard input, on both Windows and UNIX systems. It provides a getch() function-like instance.

Python, 38 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class _Getch:
    """Gets a single character from standard input.  Does not echo to the
screen."""
    def __init__(self):
        try:
            self.impl = _GetchWindows()
        except ImportError:
            self.impl = _GetchUnix()

    def __call__(self): return self.impl()


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


class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()


getch = _Getch()

18 comments

Tobias Polzin 14 years, 5 months ago  # | flag

old python versions have to include TERMIOS. If you use an old python version (e.g. 1.5) you have to include "TERMIOS" additionally to "termios" and the "TCSADRAIN" is found in "TERMIOS".

C Smith 13 years, 10 months ago  # | flag

extended for MacOS.

A few modifications make this work for the mac with Carbon,
too: a modification to the _Getch to try one more import, a
modification to the Unix init, and the inclusion of the instructions
for the MacOS with Carbon support.


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

    def __call__(self): return self.impl()


class _GetchUnix:
    def __init__(self):
        import tty, sys, termios # import termios now or else you'll get the Unix version on the Mac

    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

class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()


class _GetchMacCarbon:
    """
    A function which returns the current ASCII key that is down;
    if no ASCII key is down, the null string is returned.  The
    page http://www.mactech.com/macintosh-c/chap02-1.html was
    very helpful in figuring out how to do this.
    """
    def __init__(self):
        import Carbon

    def __call__(self):
        import Carbon
        if Carbon.Evt.EventAvail(0x0008)[0]==0: # 0x0008 is the keyDownMask
            return ''
        else:
            #
            # The event contains the following info:
            # (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
            #
            # The message (msg) contains the ASCII char which is
            # extracted with the 0x000000FF charCodeMask; this
            # number is converted to an ASCII character with chr() and
            # returned
            #
            (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
            return chr(msg <pre>
A few modifications make this work for the mac with Carbon,
too: a modification to the _Getch to try one more import, a
modification to the Unix init, and the inclusion of the instructions
for the MacOS with Carbon support.

(comment continued...)

C Smith 13 years, 10 months ago  # | flag

(...continued from previous comment)

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

    def __call__(self): return self.impl()


class _GetchUnix:
    def __init__(self):
        import tty, sys, termios # import termios now or else you'll get the Unix version on the Mac

    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

class _GetchWindows:
    def __init__(self):
        import msvcrt

    def __call__(self):
        import msvcrt
        return msvcrt.getch()


class _GetchMacCarbon:
    """
    A function which returns the current ASCII key that is down;
    if no ASCII key is down, the null string is returned.  The
    page http://www.mactech.com/macintosh-c/chap02-1.html was
    very helpful in figuring out how to do this.
    """
    def __init__(self):
        import Carbon

    def __call__(self):
        import Carbon
        if Carbon.Evt.EventAvail(0x0008)[0]==0: # 0x0008 is the keyDownMask
            return ''
        else:
            #
            # The event contains the following info:
            # (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
            #
            # The message (msg) contains the ASCII char which is
            # extracted with the 0x000000FF charCodeMask; this
            # number is converted to an ASCII character with chr() and
            # returned
            #
            (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
            return chr(msg

</pre>

C Smith 13 years, 10 months ago  # | flag

erratum for MacOS modification.

Sorry about the repeated text.  I see that the ampersand
inside the &ltpre&gt; tag is what caused it...and it also caused
the last line to appear incorrectly.  The last line should
read

return chr(msg &amp; 0x000000FF)
C Smith 11 years, 10 months ago  # | flag

updated for OS X. The following code (along with the _GetUnix and _GetWindows above) gives the correct getch behavior whether imported in the pythonIDE or in a Terminal script on the Mac. The order of trial imports is changed in _Getch because when in the IDE, the Unix import was succeeding. A single line in the _GetMacCarbon was added to see if the Carbon module has the Evt method. When the import succeeds when in the Terminal, the Carbon library there does not have the Evt method and so the import fails and the _GetchUnix() line is executed.

I also found that the curses snippet at < http://www.pythonapocrypha.com/Chapter22/Chapter22.shtml > works in the Terminal but will cause the pythonIDE to quit without warning if you run it there.

###

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

    def __call__(self): return self.impl()

class _GetchMacCarbon:
    """
    A function which returns the current ASCII key that is down;
    if no ASCII key is down, the null string is returned.  The
    page http://www.mactech.com/macintosh-c/chap02-1.html was
    very helpful in figuring out how to do this.
    """
    def __init__(self):
        import Carbon
        Carbon.Evt #see if it has this (in Unix, it doesn't)

    def __call__(self):
        import Carbon
        if Carbon.Evt.EventAvail(0x0008)[0]==0: # 0x0008 is the keyDownMask
            return ''
        else:
            #
            # The event contains the following info:
            # (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
            #
            # The message (msg) contains the ASCII char which is
            # extracted with the 0x000000FF charCodeMask; this
            # number is converted to an ASCII character with chr() and
            # returned
            #
            (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]
            return chr(msg &amp; 0x000000FF)

if __name__ == '__main__': # a little test
   print 'Press a key'
   inkey = _Getch()
   import sys
   for i in xrange(sys.maxint):
      k=inkey()
      if k&lt;&gt;'':break
   print 'you pressed ',k
###
Cullen Newsom 11 years ago  # | flag

getch with IDLE. I was unable to make this work in IDLE on OSX. I guess because of what IDLE seems to do to stdio. I always got "AttributeError".

qubodup qubodup 8 years, 12 months ago  # | flag

Carbon less invasive? Hi, I tried the code on linux and got " ImportError: No module named Carbon"

You should wrap the carbon loading in a "try:" I suppose (I'm not experienced enough to post an example, as I eg. don't know what to write into "except ImportError:" if I want nothing to happen.

galli.87 7 years, 9 months ago  # | flag

This should do the trick:

class _Getch:
    """
    Gets a single character from standard input.  Does not echo to
    the screen.
    """
    def __init__(self):
        try:
            self.impl = _GetchWindows()
        except ImportError:
            try:
                self.impl = _GetchMacCarbon()
            except(AttributeError, ImportError):
                self.impl = _GetchUnix()

    def __call__(self): return self.impl()
Stephen Chappell 7 years, 2 months ago  # | flag

With regards to Windows, may I suggest the following?

def readChar(echo=True):
    "Get a single character on Windows."
    while msvcrt.kbhit():
        msvcrt.getch()
    ch = msvcrt.getch()
    while ch in b'\x00\xe0':
        msvcrt.getch()
        ch = msvcrt.getch()
    if echo:
        msvcrt.putch(ch)
    return ch.decode()
d04jfa 6 years, 9 months ago  # | flag

using curses with unicode

Does this work on windows? (Note: this assumes utf8 input.)

#!/usr/bin/python
# coding=UTF-8

import curses
import locale

locale.setlocale(locale.LC_ALL,"")

def get_ch_gen(scr):
    while True:
        ch=scr.getch() # pauses until a key's hit
        scr.nodelay(1)
        bs=[]
        while ch != -1: # check if more data is available
            if ch > 255: # directly yield arrowkeys etc as ints
                yield ch
            else:
                bs.append(chr(ch))
                ch=scr.getch()
        scr.nodelay(0)
        for ch in ''.join(bs).decode('utf8'):
            yield ch

def doStuff2(stdscr):
    text = list(u"˙ㄚㄞㄢㄦㄗㄧㄛㄟㄣ"*5)
    import random
    random.shuffle(text)

    stdscr.addstr((''.join(reversed(text)) + '\n').encode("utf-8"))
    for ch in get_ch_gen(stdscr):
        if len(text) == 0:
            break
        if ch == text[-1]:
            stdscr.addstr(text.pop().encode("utf-8"))

def doStuff(stdscr):
    getch=get_ch_gen(stdscr).next

    stdscr.addstr(u'Testing... type å, ä, ö or q to quit\n'.encode("utf-8"))
    while True:
        if getch() in u'åäöq':
            break

curses.wrapper(doStuff)
Barry Walker 5 years, 3 months ago  # | flag

Anything that can stand the test of time deserves praise. This is one piece of code that is definitely in that category.

Well done and......

......many thanks Danny.

I have "pinched" part of it for my usage and made a pointer to this code snippet in my code too...

Here:-

http://code.activestate.com/recipes/577728-simpletron3xpy-game-to-demo-xy-drawing-using-the-k/?in=user-4177147

Thanks again and voted it up another place...

Bazza, G0LCU.

BTW, I will soon be uploading a Morse Practice Oscillator using you code too... ;o)

Ankit Daftery 4 years, 9 months ago  # | flag

I used the select module for a Non-Blocking implementation on Linux. This times out in (5 seconds here) if no input is received. Particularly useful when used in a thread, so that the getch call is non-blocking and will allow the thread to exit cleanly

{{{ http://code.activestate.com/recipes/134892/ (r2)

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

def __call__(self): return self.impl()

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

def __call__(self):
    import sys, tty, termios
    from select import select
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(sys.stdin.fileno())
        [i, o, e] = select([sys.stdin.fileno()], [], [], 5)
        if i: ch=sys.stdin.read(1)
        else: ch=''     
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch

class _GetchWindows: def __init__(self): import msvcrt

def __call__(self):
    import msvcrt
    return msvcrt.getch()

getch = _Getch()

end of http://code.activestate.com/recipes/134892/ }}}
Mohammad Etemaddar 3 years, 11 months ago  # | flag

I have problem with the main code in linux.

It returns:

<__main__._Getch instance at 0x7f826cb82fc8>

And of course it doesn't wait to get any character.

I used it in this way:

getch = _Getch()

print getch
wayne roberts 3 years, 3 months ago  # | flag

do this: print getch.impl()

gogobebe2 3 years, 2 months ago  # | flag

Hi, I'm 14 years old. My name's william. I've being making this program and it works fine in windows but not linux? I am running Linux Arch (E17 desktop environment) with both Python3 and Python2 installed. There are no errors but when I start the program, it prints it but when I push the arrow keys nothing happens?

Here it is: https://github.com/Gogo-Programming/You-wake-up

And here is what it looks like on linux (And yes my terminal has red text ^): http://www.enlightenment.org/ss/e-52d4d7716efc53.35386887.png

Help much appreciated (Im a bit of a noob with python so sorry if this is a stupid question to you pros)

Miguel Angel 2 years, 10 months ago  # | flag

Hi there!!

I needed this functionality too and it was quite difficult to follow this thread. Because of that, I created the readchar library (https://pypi.python.org/pypi/readchar) in order to make it reusable.

I mention this thread, but I changed the license to MIT in order to allow everyone to use and change it everywhere.

Please, if you want to change anything or to test it in OSX/Windows (only GNU/Linux is being tested right now), please, send your comments, add an issue or send your pull requests!

Thank you very much!

Mikael Ljunggren 2 years, 2 months ago  # | flag

Hi,

I try to use this example to read a single character from stdin but get the error

AttributeError: _Getch instance has no attribute '__trunc__'

It is used in the following code

cursamp  = (int)((int)(_Getch()) | (int)(_Getch())<<8)

Best Regards, Mikael

Daniel Lynes 1 year, 7 months ago  # | flag

Thank you so much Danny Yoo for the original post, and C Smith for the updates for Mac. It truly makes it a nice cross-platform piece of code.

Add a comment

Sign in to comment