pysnap.terminal package

Terminal interface components for PySnap.

Submodules

pysnap.terminal.clipboard module

Read the host clipboard for the Ctrl-] v paste of pysnap connect.

The clipboard is read only when the user asks for it. Copying and the usual paste are left to the terminal of the user; this paste helps where the terminal offers none from the keyboard, such as the Windows console without its Ctrl key shortcuts.

  • Windows: the Win32 clipboard API through ctypes, no external program;

  • macOS: pbpaste;

  • Linux and other systems: wl-paste under Wayland, then xclip or xsel for the X11 clipboard.

exception pysnap.terminal.clipboard.ClipboardError[source]

Bases: Exception

Report that the host clipboard cannot be read.

pysnap.terminal.clipboard.read_host_clipboard()[source]

Return the text in the host clipboard.

Returns:

Clipboard text; empty when the clipboard holds no text.

Raises:

ClipboardError – When no way to read the clipboard works.

Return type:

str

pysnap.terminal.emulator module

Terminal emulation utilities built on top of pyte.

class pysnap.terminal.emulator.SequenceFilter[source]

Bases: object

Remove control sequences pyte cannot handle before it parses them.

pyte 0.8 fails on some sequences that current programs send, for example editors such as Vim with an xterm TERM:

  • private CSI sequences other than DEC modes, such as ESC [ ? 4 m, raise TypeError;

  • >, = and < prefixes are ignored, so ESC [ > 4 ; 2 m switches on underline;

  • intermediate bytes (ESC [ 0 % m) and colon sub-parameters (ESC [ 3 8 : 2 : : 1 : 2 : 3 m) end the sequence early and the rest is drawn as text;

  • the payload of DCS, SOS, PM and APC strings is drawn as text.

The filter keeps the sequences pyte handles, rewrites colon SGR parameters to the semicolon form and drops the rest, together with all control strings (including OSC: window titles are not used). Its state survives chunk boundaries, so a sequence split between two reads is handled like a whole one.

feed(data)[source]

Return the bytes of data that pyte may parse.

Parameters:

data (bytes) – Raw guest output.

Returns:

Filtered output; parts of an unfinished sequence are kept until the next call.

Return type:

bytes

class pysnap.terminal.emulator.ScrollRegionMixin[source]

Bases: object

Add the CSI S and T scroll commands that pyte 0.8 lacks.

Programs such as nano scroll their editing area with them when the terminal is an xterm. The scroll happens inside the scrolling margins and does not move the cursor; it reuses index and reverse_index so that screens which track lines leaving the screen see them as usual.

scroll_up(count=1)[source]

Scroll the lines inside the margins up (SU).

Parameters:

count (int) – Number of lines.

Return type:

None

scroll_down(count=1)[source]

Scroll the lines inside the margins down (SD).

Parameters:

count (int) – Number of lines.

Return type:

None

class pysnap.terminal.emulator.ScreenStream(*args, **kwargs)[source]

Bases: ByteStream

pyte byte stream that also dispatches CSI S and T.

Parameters:
  • args (Any)

  • kwargs (Any)

Return type:

Any

csi = {'S': 'scroll_up', 'T': 'scroll_down'}
class pysnap.terminal.emulator.AlternateScreenMixin[source]

Bases: object

Give a pyte history screen the xterm alternate screen.

Full-screen programs such as vim, nano and less switch to the alternate screen and back when they exit, so the shell screen reappears as it was. Lines scrolled on the alternate screen do not enter the history, as in xterm. pyte 0.8 has no alternate screen: it ignores these modes.

property in_alternate_screen: bool[source]

Return whether the alternate screen is shown.

set_mode(*modes, **kwargs)[source]

Handle the alternate screen modes; pyte records all modes as usual.

Parameters:
  • modes (int)

  • kwargs (object)

Return type:

None

reset_mode(*modes, **kwargs)[source]

Leave the alternate screen; pyte records all modes as usual.

Parameters:
  • modes (int)

  • kwargs (object)

Return type:

None

index()[source]

Scroll without filling the history while the alternate screen is shown.

Return type:

None

reverse_index()[source]

Scroll back without touching the history on the alternate screen.

Return type:

None

resize(lines=None, columns=None)[source]

Resize both screens while the alternate one is shown.

The shell screen loses its top lines to the history and its cells beyond the new width, as pyte does for the visible screen.

Parameters:
  • lines (int | None)

  • columns (int | None)

Return type:

None

reset()[source]

Return to the shell screen on a full reset.

Return type:

None

pysnap.terminal.emulator.is_invisible_character(char)[source]

Return whether pyte would stop drawing at this character.

Parameters:

char (str) – One character.

Returns:

True for characters without width that do not combine with the previous character.

Return type:

bool

class pysnap.terminal.emulator.TerminalEmulator(columns=80, lines=24, history=5000, invisible_replacement='')[source]

Bases: object

Wrap a pyte.Screen and render it for the TUI.

Parameters:
  • columns (int)

  • lines (int)

  • history (int)

  • invisible_replacement (str)

feed(data)[source]

Feed new bytes into the terminal parser.

Parsing while the local viewport is scrolled back would write into the manually rotated history buffers and lose lines, so the view first returns to the live bottom. This mirrors the common jump-to-bottom-on-output behavior of desktop terminals.

Control sequences pyte cannot handle are filtered first (see SequenceFilter).

Parameters:

data (bytes) – Raw bytes read from the guest connection.

Return type:

None

resize(columns, lines)[source]

Resize the virtual screen.

Parameters:
  • columns (int) – New terminal width.

  • lines (int) – New terminal height.

Return type:

None

scroll_up(lines=1)[source]

Scroll the local view toward older history lines.

The alternate screen of full-screen programs has no history, so nothing happens while it is shown.

Parameters:

lines (int) – Number of lines to move upward.

Return type:

None

scroll_down(lines=1)[source]

Scroll the local view toward newer history lines.

Parameters:

lines (int) – Number of lines to move downward.

Return type:

None

scroll_to_top()[source]

Jump to the oldest locally retained history.

Return type:

None

scroll_to_bottom()[source]

Jump back to the live end of the terminal output.

Return type:

None

property bracketed_paste: bool[source]

Return whether the guest asked for bracketed paste (mode 2004).

property is_scrollback_active: bool[source]

Return whether the current view is above the live output bottom.

as_formatted_text()[source]

Render the current screen buffer as prompt_toolkit fragments.

Returns:

A list of (style, text) fragments.

Return type:

list[tuple[str, str]]

pysnap.terminal.keymap module

Translate prompt_toolkit key presses into byte sequences for the guest.

pysnap.terminal.keymap.key_press_to_bytes(key_press)[source]

Translate one key press into bytes for the VM serial console.

Parameters:

key_press (prompt_toolkit.key_binding.key_processor.KeyPress) – Prompt-toolkit key press.

Returns:

Bytes to send or None when the key is unsupported.

Return type:

bytes | None

pysnap.terminal.keymap.paste_to_bytes(text, *, bracketed)[source]

Return pasted text as the bytes a terminal sends for it.

Line breaks of any system (CR LF, LF) become CR, the Enter key of a terminal, so a text copied on Windows does not give empty lines. When the guest asked for bracketed paste (ESC [ ? 2004 h, as bash and vim do), the text is put between ESC [ 200 ~ and ESC [ 201 ~; markers inside the text are removed, so that it cannot end the paste early.

Parameters:
  • text (str) – Pasted text.

  • bracketed (bool) – Whether the guest enabled bracketed paste.

Returns:

UTF-8 bytes for the guest.

Return type:

bytes

pysnap.terminal.protocol module

Helpers for terminal control-sequence handling over serial TCP.

class pysnap.terminal.protocol.TerminalQueryResponder(_buffer=<factory>)[source]

Bases: object

Recognize terminal queries and synthesize compatible replies.

Raw serial consoles do not provide a PTY-style SIGWINCH channel into the guest system. PySnap therefore acts like a small terminal emulator and replies to xterm-compatible size queries that guest-side tools may emit.

Parameters:

_buffer (bytearray)

collect_responses(data, *, emulator, columns, lines)[source]

Return terminal replies implied by one incoming byte chunk.

Parameters:
  • data (bytes) – Newly received bytes from the guest.

  • emulator (TerminalEmulator) – Emulator holding the current cursor position.

  • columns (int) – Current visible terminal width.

  • lines (int) – Current visible terminal height.

Returns:

Zero or more response payloads to send back to the guest.

Return type:

list[bytes]

pysnap.terminal.session module

Interactive terminal session attached to a VM serial TCP console.

class pysnap.terminal.session.SessionStatus(vm_name, vm_state='Changing', port_label='', message='Connecting...')[source]

Bases: object

Mutable session status shown in the status bar.

Parameters:
  • vm_name (str)

  • vm_state (str)

  • port_label (str)

  • message (str)

vm_name: str
vm_state: str = 'Changing'
port_label: str = ''
message: str = 'Connecting...'
class pysnap.terminal.session.PauseTracker(paused=False)[source]

Bases: object

Follow pause and resume of the attached VM.

The session stays open while the VM is paused: keys are held back and the status bar says so. Any state other than running or paused ends it.

Parameters:

paused (bool)

observe(state)[source]

Record the current monitor state.

Parameters:

state (str) – Monitor label such as "Working" or "Paused".

Returns:

"paused" or "resumed" on a change, "exit" when the session must end, otherwise None.

Return type:

str | None

class pysnap.terminal.session.SessionTransport(name, label, port, serial, opener)[source]

Bases: object

How a terminal session reaches the guest.

Parameters:
  • name (str) – "Serial" or "ssh", used in messages.

  • label (str) – Status bar label such as UART1:2326 or ssh:2400.

  • port (int) – Host port, recorded in the session registry.

  • serial (bool) – Whether this is the COM1 console: it is woken with Enter, may be attached while paused and serves report extract through the control channel.

  • opener (Callable[[int, int], Awaitable[tuple[StreamReader, StreamWriter, Callable[[], Awaitable[None]] | None]]]) – Coroutine that connects for a terminal of the given columns and rows and returns a reader, a writer and an optional coroutine that cleans up afterwards.

name: str
label: str
port: int
serial: bool
opener: Callable[[int, int], Awaitable[tuple[StreamReader, StreamWriter, Callable[[], Awaitable[None]] | None]]]
classmethod for_serial(serial_port, *, timeout=None)[source]

Return the transport for the COM1 TCP server.

Parameters:
  • serial_port (int) – Host TCP port of COM1.

  • timeout (float | None) – Connection timeout; the transport default without.

Returns:

Serial transport.

Return type:

SessionTransport

classmethod for_ssh(target, store)[source]

Return the transport for an ssh login shell.

Parameters:
Returns:

ssh transport.

Return type:

SessionTransport

class pysnap.terminal.session.ScrollableTerminalControl(*args, **kwargs)[source]

Bases: FormattedTextControl

Formatted text control that scrolls the local history with the wheel.

Parameters:
  • on_scroll_up (Callable[[], None] | None)

  • on_scroll_down (Callable[[], None] | None)

  • mouse_scrolling_enabled (bool)

mouse_handler(mouse_event)[source]

Handle local wheel scrolling before delegating to the base control.

Parameters:

mouse_event (prompt_toolkit.mouse_events.MouseEvent)

class pysnap.terminal.session.TerminalSession(service=None)[source]

Bases: object

Manage one interactive terminal connection to a VM.

Parameters:

service (PySnapService | None)

run(vm_name)[source]

Start or attach to a VM and launch the terminal interface.

Parameters:

vm_name (str) – Virtual machine name.

Returns:

Exit code.

Return type:

int

pysnap.terminal.transport module

TCP transport helpers for VM serial console connections.

pysnap.terminal.transport.open_serial_socket(host, port, timeout=30.0, retry_delay=0.25)[source]

Wait for and open a blocking TCP socket to the VM serial port.

Parameters:
  • host (str) – Target host name.

  • port (int) – Target TCP port.

  • timeout (float) – Maximum time to wait.

  • retry_delay (float) – Delay between connection attempts.

Returns:

Connected socket object.

Raises:

PySnapError – If the TCP server does not become available in time.

Return type:

socket

pysnap.terminal.transport.serial_connection_probe(host, port, timeout=30.0, retry_delay=0.25)[source]

Open a short-lived serial TCP connection for automated checks.

The probe writes one newline to verify that the transport accepts client output and then keeps the connection open for the caller-managed context.

Parameters:
  • host (str) – Target host name.

  • port (int) – Target TCP port.

  • timeout (float) – Maximum time to wait.

  • retry_delay (float) – Delay between connection attempts.

Yields:

Connected socket object.

Return type:

Iterator[socket]

async pysnap.terminal.transport.open_serial_connection(host, port, timeout=30.0, retry_delay=0.25)[source]

Wait for and open a TCP connection to the VM serial port.

Parameters:
  • host (str) – Target host name.

  • port (int) – Target TCP port.

  • timeout (float) – Maximum time to wait.

  • retry_delay (float) – Delay between connection attempts.

Returns:

Reader and writer stream objects.

Raises:

PySnapError – If the TCP server does not become available in time.

Return type:

tuple[StreamReader, StreamWriter]