Skip to content

Commit

Permalink
Implement ESCDELAY environment value
Browse files Browse the repository at this point in the history
Closes #158
  • Loading branch information
jquast committed Oct 30, 2023
1 parent a34c6b1 commit 3a0aeec
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 9 deletions.
15 changes: 15 additions & 0 deletions blessed/keyboard.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Sub-module providing 'keyboard awareness'."""

# std imports
import os
import re
import time
import platform
Expand Down Expand Up @@ -448,4 +449,18 @@ def _read_until(term, pattern, timeout):
('KEY_BEGIN', curses.KEY_BEG),
)

#: Default delay, in seconds, of Escape key detection in
#: :meth:`Terminal.inkey`.` curses has a default delay of 1000ms (1 second) for
#: escape sequences. This is too long for modern applications, so we set it to
#: 350ms, or 0.35 seconds. It is still a bit conservative but does well with
#: for telnet or ssh servers.
DEFAULT_ESCDELAY = 0.35
if os.environ.get('ESCDELAY'):
try:
DEFAULT_ESCDELAY = int(os.environ['ESCDELAY']) / 1000.0
except ValueError:
# invalid values of 'ESCDELAY' are ignored
pass


__all__ = ('Keystroke', 'get_keyboard_codes', 'get_keyboard_sequences',)
26 changes: 17 additions & 9 deletions blessed/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
resolve_sequence,
get_keyboard_codes,
get_leading_prefixes,
get_keyboard_sequences)
get_keyboard_sequences,
DEFAULT_ESCDELAY)
from .sequences import Termcap, Sequence, SequenceTextWrapper
from .colorspace import RGB_256TABLE
from .formatters import (COLORS,
Expand Down Expand Up @@ -1425,7 +1426,7 @@ def keypad(self):
self.stream.write(self.rmkx)
self.stream.flush()

def inkey(self, timeout=None, esc_delay=0.35):
def inkey(self, timeout=None, esc_delay=DEFAULT_ESCDELAY):
"""
Read and return the next keyboard event within given timeout.
Expand All @@ -1434,12 +1435,20 @@ def inkey(self, timeout=None, esc_delay=0.35):
:arg float timeout: Number of seconds to wait for a keystroke before
returning. When ``None`` (default), this method may block
indefinitely.
:arg float esc_delay: To distinguish between the keystroke of
``KEY_ESCAPE``, and sequences beginning with escape, the parameter
``esc_delay`` specifies the amount of time after receiving escape
(``chr(27)``) to seek for the completion of an application key
before returning a :class:`~.Keystroke` instance for
``KEY_ESCAPE``.
:arg float esc_delay: Time in seconds to block after Escape key
is received to await another key sequence beginning with
escape such as *KEY_LEFT*, sequence ``'\x1b[D'``], before returning a
:class:`~.Keystroke` instance for ``KEY_ESCAPE``.
Users may also override this for all blessed and curses applications
with environment value of ESCDELAY_ as an integer in milliseconds.
You may also override user preference as an argument to this function,
as the delay value is in seconds.
It could be set to low value such as 10, modern pipelines typically
transmit a keyboard input sequence without framing and this can often
be safely set at very low values!
:rtype: :class:`~.Keystroke`.
:returns: :class:`~.Keystroke`, which may be empty (``u''``) if
``timeout`` is specified and keystroke is not received.
Expand All @@ -1458,7 +1467,6 @@ def inkey(self, timeout=None, esc_delay=0.35):
resolve = functools.partial(resolve_sequence,
mapper=self._keymap,
codes=self._keycodes)

stime = time.time()

# re-buffer previously received keystrokes,
Expand Down
13 changes: 13 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,13 @@ def child():
child()


@pytest.fixture(scope="session", autouse=True)
@pytest.mark.skipif(IS_WINDOWS, reason="requires more than 1 tty")
def test_number_of_colors_without_tty():
"""``number_of_colors`` should return 0 when there's no tty."""
if 'COLORTERM' in os.environ:
del os.environ['COLORTERM']

@as_subprocess
def child_256_nostyle():
t = TestTerminal(stream=six.StringIO())
Expand All @@ -118,6 +122,15 @@ def child_0_forcestyle():
force_styling=True)
assert (t.number_of_colors == 0)

@as_subprocess
def child_24bit_forcestyle_with_colorterm():
os.environ['COLORTERM'] = 'truecolor'
t = TestTerminal(kind='vt220', stream=six.StringIO(),
force_styling=True)
assert (t.number_of_colors == 1 << 24)



child_0_forcestyle()
child_8_forcestyle()
child_256_forcestyle()
Expand Down
29 changes: 29 additions & 0 deletions tests/test_keyboard.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
"""Tests for keyboard support."""
# std imports
import os
import sys
import platform
import tempfile
Expand Down Expand Up @@ -363,3 +364,31 @@ def child(kind): # pylint: disable=too-many-statements
assert resolve(u"\x1bOS").name == "KEY_F4"

child('xterm')

UNDER_PY34 = sys.version_info[0:2] < (3, 4)

@pytest.fixture(scope="session", autouse=True)
@pytest.mark.skipif(UNDER_PY34, reason="importlib was renamed in py34")
def test_ESCDELAY_unset():
if 'ESCDELAY' in os.environ:
del os.environ['ESCDELAY']
import importlib, blessed.keyboard
importlib.reload(blessed.keyboard)
assert blessed.keyboard.DEFAULT_ESCDELAY == 0.35

@pytest.fixture(scope="session", autouse=True)
@pytest.mark.skipif(UNDER_PY34, reason="importlib was renamed in py34")
def test_ESCDELAY_bad_value():
os.environ['ESCDELAY'] = 'XYZ123!'
import importlib, blessed.keyboard
importlib.reload(blessed.keyboard)
assert blessed.keyboard.DEFAULT_ESCDELAY == 0.35

@pytest.fixture(scope="session", autouse=True)
@pytest.mark.skipif(UNDER_PY34, reason="importlib was renamed in py34")
def test_ESCDELAY_bad_value():
os.environ['ESCDELAY'] = '10'
import importlib, blessed.keyboard
importlib.reload(blessed.keyboard)
assert blessed.keyboard.DEFAULT_ESCDELAY == 0.01

0 comments on commit 3a0aeec

Please sign in to comment.