mud/src/mudlib/caps.py
Jared Miller 388e693f8c
Add MTTS capability parsing module with client color detection
Parses MTTS bitfield values from telnetlib3 ttype3 into a ClientCaps dataclass.
Includes color_depth property that returns the best available color mode
(truecolor, 256, or 16) based on client capabilities.
2026-02-07 22:44:23 -05:00

90 lines
2.5 KiB
Python

"""MTTS (Mud Terminal Type Standard) capability parsing.
Parses MTTS bitfield values from telnetlib3's ttype3 into a structured dataclass.
MTTS is advertised via TTYPE round 3 as "MTTS <number>" where the number is a
bitfield of client capabilities.
See docs/lessons/charset-vs-mtts.txt for protocol details.
"""
from dataclasses import dataclass
from typing import Literal
@dataclass
class ClientCaps:
"""Parsed client capabilities from MTTS bitfield."""
ansi: bool = False
vt100: bool = False
utf8: bool = False
colors_256: bool = False
mouse_tracking: bool = False
osc_color_palette: bool = False
screen_reader: bool = False
proxy: bool = False
truecolor: bool = False
mnes: bool = False
mslp: bool = False
ssl: bool = False
@property
def color_depth(self) -> Literal["truecolor", "256", "16"]:
"""Return best available color mode: truecolor, 256, or 16."""
if self.truecolor:
return "truecolor"
if self.colors_256:
return "256"
return "16"
def parse_mtts(ttype3: str | None) -> ClientCaps:
"""Parse MTTS capability string into ClientCaps dataclass.
Args:
ttype3: String from writer.get_extra_info("ttype3"), e.g., "MTTS 2825"
Returns:
ClientCaps with parsed capabilities. Returns defaults for
None/empty/invalid input.
Bit values from MTTS spec (tintin.mudhalla.net/protocols/mtts/):
Bit 0 (1) = ANSI
Bit 1 (2) = VT100
Bit 2 (4) = UTF-8
Bit 3 (8) = 256 COLORS
Bit 4 (16) = MOUSE TRACKING
Bit 5 (32) = OSC COLOR PALETTE
Bit 6 (64) = SCREEN READER
Bit 7 (128) = PROXY
Bit 8 (256) = TRUECOLOR
Bit 9 (512) = MNES
Bit 10 (1024) = MSLP
Bit 11 (2048) = SSL
"""
if not ttype3:
return ClientCaps()
parts = ttype3.split()
if len(parts) != 2 or parts[0] != "MTTS":
return ClientCaps()
try:
value = int(parts[1])
except ValueError:
return ClientCaps()
return ClientCaps(
ansi=bool(value & 1),
vt100=bool(value & 2),
utf8=bool(value & 4),
colors_256=bool(value & 8),
mouse_tracking=bool(value & 16),
osc_color_palette=bool(value & 32),
screen_reader=bool(value & 64),
proxy=bool(value & 128),
truecolor=bool(value & 256),
mnes=bool(value & 512),
mslp=bool(value & 1024),
ssl=bool(value & 2048),
)