"This sub-module provides 'keyboard awareness'."
__author__ = 'Jeff Quast <contact@jeffquast.com>'
__license__ = 'MIT'
__all__ = ['Keystroke', 'get_keyboard_codes', 'get_keyboard_sequences']
import curses.has_key
import collections
import curses
import sys
if hasattr(collections, 'OrderedDict'):
OrderedDict = collections.OrderedDict
else:
# python 2.6 requires 3rd party library
import ordereddict
OrderedDict = ordereddict.OrderedDict
get_curses_keycodes = lambda: dict(
((keyname, getattr(curses, keyname))
for keyname in dir(curses)
if keyname.startswith('KEY_'))
)
# override a few curses constants with easier mnemonics,
# there may only be a 1:1 mapping, so for those who desire
# to use 'KEY_DC' from, perhaps, ported code, recommend
# that they simply compare with curses.KEY_DC.
CURSES_KEYCODE_OVERRIDE_MIXIN = (
('KEY_DELETE', curses.KEY_DC),
('KEY_INSERT', curses.KEY_IC),
('KEY_PGUP', curses.KEY_PPAGE),
('KEY_PGDOWN', curses.KEY_NPAGE),
('KEY_ESCAPE', curses.KEY_EXIT),
('KEY_SUP', curses.KEY_SR),
('KEY_SDOWN', curses.KEY_SF),
('KEY_UP_LEFT', curses.KEY_A1),
('KEY_UP_RIGHT', curses.KEY_A3),
('KEY_CENTER', curses.KEY_B2),
('KEY_BEGIN', curses.KEY_BEG),
)
# Inject KEY_{names} that we think would be useful, there are no curses
# definitions for the keypad keys. We need keys that generate multibyte
# sequences, though it is useful to have some aliases for basic control
# characters such as TAB.
_lastval = max(get_curses_keycodes().values())
for key in ('TAB', 'KP_MULTIPLY', 'KP_ADD', 'KP_SEPARATOR', 'KP_SUBTRACT',
'KP_DECIMAL', 'KP_DIVIDE', 'KP_EQUAL', 'KP_0', 'KP_1', 'KP_2',
'KP_3', 'KP_4', 'KP_5', 'KP_6', 'KP_7', 'KP_8', 'KP_9'):
_lastval += 1
setattr(curses, 'KEY_{0}'.format(key), _lastval)
if sys.version_info[0] == 3:
text_type = str
unichr = chr
else:
text_type = unicode # noqa
[docs]class Keystroke(text_type):
"""A unicode-derived class for describing keyboard input returned by
the ``inkey()`` method of ``Terminal``, which may, at times, be a
multibyte sequence, providing properties ``is_sequence`` as ``True``
when the string is a known sequence, and ``code``, which returns an
integer value that may be compared against the terminal class attributes
such as ``KEY_LEFT``.
"""
def __new__(cls, ucs='', code=None, name=None):
new = text_type.__new__(cls, ucs)
new._name = name
new._code = code
return new
@property
def is_sequence(self):
"Whether the value represents a multibyte sequence (bool)."
return self._code is not None
def __repr__(self):
return self._name is None and text_type.__repr__(self) or self._name
__repr__.__doc__ = text_type.__doc__
@property
def name(self):
"String-name of key sequence, such as ``'KEY_LEFT'`` (str)."
return self._name
@property
def code(self):
"Integer keycode value of multibyte sequence (int)."
return self._code
[docs]def get_keyboard_codes():
"""get_keyboard_codes() -> dict
Returns dictionary of (code, name) pairs for curses keyboard constant
values and their mnemonic name. Such as key ``260``, with the value of
its identity, ``KEY_LEFT``. These are derived from the attributes by the
same of the curses module, with the following exceptions:
* ``KEY_DELETE`` in place of ``KEY_DC``
* ``KEY_INSERT`` in place of ``KEY_IC``
* ``KEY_PGUP`` in place of ``KEY_PPAGE``
* ``KEY_PGDOWN`` in place of ``KEY_NPAGE``
* ``KEY_ESCAPE`` in place of ``KEY_EXIT``
* ``KEY_SUP`` in place of ``KEY_SR``
* ``KEY_SDOWN`` in place of ``KEY_SF``
"""
keycodes = OrderedDict(get_curses_keycodes())
keycodes.update(CURSES_KEYCODE_OVERRIDE_MIXIN)
# invert dictionary (key, values) => (values, key), preferring the
# last-most inserted value ('KEY_DELETE' over 'KEY_DC').
return dict(zip(keycodes.values(), keycodes.keys()))
def _alternative_left_right(term):
"""_alternative_left_right(T) -> dict
Return dict of sequences ``term._cuf1``, and ``term._cub1``,
valued as ``KEY_RIGHT``, ``KEY_LEFT`` when appropriate if available.
some terminals report a different value for *kcuf1* than *cuf1*, but
actually send the value of *cuf1* for right arrow key (which is
non-destructive space).
"""
keymap = dict()
if term._cuf1 and term._cuf1 != u' ':
keymap[term._cuf1] = curses.KEY_RIGHT
if term._cub1 and term._cub1 != u'\b':
keymap[term._cub1] = curses.KEY_LEFT
return keymap
[docs]def get_keyboard_sequences(term):
"""get_keyboard_sequences(T) -> (OrderedDict)
Initialize and return a keyboard map and sequence lookup table,
(sequence, constant) from blessed Terminal instance ``term``,
where ``sequence`` is a multibyte input sequence, such as u'\x1b[D',
and ``constant`` is a constant, such as term.KEY_LEFT. The return
value is an OrderedDict instance, with their keys sorted longest-first.
"""
# A small gem from curses.has_key that makes this all possible,
# _capability_names: a lookup table of terminal capability names for
# keyboard sequences (fe. kcub1, key_left), keyed by the values of
# constants found beginning with KEY_ in the main curses module
# (such as KEY_LEFT).
#
# latin1 encoding is used so that bytes in 8-bit range of 127-255
# have equivalent chr() and unichr() values, so that the sequence
# of a kermit or avatar terminal, for example, remains unchanged
# in its byte sequence values even when represented by unicode.
#
capability_names = curses.has_key._capability_names
sequence_map = dict((
(seq.decode('latin1'), val)
for (seq, val) in (
(curses.tigetstr(cap), val)
for (val, cap) in capability_names.items()
) if seq
) if term.does_styling else ())
sequence_map.update(_alternative_left_right(term))
sequence_map.update(DEFAULT_SEQUENCE_MIXIN)
# This is for fast lookup matching of sequences, preferring
# full-length sequence such as ('\x1b[D', KEY_LEFT)
# over simple sequences such as ('\x1b', KEY_EXIT).
return OrderedDict((
(seq, sequence_map[seq]) for seq in sorted(
sequence_map.keys(), key=len, reverse=True)))
def resolve_sequence(text, mapper, codes):
"""resolve_sequence(text, mapper, codes) -> Keystroke()
Returns first matching Keystroke() instance for sequences found in
``mapper`` beginning with input ``text``, where ``mapper`` is an
OrderedDict of unicode multibyte sequences, such as u'\x1b[D' paired by
their integer value (260), and ``codes`` is a dict of integer values (260)
paired by their mnemonic name, 'KEY_LEFT'.
"""
for sequence, code in mapper.items():
if text.startswith(sequence):
return Keystroke(ucs=sequence, code=code, name=codes[code])
return Keystroke(ucs=text and text[0] or u'')
"""In a perfect world, terminal emulators would always send exactly what
the terminfo(5) capability database plans for them, accordingly by the
value of the ``TERM`` name they declare.
But this isn't a perfect world. Many vt220-derived terminals, such as
those declaring 'xterm', will continue to send vt220 codes instead of
their native-declared codes, for backwards-compatibility.
This goes for many: rxvt, putty, iTerm.
These "mixins" are used for *all* terminals, regardless of their type.
Furthermore, curses does not provide sequences sent by the keypad,
at least, it does not provide a way to distinguish between keypad 0
and numeric 0.
"""
DEFAULT_SEQUENCE_MIXIN = (
# these common control characters (and 127, ctrl+'?') mapped to
# an application key definition.
(unichr(10), curses.KEY_ENTER),
(unichr(13), curses.KEY_ENTER),
(unichr(8), curses.KEY_BACKSPACE),
(unichr(9), curses.KEY_TAB),
(unichr(27), curses.KEY_EXIT),
(unichr(127), curses.KEY_DC),
(u"\x1b[A", curses.KEY_UP),
(u"\x1b[B", curses.KEY_DOWN),
(u"\x1b[C", curses.KEY_RIGHT),
(u"\x1b[D", curses.KEY_LEFT),
(u"\x1b[F", curses.KEY_END),
(u"\x1b[H", curses.KEY_HOME),
# not sure where these are from .. please report
(u"\x1b[K", curses.KEY_END),
(u"\x1b[U", curses.KEY_NPAGE),
(u"\x1b[V", curses.KEY_PPAGE),
# keys sent after term.smkx (keypad_xmit) is emitted, source:
# http://www.xfree86.org/current/ctlseqs.html#PC-Style%20Function%20Keys
# http://fossies.org/linux/rxvt/doc/rxvtRef.html#KeyCodes
#
# keypad, numlock on
(u"\x1bOM", curses.KEY_ENTER), # return
(u"\x1bOj", curses.KEY_KP_MULTIPLY), # *
(u"\x1bOk", curses.KEY_KP_ADD), # +
(u"\x1bOl", curses.KEY_KP_SEPARATOR), # ,
(u"\x1bOm", curses.KEY_KP_SUBTRACT), # -
(u"\x1bOn", curses.KEY_KP_DECIMAL), # .
(u"\x1bOo", curses.KEY_KP_DIVIDE), # /
(u"\x1bOX", curses.KEY_KP_EQUAL), # =
(u"\x1bOp", curses.KEY_KP_0), # 0
(u"\x1bOq", curses.KEY_KP_1), # 1
(u"\x1bOr", curses.KEY_KP_2), # 2
(u"\x1bOs", curses.KEY_KP_3), # 3
(u"\x1bOt", curses.KEY_KP_4), # 4
(u"\x1bOu", curses.KEY_KP_5), # 5
(u"\x1bOv", curses.KEY_KP_6), # 6
(u"\x1bOw", curses.KEY_KP_7), # 7
(u"\x1bOx", curses.KEY_KP_8), # 8
(u"\x1bOy", curses.KEY_KP_9), # 9
#
# keypad, numlock off
(u"\x1b[1~", curses.KEY_FIND), # find
(u"\x1b[2~", curses.KEY_IC), # insert (0)
(u"\x1b[3~", curses.KEY_DC), # delete (.), "Execute"
(u"\x1b[4~", curses.KEY_SELECT), # select
(u"\x1b[5~", curses.KEY_PPAGE), # pgup (9)
(u"\x1b[6~", curses.KEY_NPAGE), # pgdown (3)
(u"\x1b[7~", curses.KEY_HOME), # home
(u"\x1b[8~", curses.KEY_END), # end
(u"\x1b[OA", curses.KEY_UP), # up (8)
(u"\x1b[OB", curses.KEY_DOWN), # down (2)
(u"\x1b[OC", curses.KEY_RIGHT), # right (6)
(u"\x1b[OD", curses.KEY_LEFT), # left (4)
(u"\x1b[OF", curses.KEY_END), # end (1)
(u"\x1b[OH", curses.KEY_HOME), # home (7)
# The vt220 placed F1-F4 above the keypad, in place of actual
# F1-F4 were local functions (hold screen, print screen,
# set up, data/talk, break).
(u"\x1bOP", curses.KEY_F1),
(u"\x1bOQ", curses.KEY_F2),
(u"\x1bOR", curses.KEY_F3),
(u"\x1bOS", curses.KEY_F4),
)