Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pyte/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,11 @@
:license: LGPL, see LICENSE for more details.
"""

__all__ = ("Screen", "DiffScreen", "HistoryScreen", "DebugScreen",
__all__ = ("KeypadMode",
"Screen", "DiffScreen", "HistoryScreen", "DebugScreen",
"Stream", "ByteStream")

from .keyboard import KeypadMode
from .screens import Screen, DiffScreen, HistoryScreen, DebugScreen
from .streams import Stream, ByteStream

Expand Down
11 changes: 11 additions & 0 deletions pyte/escape.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
:license: LGPL, see LICENSE for more details.
"""

# non-CSI escape sequences.
# -------------------------

#: *Reset*.
RIS = "c"

Expand Down Expand Up @@ -38,6 +41,14 @@
#: selection. If none were saved, move cursor to home position.
DECRC = "8"

#: *Set keypad application mode*: Keypad sends control sequences (NUMLOCK off)
#: see: https://vt100.net/docs/vt510-rm/DECKPAM.html
DECKPAM = "="

#: *Set keypad numeric mode*: Keypad sends numbers (NUMLOCK ON).
#: see: https://vt100.net/docs/vt510-rm/DECKPNM.html
DECKPNM = ">"

# "Sharp" escape sequences.
# -------------------------

Expand Down
10 changes: 10 additions & 0 deletions pyte/keyboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations
from enum import IntEnum


class KeypadMode(IntEnum):
"""Supported keypad modes"""
NUMERIC = 0
"""Keypad sends numbers (default)."""
APPLICATION = 1
"""Keypad sends control sequences."""
23 changes: 23 additions & 0 deletions pyte/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@
from . import (
charsets as cs,
control as ctrl,
escape as esc,
graphics as g,
modes as mo
)
from .keyboard import KeypadMode
from .streams import Stream

if TYPE_CHECKING:
Expand All @@ -56,11 +58,13 @@
KT = TypeVar("KT")
VT = TypeVar("VT")


class Margins(NamedTuple):
"""A container for screen's scroll margins."""
top: int
bottom: int


class Savepoint(NamedTuple):
"""A container for savepoint, created on :data:`~pyte.escape.DECSC`."""
cursor: Cursor
Expand Down Expand Up @@ -277,6 +281,8 @@ def reset(self) -> None:
self.g0_charset = cs.LAT1_MAP
self.g1_charset = cs.VT100_MAP

self.keypad_mode: KeypadMode = KeypadMode.NUMERIC

# From ``man terminfo`` -- "... hardware tabs are initially
# set every `n` spaces when the terminal is powered up. Since
# we aim to support VT102 / VT220 and linux -- we use n = 8.
Expand Down Expand Up @@ -443,6 +449,23 @@ def reset_mode(self, *modes: int, **kwargs: Any) -> None:
if mo.DECTCEM in mode_list:
self.cursor.hidden = True

def set_keypad_mode(self, mode: str) -> None:
"""Set keypad mode

DECKPAM enables the keypad to send application sequences.
DECKPNM enables the keypad to send numeric characters to the host.

Default: Send numeric keypad characters.

:param: mode
``esc.DECKPAM`` - application mode
``esc.DECKPNM`` - numeric mode
"""
if mode == esc.DECKPAM:
self.keypad_mode = KeypadMode.APPLICATION
else:
self.keypad_mode = KeypadMode.NUMERIC

def define_charset(self, code: str, mode: str) -> None:
"""Define ``G0`` or ``G1`` charset.

Expand Down
7 changes: 7 additions & 0 deletions pyte/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class Stream:
events = frozenset(itertools.chain(
basic.values(), escape.values(), sharp.values(), csi.values(),
["define_charset"],
["set_keypad_mode"],
["set_icon_name", "set_title"], # OSC.
["draw", "debug"]))

Expand Down Expand Up @@ -239,6 +240,7 @@ def _parser_fsm(self) -> ParserGenerator:
debug = listener.debug

ESC, CSI_C1 = ctrl.ESC, ctrl.CSI_C1
KEYPAD_MODE = esc.DECKPAM + esc.DECKPNM
OSC_C1 = ctrl.OSC_C1
SP_OR_GT = ctrl.SP + ">"
NUL_OR_DEL = ctrl.NUL + ctrl.DEL
Expand Down Expand Up @@ -293,6 +295,11 @@ def create_dispatcher(mapping: Mapping[str, str]) -> dict[str, Callable[..., Non
# See http://www.cl.cam.ac.uk/~mgk25/unicode.html#term
# for the why on the UTF-8 restriction.
listener.define_charset(code, mode=char)
elif char in KEYPAD_MODE:
# See https://vt100.net/docs/vt510-rm/DECKPAM.html
# DECKPAM enables the keypad to send application sequences.
# DECKPNM enables the keypad to send numeric characters to the host.
listener.set_keypad_mode(mode=char)
else:
escape_dispatch[char]()
continue # Don't go to CSI.
Expand Down
24 changes: 23 additions & 1 deletion tests/test_screen.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pytest

import pyte
from pyte import modes as mo, control as ctrl, graphics as g
from pyte import escape as esc, modes as mo, control as ctrl, graphics as g
from pyte.screens import Char


Expand Down Expand Up @@ -301,6 +301,28 @@ def test_set_mode():
assert screen.cursor.hidden


def test_set_keypad_mode():
screen = pyte.Screen(2, 1)
# test numeric mode being the default
assert screen.keypad_mode == pyte.KeypadMode.NUMERIC
# test setting application mode
screen.set_keypad_mode(esc.DECKPAM)
assert screen.keypad_mode == pyte.KeypadMode.APPLICATION
# test setting numeric mode
screen.set_keypad_mode(esc.DECKPNM)
assert screen.keypad_mode == pyte.KeypadMode.NUMERIC


def test_numeric_keypad_mode_on_reset():
screen = pyte.Screen(2, 1)
# test setting application mode
screen.set_keypad_mode(esc.DECKPAM)
assert screen.keypad_mode == pyte.KeypadMode.APPLICATION
# test numeric mode after reset
screen.reset()
assert screen.keypad_mode == pyte.KeypadMode.NUMERIC


def test_draw():
# ``DECAWM`` on (default).
screen = pyte.Screen(3, 3)
Expand Down
26 changes: 26 additions & 0 deletions tests/test_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,3 +332,29 @@ def test_byte_stream_select_other_charset():
# c) enable utf-8
stream.select_other_charset("G")
assert stream.use_utf8


@pytest.mark.parametrize("mode", [esc.DECKPAM, esc.DECKPNM])
def test_set_keypad_mode_args(mode):
"""Verify escape sequences being passed to event handler."""
handler = argcheck()
screen = pyte.Screen(80, 1)
setattr(screen, 'set_keypad_mode', handler)

stream = pyte.Stream(screen)
stream.feed(ctrl.ESC + mode)
assert handler.count == 1
assert handler.kwargs == {"mode": mode}


def test_set_keypad_mode():
"""Verify escape sequences correctly applying keypad mode."""
screen = pyte.Screen(80, 1)
stream = pyte.Stream(screen)
assert screen.keypad_mode == pyte.KeypadMode.NUMERIC
stream.feed(ctrl.ESC + esc.DECKPAM)
assert screen.keypad_mode == pyte.KeypadMode.APPLICATION
stream.feed(ctrl.ESC + esc.DECKPNM)
assert screen.keypad_mode == pyte.KeypadMode.NUMERIC
# verify nothing added to buffer
assert screen.display[0] == " " * 80