summaryrefslogtreecommitdiff
path: root/src/keyboard.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/keyboard.py')
-rw-r--r--src/keyboard.py105
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))