Documentation
¶
Overview ¶
Package windrunner is a persistent terminal session engine: it owns pseudo-terminals, runs an authoritative VT emulator for each one, and hands exact state snapshots plus live byte streams to any number of attached clients.
The engine is deliberately unopinionated. It knows processes, terminals, and opaque metadata — never panes, layouts, agents, or rendering. Products are built on top of it, not inside it.
Index ¶
- Constants
- type AttachOptions
- type Engine
- type Event
- type EventSubscription
- type EventType
- type Message
- type Resize
- type Session
- func (s *Session) Alive() bool
- func (s *Session) Attach(buffer int) (Snapshot, *Subscription)
- func (s *Session) AttachWith(options AttachOptions) (Snapshot, *Subscription)
- func (s *Session) Done() <-chan struct{}
- func (s *Session) ExitCode() int
- func (s *Session) ID() string
- func (s *Session) Kill() error
- func (s *Session) Metadata() map[string]string
- func (s *Session) Peer() bool
- func (s *Session) Resize(cols, rows int) error
- func (s *Session) SetMetadata(metadata map[string]string)
- func (s *Session) Size() (cols, rows int)
- func (s *Session) Snapshot() Snapshot
- func (s *Session) Title() string
- func (s *Session) Write(p []byte) (int, error)
- type Snapshot
- type SpawnSpec
- type Subscription
Constants ¶
const DefaultIdleAfter = 2 * time.Second
DefaultIdleAfter is how long a session's terminal must stay quiet before it is reported idle, when SpawnSpec does not say otherwise.
const DefaultScrollback = 2000
DefaultScrollback bounds each session's emulator history when SpawnSpec does not say otherwise.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type AttachOptions ¶
type AttachOptions struct {
// Buffer is the subscription's message buffer; 0 means 64.
Buffer int
// Resync trades byte-exact replay for staying attached. A subscriber
// that fills its buffer is normally dropped (see Lagged); one that
// asked to resync instead has its backlog replaced by a Message
// carrying the terminal's exact state at the moment it fell behind,
// and keeps streaming from there.
//
// This is the right trade for a viewer and the wrong one for a
// recorder: a replica that repaints ends up correct, while a transcript
// with a hole in it does not. Watching clients — a browser terminal,
// a wall of live panes — should ask for it; anything that must see
// every byte should not.
Resync bool
}
AttachOptions configures a subscription. The zero value is the historical behavior: the default buffer, dropped on lag.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is a registry of live sessions. It can be embedded directly in a process, or served over a socket by the server package; the API is the same either way.
func (*Engine) Close ¶
func (e *Engine) Close()
Close removes every session, ends every event subscription, and refuses further spawns.
func (*Engine) Remove ¶
Remove ends a session completely: process, PTY, terminal state, and subscribers. This is the only way session state is discarded — exit alone keeps it attachable.
func (*Engine) Spawn ¶
Spawn starts a process on a PTY of its own and begins recording its terminal state.
func (*Engine) Subscribe ¶
func (e *Engine) Subscribe(buffer int) *EventSubscription
Subscribe returns a feed of engine events from this moment on. A subscriber that stops reading is dropped, not waited for (see EventSubscription.Lagged). For any one session, its spawned event is published before every other event about it; beyond that, ordering across concurrent transitions is best-effort.
type Event ¶
type Event struct {
Type EventType
SessionID string
Command string // EventSpawned
ExitCode int // EventExited
}
Event is one entry in the engine's pub/sub stream: a session appeared, went quiet, spoke again, exited, or was removed. Structured signals for coordination — richer than poke-and-peek, still not byte-splicing.
type EventSubscription ¶
type EventSubscription struct {
// contains filtered or unexported fields
}
EventSubscription is one reader of the engine's event stream.
func (*EventSubscription) Close ¶
func (sub *EventSubscription) Close()
Close ends the subscription. Safe to call at any time, any number of times.
func (*EventSubscription) Events ¶
func (sub *EventSubscription) Events() <-chan Event
Events yields engine events. The channel closes when the subscription is closed, the engine closes, or the reader falls too far behind.
func (*EventSubscription) Lagged ¶
func (sub *EventSubscription) Lagged() bool
Lagged reports whether the subscription was dropped for reading too slowly. The recovery is to subscribe again and re-list sessions: fresh state is always cheaper than an unbounded backlog.
type EventType ¶
type EventType string
EventType names one kind of daemon-observed change.
const ( EventSpawned EventType = "spawned" EventExited EventType = "exited" EventRemoved EventType = "removed" // EventIdle and EventBusy report output activity: a session is busy // while its terminal is producing output and idle once it has been // quiet for its idle window. Sessions start busy; the first event is // always an idle. EventIdle EventType = "idle" EventBusy EventType = "busy" )
type Message ¶
type Message struct {
Bytes []byte
Resize *Resize
// Resync, when set, supersedes everything the subscriber had not read
// yet: the reader fell a whole buffer behind, so the backlog was
// dropped in favor of this exact state (see AttachOptions.Resync).
// Bytes is nil and later messages continue from here, so a replica
// applies it by replacing itself wholesale rather than appending.
//
// Its ANSI is read-only: one serialization is shared by every
// subscriber owed state at that moment, so rewriting it in place
// corrupts the others. Copy before modifying.
Resync *Snapshot
}
Subscription is one attached reader of a session's output stream. Message is one delivery on a subscription: terminal output bytes, and — when the session's terminal moved under the subscriber — the new size, carried with the repaint those bytes hold so a replica resizes and repaints as one ordered step.
type Resize ¶
type Resize struct {
Cols, Rows int
}
Resize is a terminal size change riding the stream.
type Session ¶
type Session struct {
// contains filtered or unexported fields
}
Session is one process on one PTY, with the authoritative record of what its terminal looks like. All methods are safe for concurrent use.
func (*Session) Attach ¶
func (s *Session) Attach(buffer int) (Snapshot, *Subscription)
Attach returns the session's state right now and a subscription that carries every output byte after it — atomically, so nothing falls in the gap between snapshot and stream. Shorthand for AttachWith of a buffer.
func (*Session) AttachWith ¶
func (s *Session) AttachWith(options AttachOptions) (Snapshot, *Subscription)
AttachWith is Attach with a policy: see AttachOptions.
func (*Session) Done ¶
func (s *Session) Done() <-chan struct{}
Done closes when the child exits. The session's terminal state remains attachable afterward until Close.
func (*Session) Peer ¶
Peer reports whether the session opted into peer input at spawn. Immutable for the session's life — a guardrail that could be flipped after the fact would not be one.
func (*Session) Resize ¶
Resize moves the PTY and the emulator together; the child learns via SIGWINCH (and in-band reports, if it asked for them).
func (*Session) SetMetadata ¶
SetMetadata replaces the session's metadata wholesale.
type Snapshot ¶
Snapshot is a session's exact terminal state, serialized. Writing ANSI to a fresh Cols x Rows terminal reproduces the scrollback, the screen, and the cursor position.
type SpawnSpec ¶
type SpawnSpec struct {
Command string
Args []string
Dir string
// Env is the child's environment; nil inherits the engine process's.
// TERM is set to xterm-256color unless the caller provides one — the
// child must describe output for the terminal windrunner actually
// emulates. WINDRUNNER_SESSION is always this session's own ID, and
// any inherited one is replaced, so a session's process can name
// itself to the control plane and cannot name someone else.
Env []string
// EnvOverride is applied on top of Env — or on top of the inherited
// environment when Env is nil — replacing any entry that names the
// same variable. It is how a caller adds a few variables without
// having to supply the whole environment, which a client on another
// machine cannot meaningfully do: its own environ describes a
// different host, and replacing the daemon's would leave the child
// without PATH, HOME, or anything else it was going to need.
EnvOverride []string
Cols, Rows int
// Scrollback caps emulator history in lines; 0 means
// DefaultScrollback.
Scrollback int
// IdleAfter is the quiet window behind idle/busy events: no output for
// this long means idle. 0 means DefaultIdleAfter.
IdleAfter time.Duration
// Peer opts the session into peer input: writes to its stdin that
// arrive without an attachment, the way automation and other sessions
// speak. The engine only stores the bit — Write never checks it; the
// server package refuses the detached input op for sessions that did
// not opt in.
Peer bool
// Metadata travels with the session verbatim. The engine stores and
// returns it; it never reads it.
Metadata map[string]string
}
SpawnSpec describes a session to start. Cols and Rows are required; the zero values of everything else are usable defaults.
type Subscription ¶
type Subscription struct {
// contains filtered or unexported fields
}
func (*Subscription) Close ¶
func (sub *Subscription) Close()
Close detaches the subscription. Safe to call at any time, any number of times.
func (*Subscription) Lagged ¶
func (sub *Subscription) Lagged() bool
Lagged reports whether the subscription was dropped for reading too slowly. The recovery is to attach again: a fresh snapshot is always cheaper than an unbounded backlog. A subscription that asked to resync is never dropped for lag, so this is always false for one.
func (*Subscription) Output ¶
func (sub *Subscription) Output() <-chan Message
Output yields the stream's messages in terminal order. The channel closes when the session ends, the subscription is closed, or the reader falls too far behind (see Lagged).
Directories
¶
| Path | Synopsis |
|---|---|
|
Package client speaks the wire protocol to a windrunner daemon: control calls over one connection, attachments over one dedicated connection each.
|
Package client speaks the wire protocol to a windrunner daemon: control calls over one connection, attachments over one dedicated connection each. |
|
cmd
|
|
|
windrunner
command
Command windrunner is the reference client and daemon: persistent terminal sessions you can create, list, and attach to — a minimal screen alternative that doubles as a demo of the library.
|
Command windrunner is the reference client and daemon: persistent terminal sessions you can create, list, and attach to — a minimal screen alternative that doubles as a demo of the library. |
|
Package server exposes an Engine over a listener, speaking the wire protocol.
|
Package server exposes an Engine over a listener, speaking the wire protocol. |
|
Package wire is windrunner's socket protocol: length-prefixed frames over a unix socket.
|
Package wire is windrunner's socket protocol: length-prefixed frames over a unix socket. |