From 80e68637faa2c392120649e05fa8ad1e0dbb2efc Mon Sep 17 00:00:00 2001 From: guiweber Date: Fri, 4 Mar 2016 21:43:34 -0500 Subject: [PATCH 1/5] Fixed most issues on Windows + Python 3.5 Moved the linux loop in the linux code file --- .gitignore | 1 + readchar/key_windows.py | 100 +++++++++++++++++++++++++++++++++++ readchar/readchar.py | 12 +---- readchar/readchar_linux.py | 15 +++++- readchar/readchar_windows.py | 30 ++++++++--- 5 files changed, 139 insertions(+), 19 deletions(-) create mode 100644 readchar/key_windows.py diff --git a/.gitignore b/.gitignore index 4301da0..d7966b3 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ venv/ coverage.xml .eggs *.egg +.idea diff --git a/readchar/key_windows.py b/readchar/key_windows.py new file mode 100644 index 0000000..906564e --- /dev/null +++ b/readchar/key_windows.py @@ -0,0 +1,100 @@ +# These following keys are ones that have 2 meanings. +# The first key command is the one that is not in the dict below: +# ctrl_i is tab +# ctrl_[ is escape +# ctrl_2 is ctrl_c +# ctrl_h is backspace +# ctrl_j is ctrl_enter +# ctrl_m is enter + +windows_special_keys = { + # Single keys: + 'H': 'up', + 'M': 'right', + 'P': 'down', + 'K': 'left', + ';': 'f1', + '<': 'f2', + '=': 'f3', + '>': 'f4', + '?': 'f5', + '@': 'f6', + 'A': 'f7', + 'B': 'f8', + 'C': 'f9', + 'D': 'f10', + 'S': 'delete', + # key combos & shifted_keys + 'T': 'shift_f1', + 'U': 'shift_f2', + 'V': 'shift_f3', + 'W': 'shift_f4', + 'X': 'shift_f5', + 'Y': 'shift_f6', + 'Z': 'shift_f7', + '[': 'shift_f8', + '\\\\': 'shift_f9', + ']': 'shift_f10', + # ctrl_keys + '^': 'ctrl_f1', + '_': 'ctrl_f2', + '`': 'ctrl_f3', + 'a': 'ctrl_f4', + 'b': 'ctrl_f5', + 'c': 'ctrl_f6', + 'd': 'ctrl_f7', + 'e': 'ctrl_f8', + 'f': 'ctrl_f9', + 'g': 'ctrl_f10', + '\\x8d': 'ctrl_up', + '\\x91': 'ctrl_down', + 's': 'ctrl_left', + 't': 'ctrl_right', +} + +windows_keys = { + # Single keys: + '\\x1b': 'escape', + ' ': 'space', + '\\x85': 'f11', + '\\x86': 'f12', + '\\x08': 'backspace', + '\\r': 'enter', + '\\t': 'tab', + # key combos & shifted_keys + '\\x87': 'shift_f11', + '\\x88': 'shift_f12', + # ctrl_keys + '\\x89': 'ctrl_f11', + '\\x8a': 'ctrl_f12', + '\\x93': 'ctrl_delete', + '\\n': 'ctrl_enter', + '\\x94': 'ctrl_tab', + '\\x7f': 'ctrl_backspace', + '\\x1c': 'ctrl_\\', + '\\x1d': 'ctrl_]', + # ctrl letters + '\\x01': 'ctrl_a', + '\\x02': 'ctrl_b', + '\\x03': 'ctrl_c', + '\\x04': 'ctrl_d', + '\\x05': 'ctrl_e', + '\\x06': 'ctrl_f', + '\\x07': 'ctrl_g', + '\\x0b': 'ctrl_k', + '\\x0c': 'ctrl_l', + '\\x0e': 'ctrl_n', + '\\x0f': 'ctrl_o', + '\\x10': 'ctrl_p', + '\\x11': 'ctrl_q', + '\\x12': 'ctrl_r', + '\\x13': 'ctrl_s', + '\\x14': 'ctrl_t', + '\\x15': 'ctrl_u', + '\\x16': 'ctrl_v', + '\\x17': 'ctrl_w', + '\\x18': 'ctrl_x', + '\\x19': 'ctrl_y', + '\\x1a': 'ctrl_z', + '\\\\': '\\', +} diff --git a/readchar/readchar.py b/readchar/readchar.py index f96ae29..bcc1081 100644 --- a/readchar/readchar.py +++ b/readchar/readchar.py @@ -17,15 +17,7 @@ raise NotImplemented('The platform %s is not supported yet' % sys.platform) -def readkey(getchar_fn=None): +def readkey(getchar_fn=None, blocking=True): getchar = getchar_fn or readchar - buffer = getchar(True) - - while True: - if buffer not in key.ESCAPE_SEQUENCES: - return buffer - c = getchar(False) - if c is None: - return buffer - buffer += c + buffer = getchar(blocking) return buffer diff --git a/readchar/readchar_linux.py b/readchar/readchar_linux.py index eb2c327..cbed7bd 100644 --- a/readchar/readchar_linux.py +++ b/readchar/readchar_linux.py @@ -7,14 +7,27 @@ import select import tty import termios +from . import key def readchar(wait_for_char=True): old_settings = termios.tcgetattr(sys.stdin) tty.setcbreak(sys.stdin.fileno()) + buffer = '' try: if wait_for_char or select.select([sys.stdin, ], [], [], 0.0)[0]: char = os.read(sys.stdin.fileno(), 1) - return char if type(char) is str else char.decode() + buffer = char if type(char) is str else char.decode() + except Exception: + buffer = '' finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) + + while True: + if buffer not in key.ESCAPE_SEQUENCES: + return buffer + c = readchar(False) + if c is None: + return buffer + buffer += c + return buffer diff --git a/readchar/readchar_windows.py b/readchar/readchar_windows.py index b3170e1..c80f58e 100644 --- a/readchar/readchar_windows.py +++ b/readchar/readchar_windows.py @@ -3,15 +3,29 @@ # http://code.activestate.com/recipes/134892/#c9 # Thanks to Stephen Chappell import msvcrt +from .key_windows import windows_keys, windows_special_keys -def readchar(blocking=False): - "Get a single character on Windows." - - while msvcrt.kbhit(): - msvcrt.getch() +def get_char(): + """Get a single character on Windows.""" ch = msvcrt.getch() - while ch in '\x00\xe0': - msvcrt.getch() + if ch in b'\x00\xe0': ch = msvcrt.getch() - return ch.decode() + ch = repr(ch)[2:-1] + ch = windows_special_keys.get(ch, ch) + else: + ch = repr(ch)[2:-1] + if str(ch)[0] == '\\' or ch == ' ': + ch = windows_keys.get(ch, ch) + return ch + + +def readchar(blocking=False): + """gets a character or combo on windows and returns a string. + If blocking is True then it will catch ctrl+c and not have them end the program. + It will also wait for a key to be pressed before continuing on with the loop.""" + if blocking: + return get_char() + else: + if msvcrt.kbhit(): + return get_char() From 6f6591a7116a2a6bbc972d872caac32177a05bab Mon Sep 17 00:00:00 2001 From: guiweber Date: Fri, 10 Mar 2017 10:49:33 -0500 Subject: [PATCH 2/5] linux code readability improvements - Removed unneeded return statement - Improved redebility by adding else blocs - Renaed bufffer to char_buffer for python 2.x compatibility --- readchar/readchar_linux.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/readchar/readchar_linux.py b/readchar/readchar_linux.py index cbed7bd..9a56911 100644 --- a/readchar/readchar_linux.py +++ b/readchar/readchar_linux.py @@ -13,21 +13,22 @@ def readchar(wait_for_char=True): old_settings = termios.tcgetattr(sys.stdin) tty.setcbreak(sys.stdin.fileno()) - buffer = '' + char_buffer = '' try: if wait_for_char or select.select([sys.stdin, ], [], [], 0.0)[0]: char = os.read(sys.stdin.fileno(), 1) - buffer = char if type(char) is str else char.decode() + char_buffer = char if type(char) is str else char.decode() except Exception: - buffer = '' + char_buffer = '' finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) while True: - if buffer not in key.ESCAPE_SEQUENCES: - return buffer - c = readchar(False) - if c is None: - return buffer - buffer += c - return buffer + if char_buffer not in key.ESCAPE_SEQUENCES: + return char_buffer + else: + c = readchar(False) + if c is None: + return char_buffer + else: + char_buffer += c From d430445df1dd31b70b660a301d9cbcbba802d648 Mon Sep 17 00:00:00 2001 From: Surest Texas Date: Fri, 10 Mar 2017 20:05:06 -0500 Subject: [PATCH 3/5] New keys.py for new readchar_linux (coming separately) --- readchar/key.py | 89 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 23 deletions(-) diff --git a/readchar/key.py b/readchar/key.py index b046e75..72524d4 100644 --- a/readchar/key.py +++ b/readchar/key.py @@ -1,11 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2014, 2015 Miguel Ángel García (@magmax9). +# Based on previous work on gist getch()-like unbuffered character +# reading from stdin on both Windows and Unix (Python recipe), +# started by Danny Yoo. Licensed under the MIT license. + # common LF = '\x0d' CR = '\x0a' ENTER = '\x0d' BACKSPACE = '\x7f' -SUPR = '' -SPACE = '\x20' ESC = '\x1b' +TAB = '\x09' # CTRL CTRL_A = '\x01' @@ -16,6 +21,8 @@ CTRL_F = '\x06' CTRL_Z = '\x1a' +LEFTBRACKET = '\x5b' + # ALT ALT_A = '\x1b\x61' @@ -50,30 +57,66 @@ END = '\x1b\x5b\x46' INSERT = '\x1b\x5b\x32\x7e' -SUPR = '\x1b\x5b\x33\x7e' - +DELETE = '\x1b\x5b\x33\x7e' ESCAPE_SEQUENCES = ( ESC, - ESC + '\x5b', - ESC + '\x5b' + '\x31', - ESC + '\x5b' + '\x32', - ESC + '\x5b' + '\x33', - ESC + '\x5b' + '\x35', - ESC + '\x5b' + '\x36', - ESC + '\x5b' + '\x31' + '\x35', - ESC + '\x5b' + '\x31' + '\x36', - ESC + '\x5b' + '\x31' + '\x37', - ESC + '\x5b' + '\x31' + '\x38', - ESC + '\x5b' + '\x31' + '\x39', - ESC + '\x5b' + '\x32' + '\x30', - ESC + '\x5b' + '\x32' + '\x31', - ESC + '\x5b' + '\x32' + '\x32', - ESC + '\x5b' + '\x32' + '\x33', - ESC + '\x5b' + '\x32' + '\x34', + ESC + LEFTBRACKET, + ESC + LEFTBRACKET + '\x31', + ESC + LEFTBRACKET + '\x32', + ESC + LEFTBRACKET + '\x33', + ESC + LEFTBRACKET + '\x35', + ESC + LEFTBRACKET + '\x36', + ESC + LEFTBRACKET + '\x31' + '\x35', + ESC + LEFTBRACKET + '\x31' + '\x36', + ESC + LEFTBRACKET + '\x31' + '\x37', + ESC + LEFTBRACKET + '\x31' + '\x38', + ESC + LEFTBRACKET + '\x31' + '\x39', + ESC + LEFTBRACKET + '\x32' + '\x30', + ESC + LEFTBRACKET + '\x32' + '\x31', + ESC + LEFTBRACKET + '\x32' + '\x32', + ESC + LEFTBRACKET + '\x32' + '\x33', + ESC + LEFTBRACKET + '\x32' + '\x34', ESC + '\x4f', ESC + ESC, - ESC + ESC + '\x5b', - ESC + ESC + '\x5b' + '\x32', - ESC + ESC + '\x5b' + '\x33', + ESC + ESC + LEFTBRACKET, + ESC + ESC + LEFTBRACKET + '\x32', + ESC + ESC + LEFTBRACKET + '\x33', ) + +linux_keys = { + # Single keys: + CR: 'cr', + ENTER: 'enter', + BACKSPACE: 'backspace', + ESC: 'escape', + HOME: "home", + UP: 'up', + TAB: 'tab', + RIGHT: 'right', + DOWN: 'down', + LEFT: 'left', + PAGE_UP: 'page_up', + PAGE_DOWN: 'page_down', + END: "end", + F1: 'f1', + F2: 'f2', + F3: 'f3', + F4: 'f4', + F5: 'f5', + F6: 'f6', + F7: 'f7', + F8: 'f8', + F9: 'f9', + F10: 'f10', + DELETE: 'delete', + CTRL_A: 'ctrl_a', + CTRL_B: 'ctrl_b', + CTRL_C: 'ctrl_c', + CTRL_D: 'ctrl_d', + CTRL_E: 'ctrl_e', + CTRL_F: 'ctrl_f', + CTRL_Z: 'ctrl_z', + INSERT: 'insert', + CTRL_ALT_A: 'ctrl_alt_a' +} From f7bf855817880325494a4022707c433616d35756 Mon Sep 17 00:00:00 2001 From: Surest Texas Date: Fri, 10 Mar 2017 20:07:03 -0500 Subject: [PATCH 4/5] Update readchar_linux.py gives characters a name versus codes --- readchar/readchar_linux.py | 50 ++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/readchar/readchar_linux.py b/readchar/readchar_linux.py index 9a56911..8ef79f2 100644 --- a/readchar/readchar_linux.py +++ b/readchar/readchar_linux.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- -# Initially taken from: -# http://code.activestate.com/recipes/134892/ -# Thanks to Danny Yoo +# Copyright (c) 2014, 2015 Miguel Ángel García (@magmax9). +# Based on previous work on gist getch()-like unbuffered character +# reading from stdin on both Windows and Unix (Python recipe), +# started by Danny Yoo. Licensed under the MIT license. + import sys import os import select @@ -13,22 +15,40 @@ def readchar(wait_for_char=True): old_settings = termios.tcgetattr(sys.stdin) tty.setcbreak(sys.stdin.fileno()) - char_buffer = '' + charbuffer = '' + + while True: + if charbuffer in key.ESCAPE_SEQUENCES: + char1 = getkey(False, old_settings) + else: + char1 = getkey(wait_for_char, old_settings) + if (charbuffer + char1) not in key.ESCAPE_SEQUENCES: + # escape sequence complete or not an escape character.. + return convertchar(charbuffer + char1) + + # handle cases where the escape is finished, but looks incomplete - + # such as a plain old 'ESC' (\x1b) that is not followed by other + # codes: + if (charbuffer + char1) == charbuffer: + return convertchar(charbuffer) + + charbuffer += char1 + + +def getkey(wait_for_char, old_settings): + charbuffer = '' try: if wait_for_char or select.select([sys.stdin, ], [], [], 0.0)[0]: char = os.read(sys.stdin.fileno(), 1) - char_buffer = char if type(char) is str else char.decode() + charbuffer = char if type(char) is str else char.decode() except Exception: - char_buffer = '' + pass finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) + return charbuffer - while True: - if char_buffer not in key.ESCAPE_SEQUENCES: - return char_buffer - else: - c = readchar(False) - if c is None: - return char_buffer - else: - char_buffer += c + +def convertchar(charbuffer): + if charbuffer in key.linux_keys: + return key.linux_keys[charbuffer] + return charbuffer From 2466c105ae7a36831c1b2d85aebee44312b16bab Mon Sep 17 00:00:00 2001 From: guiweber Date: Sun, 7 May 2017 11:25:44 -0400 Subject: [PATCH 5/5] Added reference implementation script --- example.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 example.py diff --git a/example.py b/example.py new file mode 100644 index 0000000..4ee4977 --- /dev/null +++ b/example.py @@ -0,0 +1,9 @@ +import readchar + +if __name__ == '__main__': + print('start... press q to quit') + while 1: + k = readchar.readkey() + print('you pressed ' + str(k)) + if k == 'q': + break