No project description provided
Project description
libTerm
Lightweight Python library for direct terminal control on POSIX systems (Linux/macOS). Works with raw ANSI escape codes, terminal modes, cursor positioning, input handling, and color management — without heavy abstractions.
Quick Start
git clone https://github.com/hoefkensj/libTerm
cd libTerm
python -m venv .venv && source .venv/bin/activate
pip install -e .
python -m libTerm.examples.ex_arrowkeys
Or install directly into your project:
pip install libTerm
What is libTerm?
A direct interface to terminal I/O and ANSI control codes. Useful for:
- Terminal games (snake, roguelikes, real-time animations)
- Interactive CLI tools with custom key handling
- Live dashboards that update without clearing the screen
- Learning ANSI sequences and POSIX terminal control
Features
- Terminal modes: Switch between NORMAL (echo, canonical) and CONTROL (raw input, no echo, cursor hidden)
- Cursor control: Position, move, show/hide, save/restore positions on a stack
- Raw keyboard input: Non-blocking event polling, read all available input at once
- Color detection: Query current foreground/background colors from terminal
- Terminal size: Track terminal width and height, detect resize events
- Direct ANSI access: Use raw escape codes alongside the API
Basic Usage
from libTerm import Term, Mode, Coord
term = Term()
# Query terminal state
print(f"Size: {term.size.xy}") # (width, height)
print(f"Cursor at: {term.cursor.xy}") # (x, y)
print(f"BG color: {term.color.bg}") # Current background color
Terminal Modes
Switch between normal line-editing and raw control input:
from libTerm import Term, Mode
term = Term()
# Enter raw input mode (no echo, no line buffering, cursor hidden)
term.mode = Mode.CONTROL
# Poll for keyboard events without blocking
if term.stdin.event:
key = term.stdin.read()
if key == 'q':
break
# Restore normal mode (echo on, canonical input, cursor visible)
term.mode = Mode.NORMAL
Keyboard Input (Non-Blocking)
Read raw input one event at a time:
# Check if input is available (non-blocking)
if term.stdin.event:
key_bytes = term.stdin.read()
# Arrow keys come as ANSI sequences
if key_bytes == '\x1b[A': # Up
print("UP")
elif key_bytes == '\x1b[B': # Down
print("DOWN")
elif key_bytes == '\x1b[C': # Right
print("RIGHT")
elif key_bytes == '\x1b[D': # Left
print("LEFT")
vs. using input():
input()blocks and echoes, waits for Enterterm.stdin.event + term.stdin.read()is non-blocking, captures individual bytes, allows arrow keys and Ctrl sequences
Cursor Control
Position the cursor, move it, or hide it:
from libTerm import Coord
# Set cursor position (1-indexed)
term.cursor.xy = Coord(10, 5)
print("*")
# Move relative to current position
term.cursor.move.down(3)
term.cursor.move.right(5)
print("*")
# Show / hide cursor
term.cursor.hide()
term.cursor.show()
vs. raw ANSI:
# Without libTerm (raw ANSI):
print("\x1b[5;10H*") # Move to (10, 5), print *
print("\x1b[3B\x1b[5C*") # Move down 3, right 5, print *
print("\x1b[?25l") # Hide cursor
print("\x1b[?25h") # Show cursor
libTerm makes this discoverable and less error-prone.
Cursor Position Stack
Save and restore cursor positions easily:
term.cursor.store.save() # Push current position
print("Hello at position 1")
term.cursor.xy = Coord(1, 10)
print("Hello at position 2")
term.cursor.store.undo() # Pop back to saved position
print("Back to position 1")
Coordinates (Coord)
Immutable coordinate type:
c = Coord(5, 10)
print(c.x, c.y) # 5, 10
print(c[0], c[1]) # 5, 10 (indexing)
print(c + Coord(2, 3)) # Coord(7, 13)
Terminal Size & Resize Detection
Query size and detect when terminal is resized:
width, height = term.size.xy
print(f"{width} x {height}")
# Detect resize events
if term.size.changed:
print("Terminal was resized!")
vs. os.get_terminal_size():
- libTerm tracks resize events
- Provides a convenient object interface
- Integrates with the terminal state system
Colors
Query the current foreground and background colors:
fg = term.color.fg
bg = term.color.bg
# Color has R, G, B components (0-255 per channel)
print(f"Background: RGB({bg.R}, {bg.G}, {bg.B})")
Direct ANSI Access
libTerm doesn't hide ANSI. Mix raw codes with the API:
# Use Ansi enum for common sequences
from libTerm import Ansi
print(Ansi.hide) # Hide cursor
print(Ansi.show) # Show cursor
print("\x1b[31mRED text\x1b[m") # Raw ANSI still works
Why Use libTerm?
vs. Curses / ncurses
- libTerm: Direct, close to ANSI, minimal setup, no system package dependency
- Curses: Heavier abstraction, window-based model, more complex for simple tasks
Choose libTerm if you want raw terminal access without heavy abstractions.
vs. Manual ANSI Codes
- Raw ANSI: Error-prone escape sequences, hard to remember, no structure
- libTerm: Named enums, methods with clear intent, still lets you write raw ANSI
Choose libTerm if you want safety and discoverability alongside low-level control.
vs. Rich / Textual
- Rich/Textual: High-level TUI framework, great for dashboards, lots of abstraction
- libTerm: Low-level, minimal, close to the terminal
Choose libTerm if you want to build your own abstractions or learn terminal internals.
Examples
Located in src/libTerm/examples/:
| File | Shows |
|---|---|
ex_basic.py |
Terminal state inspection |
ex_arrowkeys.py |
Raw arrow key polling |
ex_snake_manual.py |
Keyboard-controlled game |
ex_snake_automatic.py |
Animation without input |
ex_colors.py |
Color detection |
ex_buffers.py |
Alternate screen buffer |
ex_printkeys.py |
Debug: see all key codes |
Run an example:
python -m libTerm.examples.ex_arrowkeys
Platform Support
- Linux: Full support
- macOS: Full support
- Windows: Partial (alternate implementations in progress)
Philosophy
libTerm is intentionally low-level:
- Stay close to ANSI and POSIX
- Avoid heavy abstractions
- Let you control the terminal directly
- Make common tasks simple, complex tasks possible
If you need a full TUI framework, use Rich or Textual. If you want control and simplicity, use libTerm.
Contributing
Pull requests welcome. Report bugs and experimental ideas.
Happy terminal hacking!
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file libterm-0.3.6.tar.gz.
File metadata
- Download URL: libterm-0.3.6.tar.gz
- Upload date:
- Size: 89.3 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
213ce3532f794169ae3702dfab462a9891d2016084152100709255c1c43f438b
|
|
| MD5 |
c2ddd3e3e2fb92b92ba497288eb42dd7
|
|
| BLAKE2b-256 |
a6c0134b38a9565f072d41cbf5cd203175182b078d527b68c822660a15082b45
|
File details
Details for the file libterm-0.3.6-py3-none-any.whl.
File metadata
- Download URL: libterm-0.3.6-py3-none-any.whl
- Upload date:
- Size: 46.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c85b5ba7e9b69b27145940a724a72f1837ac99aa386abc8ee52b999a0d3ff085
|
|
| MD5 |
778ba48b61c8f7db2ba2a8f3376c2066
|
|
| BLAKE2b-256 |
803b57138fbd3f5af65a0ddd957d5aed483f0b460695c28f60b02c3d6513f8fc
|