diff options
Diffstat (limited to 'src/keyboard.py')
-rw-r--r-- | src/keyboard.py | 105 |
1 files changed, 87 insertions, 18 deletions
diff --git a/src/keyboard.py b/src/keyboard.py index c8f4b60c..71bdd0d1 100644 --- a/src/keyboard.py +++ b/src/keyboard.py @@ -12,6 +12,11 @@ of the time ONE char, but may be longer if it's a keyboard shortcut, like ^A, M-a or KEY_RESIZE) """ +import curses +import curses.ascii +import logging +log = logging.getLogger(__name__) + def get_next_byte(s): """ Read the next byte of the utf-8 char @@ -27,12 +32,15 @@ def get_next_byte(s): return (None, c) return (ord(c), c.encode('latin-1')) # returns a number and a bytes object -def read_char(s, timeout=1000): +def get_char_list_old(s): """ - Read one utf-8 char + Kept for compatibility for python versions without get_wchar() + (introduced in 3.3) Read one or more bytes, concatenate them to create a + unicode char. Also treat special bytes to create special chars (like + control, alt, etc), returns one or more utf-8 chars + see http://en.wikipedia.org/wiki/UTF-8#Description """ - s.timeout(timeout) # The timeout for timed events to be checked every second ret_list = [] # The list of all chars. For example if you paste a text, the list the chars pasted # so that they can be handled at once. @@ -41,7 +49,7 @@ def read_char(s, timeout=1000): if not isinstance(first, int): # Keyboard special, like KEY_HOME etc return [char] if first == 127 or first == 8: - return ["KEY_BACKSPACE"] + ret_list.append("KEY_BACKSPACE") s.timeout(0) # we are now getting the missing utf-8 bytes to get a whole char if first < 127: # ASCII char on one byte if first <= 26: # transform Ctrl+* keys @@ -50,7 +58,7 @@ def read_char(s, timeout=1000): (first, char) = get_next_byte(s) continue if first == 27: - second = read_char(s, 0) + second = get_char_list_old(s) if second is None: # if escape was pressed, a second char # has to be read. But it timed out. return None @@ -73,22 +81,83 @@ def read_char(s, timeout=1000): return None # s.timeout(1) # timeout to detect a paste of many chars (first, char) = get_next_byte(s) - if not ret_list: - # nothing at all was read, that’s a timed event timeout - return None - if len(ret_list) != 1: - if ret_list[-1] == '^M': - ret_list.pop(-1) - return [char if char != '^M' else '^J' for char in ret_list] return ret_list +def get_char_list_new(s): + ret_list = [] + while True: + try: + key = s.get_wch() + except curses.error: + # No input, this means a timeout occurs. + return ret_list + s.timeout(0) + if isinstance(key, int): + ret_list.append(curses.keyname(key).decode()) + else: + if curses.ascii.isctrl(key): + key = curses.unctrl(key).decode() + # Here special cases for alt keys, where we get a ^[ and then a second char + if key == '^[': + try: + part = s.get_wch() + except curses.error: + pass + else: + key = 'M-%s' % part + # and an even more special case for keys like + # ctrl+arrows, where we get ^[, then [, then a third + # char. + if key == 'M-[': + try: + part = s.get_wch() + except curses.error: + pass + else: + key = '%s-%s' % (key, part) + ret_list.append('^M' if key == '\r' else key) + +class Keyboard(object): + def __init__(self): + self.get_char_list = get_char_list_new + + def get_user_input(self, s, timeout=1000): + """ + Returns a list of all the available characters to read (for example it + may contain a whole text if there’s some lag, or the user pasted text, + or the user types really really fast). Also it can return None, meaning + that it’s time to do some other checks (because this function is + blocking, we need to get out of it every now and then even if nothing + was entered). + """ + s.timeout(timeout) # The timeout for timed events to be checked every second + try: + ret_list = self.get_char_list(s) + except AttributeError: + # caught if screen.get_wch() does not exist. In that case we use the + # old version, so this exception is caught only once. No efficiency + # issue here. + log.debug("get_wch() missing, switching to old keyboard method") + self.get_char_list = get_char_list_old + ret_list = self.get_char_list(s) + if not ret_list: + # nothing at all was read, that’s a timed event timeout + return None + if len(ret_list) != 1: + if ret_list[-1] == '^M': + ret_list.pop(-1) + return [char if char != '^M' else '^J' for char in ret_list] + return ret_list + +keyboard = Keyboard() + if __name__ == '__main__': - import curses s = curses.initscr() - curses.curs_set(1) - curses.noecho() - curses.nonl() - s.keypad(True) curses.noecho() + curses.cbreak() + s.keypad(1) + while True: - s.addstr('%s\n' % read_char(s)) + chars = keyboard.get_user_input(s) + for char in chars if chars else '': + s.addstr('%s ' % (char)) |