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.
This commit is contained in:
Florent Le Coz 2012-12-14 04:14:55 +01:00
parent 1835d36495
commit 0ef9d3594b
2 changed files with 98 additions and 27 deletions

View file

@ -52,7 +52,7 @@ from logger import logger
from roster import roster from roster import roster
from contact import Contact, Resource from contact import Contact, Resource
from text_buffer import TextBuffer from text_buffer import TextBuffer
from keyboard import read_char from keyboard import keyboard
from theming import get_theme from theming import get_theme
from fifo import Fifo from fifo import Fifo
from windows import g_lock from windows import g_lock
@ -205,9 +205,11 @@ class Core(object):
"^S": self.scroll_half_up, "^S": self.scroll_half_up,
"KEY_F(5)": self.rotate_rooms_left, "KEY_F(5)": self.rotate_rooms_left,
"^P": self.rotate_rooms_left, "^P": self.rotate_rooms_left,
"M-[-D": self.rotate_rooms_left,
'kLFT3': self.rotate_rooms_left, 'kLFT3': self.rotate_rooms_left,
"KEY_F(6)": self.rotate_rooms_right, "KEY_F(6)": self.rotate_rooms_right,
"^N": self.rotate_rooms_right, "^N": self.rotate_rooms_right,
"M-[-C": self.rotate_rooms_right,
'kRIT3': self.rotate_rooms_right, 'kRIT3': self.rotate_rooms_right,
"KEY_F(4)": self.toggle_left_pane, "KEY_F(4)": self.toggle_left_pane,
"KEY_F(7)": self.shrink_information_win, "KEY_F(7)": self.shrink_information_win,
@ -1077,8 +1079,8 @@ class Core(object):
curses.noecho() curses.noecho()
curses.nonl() curses.nonl()
curses.raw() curses.raw()
stdscr.idlok(True) stdscr.idlok(1)
stdscr.keypad(True) stdscr.keypad(1)
curses.start_color() curses.start_color()
curses.use_default_colors() curses.use_default_colors()
theming.reload_theme() theming.reload_theme()
@ -1298,14 +1300,14 @@ class Core(object):
def read_keyboard(self): def read_keyboard(self):
""" """
Get the next keyboard key pressed and returns it. 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 occurs. In that case we do not return (we loop until we get
a non-None value), but we check for timed events instead. 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: while res is None:
self.check_timed_events() self.check_timed_events()
res = read_char(self.stdscr) res = keyboard.get_user_input(self.stdscr)
return res return res
####################### Commands and completions ############################## ####################### Commands and completions ##############################

View file

@ -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) shortcut, like ^A, M-a or KEY_RESIZE)
""" """
import curses
import curses.ascii
import logging
log = logging.getLogger(__name__)
def get_next_byte(s): def get_next_byte(s):
""" """
Read the next byte of the utf-8 char Read the next byte of the utf-8 char
@ -27,12 +32,15 @@ def get_next_byte(s):
return (None, c) return (None, c)
return (ord(c), c.encode('latin-1')) # returns a number and a bytes object 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 see http://en.wikipedia.org/wiki/UTF-8#Description
""" """
s.timeout(timeout) # The timeout for timed events to be checked every second
ret_list = [] ret_list = []
# The list of all chars. For example if you paste a text, the list the chars pasted # 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. # 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 if not isinstance(first, int): # Keyboard special, like KEY_HOME etc
return [char] return [char]
if first == 127 or first == 8: 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 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 < 127: # ASCII char on one byte
if first <= 26: # transform Ctrl+* keys if first <= 26: # transform Ctrl+* keys
@ -50,7 +58,7 @@ def read_char(s, timeout=1000):
(first, char) = get_next_byte(s) (first, char) = get_next_byte(s)
continue continue
if first == 27: 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 if second is None: # if escape was pressed, a second char
# has to be read. But it timed out. # has to be read. But it timed out.
return None return None
@ -73,22 +81,83 @@ def read_char(s, timeout=1000):
return None return None
# s.timeout(1) # timeout to detect a paste of many chars # s.timeout(1) # timeout to detect a paste of many chars
(first, char) = get_next_byte(s) (first, char) = get_next_byte(s)
if not ret_list:
# nothing at all was read, thats 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 return ret_list
if __name__ == '__main__': def get_char_list_new(s):
import curses ret_list = []
s = curses.initscr()
curses.curs_set(1)
curses.noecho()
curses.nonl()
s.keypad(True)
curses.noecho()
while True: while True:
s.addstr('%s\n' % read_char(s)) 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 theres some lag, or the user pasted text,
or the user types really really fast). Also it can return None, meaning
that its 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, thats 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__':
s = curses.initscr()
curses.noecho()
curses.cbreak()
s.keypad(1)
while True:
chars = keyboard.get_user_input(s)
for char in chars if chars else '':
s.addstr('%s ' % (char))