Host-managed I/O
InMemoryTerminalSession connects Ghostty’s host-managed backend to your own process, emulator, or command engine. The host supplies output with receive, and Ghostty sends user input through the write closure.
Create a session
let session = InMemoryTerminalSession(
write: { data in
backend.handleInput(data)
},
resize: { viewport in
backend.resize(columns: viewport.columns, rows: viewport.rows)
}
)
terminal.configuration = TerminalSurfaceOptions(
backend: .inMemory(session)
)
Send host output
Use UTF-8 strings for simple demos and Data for exact byte streams.
session.receive("hello from host\r\n")
session.receive(Data([0x1B, 0x5B, 0x32, 0x4A])) // ESC [ 2 J
Replay history
Bytes the terminal has already seen once — a snapshot restored into a new surface, the backlog of a session you reattach to — go through receiveReplay. They are parsed like any output and keep their place among the receive calls around them, but the queries in them (device attributes, cursor and status reports, OSC 10/11) are not answered a second time, so nothing is typed into the shell. Pass a whole screen in one call. A replay received before the surface attaches waits for it and is not subject to the 1 MiB cap on early output.
session.receiveReplay(snapshotBytes)
session.receive(liveBytes)
If the replay replaces the whole screen, output still queued ahead of it is wasted work: discardPendingOutput() drops whatever the terminal has not started parsing and returns the byte count. Only call it when you hold the terminal’s state elsewhere and are about to replay it; the dropped bytes are gone, and queries in them are never answered.
session.discardPendingOutput()
session.receiveReplay(snapshotBytes)
Read viewport text
readViewportText() returns the visible grid as a UTF-8 string. It reads viewport rows and separates them with \n.
if let text = session.readViewportText() {
print(text)
}
Finish a process
Tell Ghostty that the host-managed process has exited so lifecycle delegates and shell integration receive the close event.
session.finish(exitCode: 0, runtimeMilliseconds: 1234)