Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/jossephus/chuchu/llms.txt

Use this file to discover all available pages before exploring further.

Chuchu is built in three layers that each own a distinct concern: the Android UI layer renders terminal output and collects user input, the service layer manages connection state and session lifecycle, and the native Zig layer handles the actual SSH protocol and terminal emulation through libghostty and libssh2. Data flows down through JNI bridges and surfaces back up as immutable snapshots that Compose can render without synchronization overhead.

Layer overview

Android UI layer (Kotlin + Jetpack Compose)

The UI layer is built entirely with Jetpack Compose. Key screens include TerminalScreen for active sessions, ServerListScreen for managing saved hosts, and the settings screens for themes and SSH keys. Compose components observe StateFlow values exposed by the service layer and re-render reactively when session state changes.

Service layer (Kotlin)

The service layer sits between the UI and the native code. It owns session state, drives connection lifecycles, and handles reconnection logic.
ComponentResponsibility
TerminalSessionEngineCentral state machine — transitions between Disconnected, Connecting, Connected, Reconnecting, and Error states; coordinates the SSH or Mosh service
NativeSshServiceDrives the libssh2 SSH connection: opens the socket, authenticates (password or public key), launches the remote PTY, and pipes data through the IPC bridge
NativeMoshServiceManages the Mosh UDP session after the SSH bootstrap phase completes
GhosttyBridgeJNI wrapper around libghostty — creates and destroys terminal sessions, encodes keystrokes and mouse events, takes terminal snapshots

Native layer (Zig JNI)

The native layer is compiled from the zig-src/ directory into libchuchu_jni.so. It is a single shared library that bundles libssh2, libghostty (as the ghostty-vt module), zigimg, and the Mosh client.
Source filePurpose
src/bridge/chuchu_ssh.zigSSH connection management via libssh2: socket setup, authentication, channel I/O, SFTP upload
src/bridge/chuchu_mosh.zigMosh UDP session, driven after SSH bootstrap
src/bridge/zignal_png.zigImage rendering — decodes PNG data and feeds it into libghostty’s kitty image protocol support
src/bridge/ipc.zigBinary framing protocol shared between Kotlin and Zig

Data flow

Input path

User keystrokes and mouse events enter TerminalScreen and are forwarded to TerminalSessionEngine. The engine calls into GhosttyBridge to encode the event into the correct escape sequence using nativeEncodeKey or nativeEncodeMouse. The encoded bytes are drained from the libghostty write buffer via nativeDrainPtyWrites and sent over the SSH channel through NativeSshService.
User input
  → TerminalScreen (Compose)
  → TerminalSessionEngine
  → GhosttyBridge.nativeEncodeKey / nativeEncodeMouse
  → GhosttyBridge.nativeDrainPtyWrites
  → NativeSshService → libssh2 channel write

Output path

Data arriving from the remote server travels the reverse direction. NativeSshService reads bytes from the libssh2 channel and passes them to GhosttyBridge.nativeWriteRemote, which feeds them into libghostty’s VT parser. Compose calls nativeSnapshot on each frame to retrieve an immutable TerminalSnapshot and renders it.
libssh2 channel read
  → GhosttyBridge.nativeWriteRemote (VT parse + render)
  → GhosttyBridge.nativeSnapshot → TerminalSnapshot
  → TerminalScreen (Compose UI render)

Key components

GhosttyBridge

GhosttyBridge is the JNI wrapper for libghostty terminal sessions. It loads libchuchu_jni.so at startup and exposes the following operations:
MethodDescription
nativeCreate(cols, rows, maxScrollback)Allocates a new terminal session; returns a handle (Long)
nativeDestroy(handle)Frees the session and all associated memory
nativeWriteRemote(handle, data)Feeds raw bytes from the server into the VT parser
nativeSnapshot(handle)Returns a ByteBuffer containing the current terminal cell state
nativeEncodeKey(handle, key, cp, mods, action, utf8)Encodes a keystroke into the appropriate escape sequence
nativeEncodeMouse(handle, ...)Encodes a mouse event
nativeDrainPtyWrites(handle)Returns bytes that the terminal wants to send back to the server (responses to escape sequences)
nativeSnapshotImages(handle)Returns kitty protocol image data for in-terminal image rendering

IPC binary framing

The ipc.zig module defines a compact 6-byte framing protocol used to pass messages between the Kotlin and Zig sides of the JNI boundary.
┌─────────┬─────────┬──────────────┐
│ version │   tag   │   length     │   payload...
│  1 byte │  1 byte │   4 bytes    │
└─────────┴─────────┴──────────────┘
Tag valueMeaning
Write (1)Send data to the remote PTY
Read (2)Request data from the remote PTY
Ack (100)Acknowledge a message
Data (101)Carry a data payload
Error (255)Signal an error condition

Session state machine

TerminalSessionEngine manages session state through the SessionStatus enum. Transitions are driven by connection outcomes and reconnect logic.
Disconnected

    ▼ connect()
Connecting
    │                    ╲ failure
    ▼ success             ▼
Connected            Reconnecting
    │                    │
    ▼ disconnect()        ▼ max retries exceeded
Disconnected         Error

Transport routing

The Transport enum in the session model determines which service handles the connection:
TransportHandler
SSHNativeSshService (direct libssh2)
TailscaleSSHNativeSshService (same path, Tailscale resolves the address)
MoshSSH bootstrap first, then NativeMoshService over UDP

Mosh bootstrap flow

Mosh sessions start over SSH. The engine connects to the server via NativeSshService, runs mosh-server new on the remote, parses the MOSH CONNECT response line to extract the UDP port and session key, closes the SSH channel, then opens a UDP connection through NativeMoshService.
SSH connect → run mosh-server → parse MOSH CONNECT → close SSH → open UDP Mosh

Room database

Persistent data is stored in a Room database. Two main entities are used:
  • HostProfile — stores server connection details (host, port, username, transport, auth method)
  • SshKey — stores SSH private key material for public-key authentication
The Zig native layer is compiled with -Doptimize=ReleaseSmall for production builds, which applies size-focused optimizations and strips debug symbols. All heap allocations in the Zig layer use std.heap.c_allocator, which delegates to the system malloc/free provided by the Android libc.

Build docs developers (and LLMs) love