From 0ef9d3594beb8c0bd95dcf8dbb55fa2dda1f2777 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Fri, 14 Dec 2012 04:14:55 +0100 Subject: Use get_wch() if available, otherwise use the old (maybe buggy) method. This makes it possible to read the ctrl+arrows keys with python3.3, assign ctrl+left/right to next/previous tab, in the default config. --- src/core.py | 14 ++++---- src/keyboard.py | 105 ++++++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 95 insertions(+), 24 deletions(-) diff --git a/src/core.py b/src/core.py index 4e17d747..5dc45063 100644 --- a/src/core.py +++ b/src/core.py @@ -52,7 +52,7 @@ from logger import logger from roster import roster from contact import Contact, Resource from text_buffer import TextBuffer -from keyboard import read_char +from keyboard import keyboard from theming import get_theme from fifo import Fifo from windows import g_lock @@ -205,9 +205,11 @@ class Core(object): "^S": self.scroll_half_up, "KEY_F(5)": self.rotate_rooms_left, "^P": self.rotate_rooms_left, + "M-[-D": self.rotate_rooms_left, 'kLFT3': self.rotate_rooms_left, "KEY_F(6)": self.rotate_rooms_right, "^N": self.rotate_rooms_right, + "M-[-C": self.rotate_rooms_right, 'kRIT3': self.rotate_rooms_right, "KEY_F(4)": self.toggle_left_pane, "KEY_F(7)": self.shrink_information_win, @@ -1077,8 +1079,8 @@ class Core(object): curses.noecho() curses.nonl() curses.raw() - stdscr.idlok(True) - stdscr.keypad(True) + stdscr.idlok(1) + stdscr.keypad(1) curses.start_color() curses.use_default_colors() theming.reload_theme() @@ -1298,14 +1300,14 @@ class Core(object): def read_keyboard(self): """ Get the next keyboard key pressed and returns it. - read_char() has a timeout: it returns None when the timeout + get_user_input() has a timeout: it returns None when the timeout occurs. In that case we do not return (we loop until we get a non-None value), but we check for timed events instead. """ - res = read_char(self.stdscr) + res = keyboard.get_user_input(self.stdscr) while res is None: self.check_timed_events() - res = read_char(self.stdscr) + res = keyboard.get_user_input(self.stdscr) return res ####################### Commands and completions ############################## 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)) -- cgit v1.2.3