Skip to content

Instantly share code, notes, and snippets.

@michelbl
Last active August 2, 2023 03:53
Show Gist options
  • Save michelbl/efda48b19d3e587685e3441a74457024 to your computer and use it in GitHub Desktop.
Save michelbl/efda48b19d3e587685e3441a74457024 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
'''
A Python class implementing KBHIT, the standard keyboard-interrupt poller.
Works transparently on Windows and Posix (Linux, Mac OS X). Doesn't work
with IDLE.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
'''
import os
# Windows
if os.name == 'nt':
import msvcrt
# Posix (Linux, OS X)
else:
import sys
import termios
import atexit
from select import select
class KBHit:
def __init__(self):
'''Creates a KBHit object that you can call to do various keyboard things.
'''
if os.name == 'nt':
pass
else:
# Save the terminal settings
self.fd = sys.stdin.fileno()
self.new_term = termios.tcgetattr(self.fd)
self.old_term = termios.tcgetattr(self.fd)
# New terminal setting unbuffered
self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)
# Support normal-terminal reset at exit
atexit.register(self.set_normal_term)
def set_normal_term(self):
''' Resets to normal terminal. On Windows this is a no-op.
'''
if os.name == 'nt':
pass
else:
termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)
def getch(self):
''' Returns a keyboard character after kbhit() has been called.
Should not be called in the same program as getarrow().
'''
s = ''
if os.name == 'nt':
return msvcrt.getch().decode('utf-8')
else:
return sys.stdin.read(1)
def getarrow(self):
''' Returns an arrow-key code after kbhit() has been called. Codes are
0 : up
1 : right
2 : down
3 : left
Should not be called in the same program as getch().
'''
if os.name == 'nt':
msvcrt.getch() # skip 0xE0
c = msvcrt.getch()
vals = [72, 77, 80, 75]
else:
c = sys.stdin.read(3)[2]
vals = [65, 67, 66, 68]
return vals.index(ord(c.decode('utf-8')))
def kbhit(self):
''' Returns True if keyboard character was hit, False otherwise.
'''
if os.name == 'nt':
return msvcrt.kbhit()
else:
dr,dw,de = select([sys.stdin], [], [], 0)
return dr != []
# Test
if __name__ == "__main__":
kb = KBHit()
print('Hit any key, or ESC to exit')
while True:
if kb.kbhit():
c = kb.getch()
c_ord = ord(c)
print(c)
print(c_ord)
if c_ord == 27: # ESC
break
print(c)
kb.set_normal_term()
@morin-zoom
Copy link

morin-zoom commented Feb 16, 2021

this doesn't work in python 3
windows10 understands unicode quite well, so you can use msvcrt.getwch
a small example

# coding: utf-8
import msvcrt


while True:
    while msvcrt.kbhit():
        key_in = msvcrt.getwch()
        #arrows, function keys, and so on
        if key_in.encode() == b'\xc3\xa0' or key_in.encode() == b'\x00':
            print(key_in.encode(), end='')
            key_in = msvcrt.getwch()
        #________________________________
        print(key_in.encode())
        print(key_in)
        print('_____')

@MarioEduardoVienna
Copy link

MarioEduardoVienna commented Feb 22, 2023

Is there a possibilty to get on Windows and Linux the same KeyCodes. e.g. F1 behaves different ...

@jhkwag970
Copy link

Hello, great work! Can I be able to use the code for my research work? I will site your github. Thank you!

@michelbl
Copy link
Author

You can do whatever you want with this piece of code.

@griiid
Copy link

griiid commented Jun 26, 2023

Is it possible to get 0x03 when pressing ctrl-c?
How to modify the code?

@LcyDev
Copy link

LcyDev commented Aug 2, 2023

import os

if os.name == 'nt':
    import msvcrt
else:
    import sys
    import termios
    import atexit
    from select import select

class KBHit:
    def __init__(self):
        '''
        Creates a KBHit object that you can call to do various keyboard things.
        '''
        if os.name != 'nt':
            self._setup_posix()

    def _setup_posix(self):
        '''
        Sets up the terminal to read non-blocking input.
        '''
        # Save the terminal settings
        self.fd = sys.stdin.fileno()
        self.new_term = termios.tcgetattr(self.fd)
        self.old_term = termios.tcgetattr(self.fd)
        # New terminal setting unbuffered
        self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)
        termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)
        # Support normal-terminal reset at exit
        atexit.register(self._reset_posix_term)

    def _reset_posix_term(self):
        # Resets to normal terminal. On Windows, this is a no-op.
        if os.name != 'nt':
            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)

    def getch(self):
        '''
        Returns a keyboard character after kbhit() has been called.
        '''
        if os.name == 'nt':
            return msvcrt.getch().decode('utf-8')
        else:
            return sys.stdin.read(1)

    def get_arrow(self):
        '''
        Returns an arrow-key code after kbhit() has been called. Codes are:
        0: up, 1: right, 2: down, 3: left
        '''
        if os.name == 'nt':
            msvcrt.getch()  # skip 0xE0
            c = msvcrt.getch()
            vals = [72, 77, 80, 75]
        else:
            c = sys.stdin.read(3)[2]
            vals = [65, 67, 66, 68]

        return vals.index(ord(c.decode('utf-8')))

    def kbhit(self):
        '''
        Returns True if a keyboard character was hit, False otherwise.
        '''
        if os.name == 'nt':
            return msvcrt.kbhit()
        else:
            dr, dw, de = select([sys.stdin], [], [], 0)
            return dr != []
    
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if os.name != 'nt':
            self._reset_posix_term()


# Test
if __name__ == "__main__":
    kb = KBHit()
    print('Hit any key, or ESC to exit')

    with KBHit() as kb:
        try:
            while True:
                if kb.kbhit():
                    c = kb.getch()
                    c_ord = ord(c)
                    print(c)
                    print(c_ord)
                    if c_ord == 27:  # ESC
                        break
                    print(c)
        except KeyboardInterrupt:
            pass

A lil' refactor, nothing much

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment