par-term-emu-core-rust 0.39.1
A comprehensive terminal emulator library in Rust with Python bindings - supports true color, alt screen, mouse reporting, bracketed paste, and full Unicode
Requires Python: >=3.12
Keywords: terminal, emulator, vt100, vt220, vt320, vt420, ansi, sixel, pty, rust, pyo3, terminal-emulator, unicode, true-color

# Par Term Emu Core Rust

[![PyPI](https://img.shields.io/pypi/v/par_term_emu_core_rust)](https://pypi.org/project/par_term_emu_core_rust/)
[![Crates.io](https://img.shields.io/crates/v/par-term-emu-core-rust)](https://crates.io/crates/par-term-emu-core-rust)
[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/par_term_emu_core_rust.svg)](https://pypi.org/project/par_term_emu_core_rust/)
![Runs on Linux | MacOS | Windows](https://img.shields.io/badge/runs%20on-Linux%20%7C%20MacOS%20%7C%20Windows-blue)
![Arch x86-64 | ARM | AppleSilicon](https://img.shields.io/badge/arch-x86--64%20%7C%20ARM%20%7C%20AppleSilicon-blue)
![PyPI - Downloads](https://img.shields.io/pypi/dm/par_term_emu_core_rust)
![Crates.io Downloads](https://img.shields.io/crates/d/par-term-emu-core-rust)
![PyPI - License](https://img.shields.io/pypi/l/par_term_emu_core_rust)

A comprehensive terminal emulator library written in Rust with Python bindings for Python 3.12+. Provides VT100/VT220/VT320/VT420/VT520 compatibility with PTY support, matching iTerm2's feature set.

[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://buymeacoffee.com/probello3)

## What's New

Version 0.39.1 raises the OSC data size limit from 1 MB to 128 MB, fixing silent drops of inline images (iTerm2/Kitty protocols) whose base64-encoded OSC sequence exceeded ~750 KB raw. See [CHANGELOG.md](CHANGELOG.md) for complete release notes.

## What's New in 0.34.0

### OSC 1337 RemoteHost Support

Parse `OSC 1337 ; RemoteHost=user@hostname ST` sequences for remote host detection. This is iTerm2's dedicated mechanism for reporting remote host information, commonly emitted by shell integration scripts on remote hosts. The `ShellIntegration` state now includes `hostname` and `username` attributes, and a `cwd_changed` event is emitted when the remote host changes.

```python
# After SSH to a remote host with iTerm2 shell integration:
# The shell sends: printf '\e]1337;RemoteHost=%s@%s\a' "$USER" "$HOSTNAME"

state = terminal.shell_integration_state()
print(f"Host: {state.hostname}")    # "remote-server.example.com"
print(f"User: {state.username}")    # "alice"
```

### 🔤 Unicode Normalization (NFC/NFD/NFKC/NFKD)

Configurable Unicode normalization ensures consistent text storage for search, comparison, and cursor movement. Unicode characters can have multiple binary representations that look identical (e.g., `é` can be precomposed U+00E9 or decomposed U+0065 + U+0301). Normalization eliminates this ambiguity.

```python
from par_term_emu_core_rust import Terminal, NormalizationForm

term = Terminal(80, 24)

# Default is NFC (Canonical Composition) - most common form
assert term.normalization_form() == NormalizationForm.NFC

# Switch to NFD (Canonical Decomposition) for macOS HFS+ compatibility
term.set_normalization_form(NormalizationForm.NFD)

# NFKC replaces compatibility characters (e.g., ﬁ ligature → fi)
term.set_normalization_form(NormalizationForm.NFKC)

# Disable normalization entirely
term.set_normalization_form(NormalizationForm.Disabled)
```

**Normalization Forms:**
- `NormalizationForm.NFC` - Canonical Composition (default): composes `e` + combining accent → `é`
- `NormalizationForm.NFD` - Canonical Decomposition: decomposes `é` → `e` + combining accent
- `NormalizationForm.NFKC` - Compatibility Composition: NFC + replaces compatibility chars (`ﬁ` → `fi`)
- `NormalizationForm.NFKD` - Compatibility Decomposition: NFD + replaces compatibility chars
- `NormalizationForm.Disabled` - No normalization, store text as received

### OSC 1337 SetUserVar Support

Shell integration scripts can now send user variables via `OSC 1337 SetUserVar=<name>=<base64_value>` sequences. Variables are base64-decoded, stored on the terminal, and accessible via a dedicated API. A `UserVarChanged` event is emitted when values change, enabling features like remote host detection, automatic profile switching, and hostname display.

```python
# After shell sends: printf '\e]1337;SetUserVar=%s=%s\a' "hostname" "$(printf 'server1' | base64)"
host = terminal.get_user_var("hostname")     # "server1"
all_vars = terminal.get_user_vars()           # {"hostname": "server1", ...}

# Event-driven: poll for changes
for event in terminal.poll_events():
    if event["type"] == "user_var_changed":
        print(f"{event['name']} = {event['value']}")
```

### Image Metadata Serialization for Session Persistence

Graphics state can now be serialized and restored for session persistence. All active placements, scrollback graphics, and animation state are captured in a versioned JSON snapshot with base64-encoded pixel data. External file references are also supported for compact on-disk storage.

```python
# Save graphics state
json_str = terminal.export_graphics_json()
with open("session_graphics.json", "w") as f:
    f.write(json_str)

# Restore graphics state in a new session
with open("session_graphics.json") as f:
    count = terminal.import_graphics_json(f.read())
    print(f"Restored {count} graphics")
```

### Image Placement Metadata

All graphics protocols now expose unified `ImagePlacement` metadata on `Graphic.placement`, abstracting protocol-specific placement parameters so frontends can implement inline/cover/contain rendering. The Kitty protocol exposes columns/rows sizing, z-index for layering, and sub-cell offsets. The iTerm2 protocol exposes width/height with unit support (cells, pixels, percent, auto) and `preserveAspectRatio`. New `ImagePlacement` and `ImageDimension` classes are importable from the package.

### Original Image Dimensions for Aspect Ratio Preservation

All graphics protocols (Sixel, iTerm2, Kitty) now expose `original_width` and `original_height` on `Graphic` objects. These fields preserve the original decoded pixel dimensions even when `width`/`height` change during animation, enabling frontends to calculate correct aspect ratios when scaling images to fit terminal cells.

### Kitty Graphics Compression Support

The Kitty graphics protocol now supports zlib-compressed image payloads (`o=z` parameter). Compressed data is automatically decompressed before pixel decoding, reducing data sent over the PTY. A new `was_compressed` flag on the `Graphic` class allows frontends to track compression usage for diagnostics.

### Dependencies

- Migrated to **PyO3 0.28** from 0.23, updating all Python binding patterns to the latest API
- `flate2` is now a non-optional dependency (required for Kitty `o=z` decompression)
- Added `unicode-normalization` v0.1.25 for Unicode text normalization support

## What's New in 0.33.0

### Multi-Session Streaming Server

The streaming server now supports multiple concurrent terminal sessions. Each WebSocket client can connect to a named session, and new sessions are created on demand:

```
ws://host:port/ws?session=my-session     # Connect to (or create) a named session
ws://host:port/ws?preset=python           # Create a session using a shell preset
ws://host:port/ws                         # Connect to the default session
```

**Key features:**
- **Session isolation**: Each session has its own terminal, PTY, and broadcast channels
- **Shell presets**: Define named shell commands (`--preset python=python3 --preset node=node`)
- **Idle timeout**: Sessions with no clients are automatically reaped (default: 15 minutes)
- **Client identity**: Each client receives a unique `client_id` in the Connected handshake
- **Read-only awareness**: The `readonly` field in Connected tells clients their permission level

**Default limits:**
- Max concurrent sessions: 10
- Idle session timeout: 900 seconds (15 minutes)
- Max clients per server: 100 (unchanged)

### New Streaming Events: Mode, Graphics, and Hyperlink

Three new event types allow streaming clients to react to terminal state changes:

- **ModeChanged**: Fires when terminal modes toggle (e.g., cursor visibility, mouse tracking, bracketed paste). Subscribe with `"mode"`.
- **GraphicsAdded**: Fires when images are rendered via Sixel, iTerm2, or Kitty protocols. Includes row position and format. Subscribe with `"graphics"`.
- **HyperlinkAdded**: Fires when OSC 8 hyperlinks are added. Includes URL, row, column, and optional link ID. Subscribe with `"hyperlink"`.

**Breaking:** `StreamingConfig` has new required fields (`max_sessions`, `session_idle_timeout`, `presets`). `ServerMessage::Connected` now includes `client_id` and `readonly` fields.

## What's New in 0.32.0

### Coprocess Restart Policies & Stderr Capture

Coprocesses now support automatic restart when they exit, and stderr is captured separately:

```python
from par_term_emu_core_rust import PtyTerminal, CoprocessConfig

with PtyTerminal(80, 24) as term:
    term.spawn_shell()

    # Start a coprocess that auto-restarts on failure with a 1-second delay
    config = CoprocessConfig(
        "my-watcher",
        restart_policy="on_failure",
        restart_delay_ms=1000,
    )
    cid = term.start_coprocess(config)

    # Read stderr separately from stdout
    errors = term.read_coprocess_errors(cid)
    output = term.read_from_coprocess(cid)
```

**Restart Policies:** `"never"` (default), `"always"`, `"on_failure"` (non-zero exit only)

### Trigger Notify & MarkLine as Frontend Events

`Notify` and `MarkLine` trigger actions now emit `ActionResult` events (via `poll_action_results()`) instead of directly modifying internal state. This gives frontends full control over how notifications and line marks are displayed. `MarkLine` also supports an optional `color` parameter:

```python
mark = TriggerAction("mark_line", {"label": "Error", "color": "255,0,0"})
```

**Breaking:** If you relied on `Notify` triggers adding to the notification queue or `MarkLine` triggers adding bookmarks directly, you must now handle these via `poll_action_results()`.

## What's New in 0.31.1

### Trigger Column Mapping Fix

`TriggerMatch.col` and `TriggerMatch.end_col` now correctly report grid column positions for text containing wide characters (CJK, emoji) and multi-byte UTF-8 characters. Previously, regex byte offsets were used directly, producing incorrect column values for non-ASCII text. Trigger highlights now correctly overlay the matched text even when wide or combining characters appear in the same row.

## What's New in 0.31.0

### Triggers & Automation

Register regex patterns to automatically match terminal output and execute actions — highlight matches, send notifications, set bookmarks, update session variables, or emit events for frontend handling:

```python
from par_term_emu_core_rust import Terminal, TriggerAction

term = Terminal(80, 24)

# Highlight errors in red
highlight = TriggerAction("highlight", {"bg_r": "255", "bg_g": "0", "bg_b": "0"})
term.add_trigger("errors", r"ERROR:\s+(\S+)", [highlight])

# Set a session variable from matched output
set_var = TriggerAction("set_variable", {"name": "last_status", "value": "$1"})
term.add_trigger("status", r"STATUS: (\w+)", [set_var])

# Process terminal output and scan for matches
term.process_str("ERROR: diskfull\nSTATUS: RUNNING\n")
term.process_trigger_scans()

# Poll results
matches = term.poll_trigger_matches()  # TriggerMatch objects with captures
highlights = term.get_trigger_highlights()  # Active highlight overlays
```

**Trigger Actions:** `highlight`, `notify`, `mark_line`, `set_variable`, `run_command`, `play_sound`, `send_text`, `stop`

**Features:**
- `RegexSet`-based multi-pattern matching for efficient scanning
- Capture group substitution (`$1`, `$2`) in action parameters
- Highlight overlays with optional time-based expiry
- Automatic scanning in PTY mode; manual `process_trigger_scans()` for non-PTY

### Coprocess Management

Run external processes alongside terminal sessions with automatic output piping:

```python
from par_term_emu_core_rust import PtyTerminal, CoprocessConfig

with PtyTerminal(80, 24) as term:
    term.spawn_shell()

    # Start a coprocess that receives terminal output
    config = CoprocessConfig("grep", args=["ERROR"], copy_terminal_output=True)
    cid = term.start_coprocess(config)

    # Read coprocess output
    lines = term.read_from_coprocess(cid)

    # Check status and stop
    term.coprocess_status(cid)  # True if running
    term.stop_coprocess(cid)
```

**New Python Classes:** `Trigger`, `TriggerAction`, `TriggerMatch`, `CoprocessConfig`

## What's New in 0.30.0

### ⌨️ modifyOtherKeys Protocol Support

XTerm extension for enhanced keyboard input reporting, enabling applications to receive modifier keys with regular characters:

```python
from par_term_emu_core_rust import Terminal

term = Terminal(80, 24)

# Enable modifyOtherKeys mode via escape sequence
term.process(b"\x1b[>4;2m")  # Mode 2: report all keys with modifiers
print(f"Mode: {term.modify_other_keys_mode()}")  # Output: 2

# Or set directly
term.set_modify_other_keys_mode(1)  # Mode 1: special keys only

# Query mode (response in drain_responses())
term.process(b"\x1b[?4m")
response = term.drain_responses()  # Returns b"\x1b[>4;1m"
```

**Modes:**
- `0` - Disabled (default)
- `1` - Report modifiers for special keys only
- `2` - Report modifiers for all keys

**New Methods:**
- `modify_other_keys_mode()` - Get current mode
- `set_modify_other_keys_mode(mode)` - Set mode directly (values > 2 clamped to 2)

**Sequences:**
- `CSI > 4 ; mode m` - Set mode
- `CSI ? 4 m` - Query mode (response: `CSI > 4 ; mode m`)

**Note:** Mode resets to 0 on terminal reset and when exiting alternate screen.

### 🎨 Faint Text Alpha Control

Configurable alpha multiplier for SGR 2 (dim/faint) text, allowing fine-grained control over how dim text is rendered:

```python
from par_term_emu_core_rust import Terminal

term = Terminal(80, 24)

# Get current faint text alpha (default: 0.5 = 50% dimming)
print(f"Alpha: {term.faint_text_alpha()}")  # Output: 0.5

# Set faint text to be more transparent (more dimmed)
term.set_faint_text_alpha(0.3)  # 30% opacity

# Set faint text to be less transparent (less dimmed)
term.set_faint_text_alpha(0.7)  # 70% opacity

# Values are clamped to 0.0-1.0 range
term.set_faint_text_alpha(1.5)  # Clamped to 1.0
term.set_faint_text_alpha(-0.5)  # Clamped to 0.0
```

**New Methods:**
- `faint_text_alpha()` - Get current alpha multiplier (0.0-1.0)
- `set_faint_text_alpha(alpha)` - Set alpha multiplier (clamped to valid range)

**Usage:** This setting is used by the screenshot renderer and can be queried by frontends for consistent rendering of dim text (SGR 2).

## What's New in 0.28.0

### 🏷️ Badge Format Support (OSC 1337 SetBadgeFormat)

iTerm2-style badge support for terminal overlays with variable interpolation:

```python
from par_term_emu_core_rust import Terminal

term = Terminal(80, 24)

# Set badge format with variables
term.set_badge_format(r"\(username)@\(hostname)")

# Set session variables
term.set_badge_session_variable("username", "alice")
term.set_badge_session_variable("hostname", "server1")

# Evaluate badge - returns "alice@server1"
badge = term.evaluate_badge()
print(f"Badge: {badge}")

# Get all session variables
vars = term.get_badge_session_variables()
print(f"Columns: {vars['columns']}, Rows: {vars['rows']}")
```

**New Methods:**
- `badge_format()` - Get current badge format template
- `set_badge_format(format)` - Set badge format with `\(variable)` placeholders
- `clear_badge_format()` - Clear badge format
- `evaluate_badge()` - Evaluate badge with session variables
- `get_badge_session_variable(name)` - Get a session variable value
- `set_badge_session_variable(name, value)` - Set a custom session variable
- `get_badge_session_variables()` - Get all session variables as a dictionary

**Built-in Variables:**
`hostname`, `username`, `path`, `job`, `last_command`, `profile_name`, `tty`, `columns`, `rows`, `bell_count`, `selection`, `tmux_pane_title`, `session_name`, `title`

**Security:** Badge formats are validated to reject shell injection patterns (backticks, `$()`, pipes, etc.)

### 🔧 Tmux Control Mode Fixes

- Fixed CRLF line ending handling (strips `\r` from `\r\n` line endings)
- Fixed `%output` notifications to preserve trailing spaces
- Fixed OSC 133 exit code parsing from `OSC 133 ; D ; <exit_code> ST`

## What's New in 0.27.0

### 🔄 Tmux Control Mode Auto-Detection

Automatic detection and switching to tmux control mode to handle race conditions:

```python
from par_term_emu_core_rust import Terminal

term = Terminal(80, 24)

# Enable auto-detection before starting tmux
# Parser will automatically switch to control mode when %begin is seen
term.set_tmux_auto_detect(True)

# Or just call set_tmux_control_mode(True) which enables auto-detect automatically
term.set_tmux_control_mode(True)

# Process tmux output - auto-detects %begin and switches modes
term.process_str("$ tmux -CC\n%begin 1234567890 1\n%output %1 Hello\n")

# Check modes
print(f"Control mode: {term.is_tmux_control_mode()}")
print(f"Auto-detect: {term.is_tmux_auto_detect()}")
```

**New Methods:**
- `set_tmux_auto_detect(enabled)` - Enable/disable auto-detection of tmux control mode
- `is_tmux_auto_detect()` - Check if auto-detection is enabled

**Behavior:**
- When `%begin` notification is detected, parser automatically switches to control mode
- Data before `%begin` is returned as `TerminalOutput` notification for normal display
- Calling `set_tmux_control_mode(True)` now also enables auto-detect

## What's New in 0.26.0

### 🎬 Session Recording Enhancements

Full Python API for session recording with event iteration and environment capture:

```python
from par_term_emu_core_rust import Terminal, RecordingEvent, RecordingSession

term = Terminal(80, 24)

# Start recording
term.start_recording("demo session")
term.process_str("echo hello\n")
term.record_marker("checkpoint")
session = term.stop_recording()

# Access session metadata
print(f"Duration: {session.get_duration_seconds()}s")
print(f"Size: {session.get_size()}")
print(f"Environment: {session.env}")

# Iterate over recorded events
for event in session.events:
    print(f"{event.event_type} at {event.timestamp}ms: {event.get_data_str()}")
```

**New Exports:**
- `RecordingEvent` and `RecordingSession` now directly importable from the module

**New RecordingSession Properties:**
- `session.events` - List of RecordingEvent objects
- `session.env` - Dict of captured environment variables

**New PtyTerminal Methods:**
- `record_output()`, `record_input()`, `record_resize()`, `record_marker()`, `get_recording_session()`

## What's New in 0.25.0

### 🌐 Configurable Unicode Width

Full control over character width calculations for proper terminal alignment in CJK and mixed-script environments:

```python
from par_term_emu_core_rust import (
    Terminal, WidthConfig, UnicodeVersion, AmbiguousWidth,
    char_width, str_width, is_east_asian_ambiguous
)

# Configure terminal for CJK environment (Greek/Cyrillic = 2 cells)
term = Terminal(80, 24)
term.set_width_config(WidthConfig.cjk())

# Or configure individually
term.set_ambiguous_width(AmbiguousWidth.Wide)
term.set_unicode_version(UnicodeVersion.Auto)

# Standalone width functions
print(char_width("日"))  # 2 - CJK character
print(char_width("α", WidthConfig.cjk()))  # 2 - Greek with CJK config
print(str_width("Hello日本"))  # 9 - mixed text
print(is_east_asian_ambiguous("α"))  # True - Greek is ambiguous
```

**New Types:**
- `UnicodeVersion`: Unicode9-Unicode16, Auto
- `AmbiguousWidth`: Narrow (1 cell), Wide (2 cells)
- `WidthConfig`: Combines both with `.cjk()` and `.western()` presets

**New Functions:**
- `char_width(c, config?)` / `str_width(s, config?)` - configurable width
- `char_width_cjk(c)` / `str_width_cjk(s)` - CJK convenience functions
- `is_east_asian_ambiguous(c)` - check if character is ambiguous

## What's New in 0.23.0

### 📨 Configurable ENQ Answerback

- Added an optional answerback string that the terminal returns when receiving **ENQ (0x05)**
- Disabled by default for security; set a custom value via Rust API or Python bindings
- Responses are buffered in the existing response buffer and drained with `drain_responses()`
- Python bindings now expose `answerback_string()` and `set_answerback_string()`

## What's New in 0.22.1

### 🐛 Search Unicode Bug Fix

Fixed `search()` and `search_scrollback()` returning byte offsets instead of character offsets for text containing multi-byte Unicode characters (CJK, emoji, etc.):

- `SearchMatch.col` now correctly returns the character column position
- `SearchMatch.length` now correctly returns the character count
- Example: Searching for "World" in "こんにちは World" now returns `col=6` (correct) instead of `col=16` (byte offset)

## What's New in 0.22.0

### 🏳️ Regional Indicator Flag Emoji Support

Proper grapheme cluster handling for flag emoji like 🇺🇸, 🇬🇧, 🇯🇵:

- Flag emoji are now correctly combined into single wide (2-cell) graphemes
- Two regional indicator codepoints are combined with the first as the base character and the second in the combining vector
- Cursor correctly advances by 2 cells after writing a flag
- Added `unicode-segmentation` crate dependency for grapheme cluster support
- Comprehensive test suite for flag emoji

## What's New in 0.21.0

### 🚀 parking_lot Migration

The entire library has been migrated from `std::sync::Mutex` to **`parking_lot::Mutex`**.

- **Improved Reliability**: Eliminated "Mutex Poisoning". A panic in one thread no longer renders the terminal state permanently inaccessible to other threads.
- **Better Performance**: Faster lock/unlock operations and significantly smaller memory footprint for locks.
- **Ergonomic API**: Lock acquisition no longer requires `.unwrap()`, making the code cleaner and more robust.

## What's New in 0.20.1

### 🔧 Safe Environment Variable API

Added new methods to pass environment variables and working directory directly to spawned processes without modifying the global environment of the parent process.

- **Rust**: `spawn_shell_with_env(env, cwd)`, `spawn_with_env(command, args, env, cwd)`
- **Python**: `spawn_shell(env=None, cwd=None)` - now supports optional environment dictionary and working directory path.
- **Thread Safety**: Eliminates the need for `unsafe { std::env::set_var() }` in multi-threaded applications like those using Tokio.

## What's New in 0.20.0

### 🎨 External UI Theme

The web frontend UI chrome can now be customized **after static build** without rebuilding:

```css
/* Edit web_term/theme.css */
:root {
  --terminal-bg: #0a0a0a;      /* Main background */
  --terminal-surface: #1a1a1a; /* Status bar, cards */
  --terminal-border: #2a2a2a;  /* Borders */
  --terminal-accent: #3a3a3a;  /* Scrollbar, accents */
  --terminal-text: #e0e0e0;    /* Primary text */
}
```

- Edit colors and refresh the page - no rebuild required
- Terminal emulator colors (ANSI palette) still controlled by server `--theme` option
- See [docs/STREAMING.md](docs/STREAMING.md#theme-system) for details

### 🐛 Bug Fixes

- **Web Terminal On-Screen Keyboard**: Fixed native device keyboard appearing when tapping on-screen keyboard buttons on mobile devices
  - The on-screen keyboard now properly prevents xterm's internal textarea from gaining focus
  - Tapping virtual keys no longer triggers the device's native keyboard

## What's New in 0.19.5

### 🐛 Bug Fixes

- **Streaming Server Shell Restart Input**: Fixed WebSocket client connections not receiving input after shell restart
  - PTY writer was captured once at connection time, becoming stale after shell restart
  - Client keyboard input now properly reaches the shell after any restart

## What's New in 0.19.4

### 🔧 Python SDK Sync

- **Python SDK aligned with Rust SDK**: All streaming features now available in Python bindings
  - `StreamingConfig.enable_http` / `web_root` - HTTP server configuration (getter/setter)
  - `StreamingServer.max_clients()` - Query maximum allowed clients
  - `StreamingServer.create_theme_info()` - Create theme dictionaries for protocol
  - `encode_server_message("pong")` - Pong message encoding support
  - `encode_server_message("connected", theme=...)` - Theme support in connected messages

```python
from par_term_emu_core_rust import StreamingConfig, StreamingServer, encode_server_message

# Configure HTTP serving
config = StreamingConfig(enable_http=True, web_root="/var/www/terminal")

# Create theme for connected message
theme = StreamingServer.create_theme_info(
    name="my-theme",
    background=(0, 0, 0),
    foreground=(255, 255, 255),
    normal=[(0,0,0), (255,0,0), (0,255,0), (255,255,0), (0,0,255), (255,0,255), (0,255,255), (200,200,200)],
    bright=[(128,128,128), (255,128,128), (128,255,128), (255,255,128), (128,128,255), (255,128,255), (128,255,255), (255,255,255)]
)

# Encode messages
pong = encode_server_message("pong")
connected = encode_server_message("connected", cols=80, rows=24, session_id="abc", theme=theme)
```

## What's New in 0.19.2

### 🐛 Bug Fixes

- **Streaming Server Hang on Shell Exit**: Fixed server hanging indefinitely when the shell exits
  - Added shutdown signal mechanism to gracefully terminate the broadcaster loop
  - Prevents blocking indefinitely when shell exits in some conditions

## What's New in 0.19.1

### 🐛 Bug Fixes

- **Streaming Server Ping/Pong**: Fixed application-level ping/pong handling
  - Server was sending WebSocket-level pong frames instead of protobuf `Pong` messages
  - Frontend heartbeat mechanism now properly receives pong responses
  - Fixes stale connection detection that was failing due to missing pong responses

## What's New in 0.19.0

### 🎉 New Features

- **Automatic Shell Restart**: Streaming server now automatically restarts the shell when it exits
  - Default behavior: shell is restarted automatically when it exits
  - New `--no-restart-shell` CLI option to disable automatic restart
  - New `PAR_TERM_NO_RESTART_SHELL` environment variable support
  - When restart is disabled, server exits gracefully when the shell exits

- **Header/Footer Toggle in On-Screen Keyboard**: Layout toggle button in keyboard header
  - Show/hide header and footer directly from the on-screen keyboard
  - Blue indicator shows when header/footer is visible
  - Convenient for maximizing terminal space on mobile

- **Font Size Controls in On-Screen Keyboard**: Plus/minus buttons in keyboard header
  - Adjust font size (8-32px) without opening the header panel

### 🔧 Changes

- **StreamingServer API**: `set_pty_writer` now uses interior mutability for shell restart support
- **UI Improvements**: Font size controls moved to keyboard header; floating buttons repositioned side by side

## What's New in 0.18.2

### 🎉 New Features

- **Font Size Control**: User-adjustable terminal font size in web frontend
  - Plus/minus buttons in header (8px to 32px range)
  - Persisted to localStorage across sessions

- **Heartbeat/Ping Mechanism**: Stale WebSocket connection detection
  - Sends ping every 25s, expects pong within 10s
  - Automatically closes and reconnects stale connections

### 🔒 Security Hardening

- **Web Terminal Security Fixes**: Comprehensive security audit remediation
  - **Reverse-tabnabbing prevention**: Terminal links now open with `noopener,noreferrer`
  - **Zip bomb protection**: Added decompression size limits (256KB compressed, 2MB decompressed)
  - **Localhost probe fix**: WebSocket preconnect hints gated to development mode only
  - **Snapshot size guard**: 1MB limit on screen snapshots to prevent UI freezes

### 🐛 Bug Fixes

- **WebSocket URL Changes**: Properly disconnects and reconnects when URL changes
- **Invalid URL Handling**: Displays friendly error instead of crashing
- **Next.js Config**: Merged duplicate config files into single file
- **Toggle Button Overlap**: Moved button left to avoid scrollbar overlap

## What's New in 0.18.1

### 🐛 Bug Fixes

- **Web Terminal On-Screen Keyboard**: Fixed device virtual keyboard appearing when tapping on-screen keyboard buttons on mobile devices
  - Added `tabIndex={-1}` to all buttons to prevent focus acquisition that triggered device keyboard

## What's New in 0.18.0

### 🎉 New Features

- **Environment Variable Support**: All CLI options now support environment variables with `PAR_TERM_` prefix
  - Examples: `PAR_TERM_HOST`, `PAR_TERM_PORT`, `PAR_TERM_THEME`, `PAR_TERM_HTTP_USER`
  - Configuration via environment for containerized deployments

- **HTTP Basic Authentication**: New password protection for the web frontend
  - `--http-user` - Username for HTTP Basic Auth
  - `--http-password` - Clear text password
  - `--http-password-hash` - htpasswd format hash (bcrypt, apr1, SHA1, MD5 crypt)
  - `--http-password-file` - Read password from file (auto-detects hash vs clear text)

### 🧪 Test Coverage

- **Comprehensive Streaming Test Suite**: 94 new tests for streaming functionality
  - Protocol message constructors, theme info, HTTP Basic Auth configuration
  - Binary protocol encoding/decoding with compression
  - Event types, streaming errors, JSON serialization
  - Unicode content and ANSI escape sequence preservation

### 🔧 Improvements

- **Python Bindings**: Binary protocol functions now properly exported (`encode_server_message`, `decode_server_message`, `encode_client_message`, `decode_client_message`)

### Usage Examples

```bash
# Environment variables
export PAR_TERM_HOST=0.0.0.0
export PAR_TERM_HTTP_USER=admin
export PAR_TERM_HTTP_PASSWORD=secret
par-term-streamer --enable-http

# CLI with htpasswd hash
par-term-streamer --enable-http --http-user admin --http-password-hash '$apr1$...'
```

## What's New in 0.17.0

### 🎉 New Features

- **Web Terminal Macro System**: New macro tab in the on-screen keyboard for creating and playing terminal command macros
  - Create named macros with multi-line scripts (one command per line)
  - Quick select buttons to run macros with a single tap
  - Playback with 200ms delay before each Enter key for reliable command execution
  - Edit and delete existing macros via hover menu
  - Stop button to abort macro playback mid-execution
  - Macros persist to localStorage across sessions
  - Visual feedback during playback (pulsing animation, stop button)
  - Option to disable sending Enter after each line (for text insertion macros)
  - Template commands for advanced scripting: `[[delay:N]]`, `[[enter]]`, `[[tab]]`, `[[esc]]`, `[[space]]`, `[[ctrl+X]]`, `[[shift+X]]`, `[[ctrl+shift+X]]`, `[[shift+tab]]`, `[[shift+enter]]`

- **On-Screen Keyboard Enhancements**:
  - Permanent symbols grid on the right side with all keyboard symbols (32 keys)
  - Added Space, Enter, http://, and https:// buttons to modifier row
  - Added tooltips to Ctrl shortcut buttons
  - Expanded symbol keys with full punctuation set

### 🔧 Improvements

- **On-Screen Keyboard Layout**: Reorganized for better usability with more compact vertical layout and persistent symbols grid

### 📦 Dependency Updates

- **Web Frontend**: Updated @types/node (25.0.1 → 25.0.2)

## What's New in 0.16.3

### 🐛 Bug Fixes

- **Web Terminal tmux/TUI Fix**: Fixed control characters (`^[[?1;2c^[[>0;276;0c`) appearing when running tmux or other TUI applications in the web terminal. The issue was caused by xterm.js generating Device Attributes responses when the backend terminal emulator already handles these queries.

### 🚀 Performance Optimizations

- **jemalloc Allocator**: New optional `jemalloc` feature for 5-15% server throughput improvement (non-Windows only)
- **TCP_NODELAY**: Disabled Nagle's algorithm for lower keystroke latency (up to 40ms improvement)
- **Output Batching**: Time-based batching at 60fps reduces WebSocket overhead by 50-80% during burst output
- **Compression Threshold**: Lowered to 256 bytes to compress more typical terminal output
- **WebSocket Preconnect**: Reduces initial connection latency by 100-200ms
- **Font Preloading**: Eliminates layout shift and font flash

### 📦 Dependency Updates

- **Web Frontend**: Updated Next.js and type definitions
- **Pre-commit Hooks**: Updated ruff linter

## What's New in 0.16.2

### 🔧 Compatibility Fix

- **TERM Environment Variable**: Changed default `TERM` from `xterm-kitty` to `xterm-256color` for better compatibility with systems lacking kitty terminfo

## What's New in 0.16.0

### 🔒 TLS/SSL Support

- **Secure WebSocket Connections** for production deployments:
  - New CLI options: `--tls-cert`, `--tls-key`, `--tls-pem`
  - Supports separate cert/key files or combined PEM
  - Enables HTTPS and WSS (secure WebSocket)

```bash
# Using separate cert and key files
par-term-streamer --enable-http --tls-cert cert.pem --tls-key key.pem

# Using combined PEM file
par-term-streamer --enable-http --tls-pem combined.pem
```

### 🚀 Performance: Binary Protocol

- **BREAKING: Protocol Buffers for WebSocket Streaming**:
  - Replaced JSON with binary Protocol Buffers encoding
  - **~80% smaller messages** for typical terminal output
  - Optional zlib compression for large payloads (screen snapshots)
  - Wire format: 1-byte header + protobuf payload

### 🐍 Python Bindings

- **TLS Configuration**: `StreamingConfig` methods for TLS setup
- **Binary Protocol Functions**: `encode_server_message()`, `decode_server_message()`, `encode_client_message()`, `decode_client_message()`

See [CHANGELOG.md](CHANGELOG.md) for complete version history.

## What's New in 0.15.0

### 🎉 New Features

- **Streaming Server CLI Enhancements**:
  - `--download-frontend` option to download prebuilt web frontend from GitHub releases
  - `--frontend-version` option to specify version to download (default: "latest")
  - `--use-tty-size` option to use current terminal size from TTY
  - No longer requires Node.js/npm to use web frontend - can download prebuilt version

### Quick Start

```bash
# Build the streaming server
make streamer-build-release

# Download prebuilt web frontend (no Node.js required!)
./target/release/par-term-streamer --download-frontend

# Run server with frontend
./target/release/par-term-streamer --enable-http

# Open browser to http://127.0.0.1:8099
```

## What's New in 0.14.0

### 🎉 New Features

- **Web Terminal Onscreen Keyboard**: Mobile-friendly virtual keyboard for touch devices
  - Special keys missing from iOS/Android keyboards: Esc, Tab, arrow keys, Page Up/Down, Home, End, Insert, Delete
  - Function keys F1-F12 (toggleable), symbol keys (|, \, `, ~, {, }, etc.)
  - Modifier keys (Ctrl, Alt, Shift) that combine with other keys
  - Quick Ctrl shortcuts: ^C, ^D, ^Z, ^L, ^A, ^E, ^K, ^U, ^W, ^R
  - Glass morphism design, haptic feedback, auto-shows on mobile

- **OSC 9;4 Progress Bar Support** (ConEmu/Windows Terminal style):
  - Terminal applications can report progress that can be displayed in tab bars, taskbars, or window titles

## What's New in 0.13.0

### 🎉 New Features
- **Streaming Server Enhancements**:
  - `--size` CLI option for specifying terminal size in `COLSxROWS` format (e.g., `--size 120x40` or `-s 120x40`)
  - `--command` / `-c` CLI option to execute a command after shell startup (with 1 second delay for prompt settling)
  - `initial_cols` and `initial_rows` configuration options in `StreamingConfig` for both Rust and Python APIs

- **Python Bindings Enhancements**:
  - New `MouseEncoding` enum for mouse event encoding control (Default, Utf8, Sgr, Urxvt)
  - Direct screen buffer control: `use_alt_screen()`, `use_primary_screen()`
  - Mouse encoding control: `mouse_encoding()`, `set_mouse_encoding()`
  - Mode setters: `set_focus_tracking()`, `set_bracketed_paste()`, `set_title()`
  - Bold brightening control: `bold_brightening()`, `set_bold_brightening()`
  - Faint text alpha control: `faint_text_alpha()`, `set_faint_text_alpha()`
  - Color getters for all theme colors (link, bold, cursor guide, badge, match, selection)

## What's New in 0.12.0

### 🐛 Bug Fixes
- **Terminal Reflow Improvements**: Multiple fixes to scrollback and grid reflow behavior during resize

## What's New in 0.11.0

### 🎉 New Features
- **Full Terminal Reflow on Width Resize**: Both scrollback AND visible screen content now reflow when terminal width changes
  - Previously, width changes cleared scrollback and clipped visible content
  - Now implements intelligent reflow similar to xterm and iTerm2:
    - **Scrollback**: Preserves all history with proper line wrapping/unwrapping
    - **Visible Screen**: Content wraps instead of being clipped when narrowing
    - Width increase: Unwraps soft-wrapped lines into longer lines
    - Width decrease: Re-wraps lines that no longer fit
  - Preserves all cell attributes (colors, bold, italic, etc.)
  - Handles wide characters (CJK, emoji) correctly at line boundaries
  - Significant UX improvement for terminal resize operations

## What's New in 0.10.0

### 🎉 New Features
- **Emoji Sequence Preservation**: Complete support for complex emoji sequences and grapheme clusters
  - ⚠️ vs ⚠ - Variation selectors (emoji vs text style)
  - 👋🏽 - Skin tone modifiers (Fitzpatrick scale)
  - 👨‍👩‍👧‍👦 - ZWJ sequences (family emoji)
  - 🇺🇸 🇬🇧 - Regional indicator flags
  - é - Combining diacritics and marks
  - New `grapheme` module for Unicode cluster detection
  - Enhanced Python bindings export full grapheme clusters

- **Web Terminal Frontend**: Modern Next.js-based web interface
  - Built with Next.js, TypeScript, and Tailwind CSS v4
  - Theme support with configurable color palettes
  - Nerd Font support for file/folder icons
  - New Makefile targets for web frontend development

- **Terminal Sequence Support**:
  - CSI 3J - Clear scrollback buffer command
  - Improved cursor positioning for snapshot exports

### 🐛 Bug Fixes
- Graphics now properly preserved when scrolling into scrollback buffer
- Sixel content saved to scrollback during large scrolling operations
- Kitty Graphics Protocol animation parsing fixes (base64 encoding, frame actions)

### ⚠️ Breaking Changes (Rust API only)
- **`Cell` struct no longer implements `Copy`** (now `Clone` only)
  - Required for variable-length grapheme cluster storage
  - All cell copy operations now require explicit `.clone()` calls
  - **Python bindings are unaffected** - no changes needed in Python code
  - Performance impact is minimal due to efficient cloning

## What's New in 0.9.1

- **Theme Rendering Fix**: Fixed theme color palette application in Python bindings

## What's New in 0.9.0

- **Graphics Protocol Support**: Comprehensive multi-protocol graphics implementation
  - **iTerm2 Inline Images** (OSC 1337): PNG, JPEG, GIF support with base64 encoding
  - **Kitty Graphics Protocol** (APC G): Advanced image placement with reuse and animations
  - **Sixel Graphics**: Enhanced with unique IDs and configurable cell dimensions
  - Unified `GraphicsStore` with scrollback support and memory limits
  - Animation support with frame composition and timing control
  - Graphics dropped event tracking for resource management

- **Pre-built Streaming Server Binaries**: Download ready-to-run binaries from GitHub Releases
  - Linux (x86_64, ARM64), macOS (Intel, Apple Silicon), Windows (x86_64)
  - No compilation needed - just download and run
  - Includes separate web frontend package (tar.gz/zip) for serving the terminal interface
  - Published to crates.io for Rust developers: `cargo install par-term-emu-core-rust --features streaming`

See [CHANGELOG.md](CHANGELOG.md) for complete version history.

## Features

### Core Terminal Emulation

- **VT100/VT220/VT320/VT420/VT520 Support** - Comprehensive terminal emulation matching iTerm2
- **Rich Color Support** - 16 ANSI colors, 256-color palette, 24-bit RGB (true color)
- **Text Attributes** - Bold, italic, underline (5 styles), strikethrough, blink, reverse, dim, hidden
- **Advanced Cursor Control** - Full VT cursor movement and positioning
- **Line/Chara