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:
parent
1835d36495
commit
0ef9d3594b
2 changed files with 98 additions and 27 deletions
14
src/core.py
14
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 ##############################
|
||||
|
|
111
src/keyboard.py
111
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
|
||||
|
||||
if __name__ == '__main__':
|
||||
import curses
|
||||
s = curses.initscr()
|
||||
curses.curs_set(1)
|
||||
curses.noecho()
|
||||
curses.nonl()
|
||||
s.keypad(True)
|
||||
curses.noecho()
|
||||
def get_char_list_new(s):
|
||||
ret_list = []
|
||||
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 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__':
|
||||
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))
|
||||
|
|
Loading…
Reference in a new issue