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 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 ##############################
|
||||||
|
|
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)
|
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, 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
|
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 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