Documentation
¶
Overview ¶
Package plugin is AutoDoc's plugins (ADR 0209, ADR 1791268009): the protocol between the TUI and a plugin it runs, and the SDK a plugin is written with.
A dialog plugin is a program the TUI starts when its Plugins menu entry is chosen. The TUI draws a dialog over the page and fills it with the plugin's frames; every key but Esc goes to the plugin, and Esc closes it. A non-modal one (a card) sits beside the page and takes the keys only when focused; a service has no surface at all. The two speak msgpack-RPC NOTIFICATIONS, both ways, over the plugin's stdin and stdout — never a request, which golib's RPC client cannot take:
host → plugin plugin.open {protocol, width, height, theme} the first, once; a service's has no size
plugin.key {key, text, ctrl, alt, shift}
plugin.pointer {kind, button, x, y, count} 6: a frame's left press or wheel
plugin.resize {width, height}
plugin.theme {theme}
plugin.hide {}, plugin.show {} esc = "hide": hidden, shown again
plugin.focus {focused} 2: a card took or gave back the keys
plugin.document {path, workspace, text, 2: the open note, as [feed] declares
cursor, selection, version, too_large,
cursor_byte, selection_bytes} 4: the byte offsets
plugin.command {id} 2: one of the manifest's [[commands]]
plugin.command {id, document} 4: with the note as it stood, under [feed]
plugin.result {call, ok, error} 3: the answer to host.call
plugin.table.fetch {id, n} 4: up to n more rows of the table
plugin.table.close {id} 4: the table closed, or replaced
plugin.close {} the last
plugin → host host.ready {protocol} the answer to plugin.open
host.frame {rows} the whole dialog, any time
host.title {title}
host.call {call, id, args} 3: run a command of AutoDoc's catalog
host.table {id, title, fields, rows, more} 4: a table of rows, as [table] declares
host.table {…, kinds} 5: with each cell's kind
host.rows {id, rows, more} 4: the next rows, answering a fetch
host.rows {…, kinds} 5: with each cell's kind
host.table_end {id, error} 4: the table's rows ended early
host.close {}
The notifications marked 2 are protocol 2's, those marked 3 protocol 3's, those marked 4 protocol 4's. A host sends them only to a plugin whose manifest says that protocol (or a later one) and declares them, so a protocol-1 plugin never sees one. The fields marked 5 are read only from a plugin whose manifest says protocol 5. Pointer notifications go only to protocol-6 plugins.
A table is the host's to show: the plugin sends its fields and its first rows (host.table), and AutoDoc shows them in a results panel, asking for more as the user nears the end of those loaded (plugin.table.fetch), one request at a time until host.rows answers it. more says rows remain; host.table_end ends them early, with why. A newer host.table replaces the table, and the host says plugin.table.close for the one it replaced, as it does when the panel closes. Each table has an id of its own: one that reuses the shown table's id is refused, and host.rows no fetch asked for is dropped.
From protocol 5 a table's cells may carry their kinds: kinds is one string per row, a byte per field, each one of KindText, KindNull, KindNumber, KindBool and KindJSON. A NULL is then told from the text "NULL", and a number's text is its literal. The host sorts by them and writes them into JSON. A kinds string of the wrong length, or with another byte, makes its message malformed.
host.call and plugin.result are a request and its answer made of two notifications, matched by call, a number the plugin chooses and does not reuse while it waits. The host runs only the command ids the manifest's calls = [...] declares, and answers every call it accepts exactly once: ok, or error "<code> <message>" with the code refused (an id not declared, a plugin's command, or more than MaxPendingCalls waiting: "refused busy"), unknown (no command has the id) or failed (the command's own error). A call reusing a number still waiting is a protocol violation: the host logs it, runs nothing, and sends no result for it, so the waiting call's is the only one with that number. Peer.Call does all of this for a plugin in Go.
A card or dialog need not draw its cells by hand: plugin/card is a native kit of pages and controls (inputs, buttons, lists, trees), and plugin/decl runs a golib QML layout. Both draw in AutoDoc's look and theme (docs/plugins.md, "Drawing a card").
A plugin in Go writes a Handler and calls Serve; one in another language speaks the table above. The frame's colours are the themes' vocabulary: "default", an ANSI name ("brightwhite"), or "#rrggbb" — golib's style.ParseColor reads them, as it reads the theme the plugin is sent.
Index ¶
- Constants
- Variables
- func BufferFreeMsg(id int) map[string]any
- func BuffersMsg(gen int, specs []SurfaceBufferSpec) map[string]any
- func CallParams(c Call) []any
- func CaretParams(c Caret) []any
- func CheckKinds(kinds []string, rows, fields int) string
- func CheckMenu(u MenuUpdate) error
- func CheckNotice(n Notice) error
- func CheckSession(values map[string]string) error
- func ClipboardWriteParams(text string) []any
- func CloseParams(reason string) []any
- func CommandArgsParams(id string, args []string) []any
- func CommandDocParams(id string, d Document) []any
- func CommandParams(id string) []any
- func DocumentParams(d Document) []any
- func DocumentParamsAt(protocol int, d Document) []any
- func EmptyParams() []any
- func EncodedSize(method string, params []any) (int, error)
- func FetchParams(f TableFetch) []any
- func FileConn(r, w *os.File) net.Conn
- func FitCommand(id string, d Document) []any
- func FocusParams(focused bool) []any
- func FrameDoneMsg(d SurfaceFrameDone) map[string]any
- func FrameMsg(f SurfaceFrame) map[string]any
- func FrameParams(rows []Row) []any
- func KeyCode(name string) (rune, bool)
- func KeyName(code rune) string
- func KeyParams(k Key) []any
- func MenuParams(u MenuUpdate) []any
- func NotifyParams(n Notice) []any
- func OpenParams(o Open) []any
- func PasteParams(text string) []any
- func PointerParams(p Pointer) []any
- func PointerShapeParams(shape string) []any
- func ReadBufferFreeMsg(m map[string]any) (int, error)
- func ReadClipboardWrite(params []any) (string, error)
- func ReadClose(params []any) string
- func ReadCommand(params []any) (string, error)
- func ReadCommandArgs(params []any) []string
- func ReadFocus(params []any) (bool, error)
- func ReadPaste(params []any) (string, error)
- func ReadPointerShape(params []any) (string, error)
- func ReadReady(params []any) (int, error)
- func ReadResize(params []any) (w, h int, err error)
- func ReadRows(params []any) (id string, rows [][]string, more bool, dropped int, err error)
- func ReadSession(params []any) (map[string]string, error)
- func ReadTableEnd(params []any) (id, why string, err error)
- func ReadTableGone(params []any) (string, error)
- func ReadTitle(params []any) (string, error)
- func ReadyParams(protocol int) []any
- func ResizeParams(w, h int) []any
- func ResultParams(r Result) []any
- func RowsKindsParams(id string, rows [][]string, kinds []string, more bool) []any
- func RowsParams(id string, rows [][]string, more bool) []any
- func Serve(ctx context.Context, h Handler) error
- func ServeConn(ctx context.Context, conn net.Conn, h Handler) error
- func SessionParams(values map[string]string) []any
- func SurfaceParams(s SurfaceInfo) []any
- func SurfacePointerParams(p SurfacePointer) []any
- func TableEndParams(id, why string) []any
- func TableGoneParams(id string) []any
- func TableParams(t TableOpen) []any
- func ThemeParams(t Theme) []any
- func TitleParams(title string) []any
- func WireOp(m map[string]any) string
- func WithArgs(params []any, args []string) []any
- type Base
- type Call
- type CallError
- type Caret
- type CommandArgser
- type Commander
- type Document
- type DocumentReader
- type FDGuard
- type Focuser
- type Frame
- type Handler
- type Hider
- type Key
- type Link
- type MenuUpdate
- type Notice
- type Open
- type Paster
- type Peer
- func (p *Peer) Call(id string, args ...string) (bool, error)
- func (p *Peer) Close() error
- func (p *Peer) CloseReason() string
- func (p *Peer) Frame(f *Frame) error
- func (p *Peer) MenuRow(id string, title *string, enabled *bool) error
- func (p *Peer) Notify(n Notice) error
- func (p *Peer) Rows(id string, rows [][]string, more bool) error
- func (p *Peer) RowsKinds(id string, rows [][]string, kinds []string, more bool) error
- func (p *Peer) SetCaret(c Caret) error
- func (p *Peer) SetPointerShape(shape string) error
- func (p *Peer) SetSession(values map[string]string) error
- func (p *Peer) Table(t TableOpen) error
- func (p *Peer) TableEnd(id string, err error) error
- func (p *Peer) Title(title string) error
- func (p *Peer) WriteClipboard(text string) error
- type Pointer
- type PointerHandler
- type Position
- type Range
- type Result
- type Row
- type Run
- type Style
- type Surface
- type SurfaceBuffer
- type SurfaceBufferSpec
- type SurfaceConn
- type SurfaceFrame
- type SurfaceFrameDone
- type SurfaceHandler
- type SurfaceInfo
- type SurfaceKind
- type SurfacePointer
- type TableFetch
- type TableFetcher
- type TableOpen
- type TableRows
- type Theme
Constants ¶
const ( MaxNoticeBytes = 1024 // the message, in bytes of UTF-8 MaxNoticeIDBytes = 64 // the id: letters, digits, '.', '_' and '-' MaxActiveNotices = 16 // ongoing ids one run of a plugin may hold at once )
The bounds of a notice: past any, host.notify is refused and nothing shows.
const ( MinProtocol = 1 Protocol = 11 // 11: [[menu]] and host.menu (ADR 1792991184); 10: [surface] (ADR 1792797354) )
The plugin protocol's versions this SDK speaks. A host speaks to each plugin the one its manifest says, and Serve answers any from MinProtocol to Protocol with that same one. The protocol rises when a manifest can declare something an older host must not run without, for a changed meaning, or for something a plugin must handle (ADR 1791268009 §1).
const ( MethodOpen = "plugin.open" MethodKey = "plugin.key" MethodPointer = "plugin.pointer" // protocol 6: a plugin view's left press or vertical wheel MethodResize = "plugin.resize" MethodTheme = "plugin.theme" MethodClose = "plugin.close" // MethodHide and MethodShow are a dialog whose manifest says esc = "hide": Esc hid it, and // its Plugins menu entry showed it again. A plugin may ignore them; it keeps running either way. MethodHide = "plugin.hide" MethodShow = "plugin.show" // MethodFocus is a non-modal card focused (a click, or its command's key) or given back (Esc). MethodFocus = "plugin.focus" // MethodDocument is the open note, to a plugin whose manifest declares [feed] document = true. MethodDocument = "plugin.document" // MethodCommand is one of the manifest's [[commands]], chosen. MethodCommand = "plugin.command" // MethodResult is the host's answer to a host.call, by its call number. MethodResult = "plugin.result" // MethodFetch asks for up to n more rows of the table id (protocol 4). The host sends another // only once host.rows (or host.table_end) has answered it. MethodFetch = "plugin.table.fetch" // MethodTableGone is the table id closed: its panel closed, or a newer table replaced it. The // plugin frees what serves its rows (a database cursor, say). MethodTableGone = "plugin.table.close" )
The notifications the host sends.
const ( MethodReady = "host.ready" MethodFrame = "host.frame" MethodTitle = "host.title" MethodHostClose = "host.close" // MethodCall asks the host to run a command of its catalog, one the manifest's calls declares. MethodCall = "host.call" // MethodSession replaces the plugin's session slot (protocol 7, session.go): kept by the host // in memory until AutoDoc quits, and handed back in each plugin.open. MethodSession = "host.session" // MethodTable shows a table (protocol 4): its id, title, fields and first rows, and whether // more remain. It replaces the table shown, any plugin's. MethodTable = "host.table" // MethodRows is the table's next rows, answering plugin.table.fetch, and whether more remain. MethodRows = "host.rows" // MethodTableEnd is the table's rows ended before the last was sent: no more will come, and // error, when set, says why. MethodTableEnd = "host.table_end" )
The notifications the plugin sends.
const ( // MaxPendingCalls is how many of a plugin's calls the host keeps waiting; one more is answered // "refused busy". MaxPendingCalls = 64 // CallTimeout is how long Peer.Call waits for the host's answer. CallTimeout = 10 * time.Second )
The bounds of host.call (protocol 3).
const ( CallRefused = "refused" // not declared, a plugin's command, or too many waiting (busy) CallUnknown = "unknown" // no command has the id CallFailed = "failed" // the command's own error )
The codes of a plugin.result's error.
const ( MaxRows = 500 MaxRunsPerRow = 1000 )
Limits on what the host reads of a frame: past them it is clipped.
const ( KindText byte = 't' KindNull byte = 'z' // the cell is NULL; its text is what shows (the plugin's "NULL", say) KindNumber byte = 'n' // the cell's text is the number's literal KindBool byte = 'b' // the cell's text is true or false KindJSON byte = 'j' // the cell's text is a JSON value )
The kinds of a table's cells (protocol 5), one byte each: golib's widget.TableCellKind bytes.
const ( MaxSessionKeys = 32 MaxSessionValue = 8 << 10 // bytes, each value MaxSessionBytes = 64 << 10 // keys and values together )
The bounds of a session slot: past any, host.session is refused and the slot kept as it was.
const ( CloseQuit = "quit" // AutoDoc is quitting: the slot goes with it CloseOff = "off" // the user turned the plugin off; the slot stays for its next start CloseUpdate = "update" // its new build is about to start; the slot stays CloseRemove = "remove" // it is being removed: the slot is dropped CloseFailure = "failed" // the host stopped it for a fault (no answer, a wrong message) CloseClosed = "closed" // closed as a dialog closes: Esc on a modal one, or its own host.close )
The reasons a plugin.close gives (protocol 7). A plugin reads one with Peer.CloseReason inside Close. It is best-effort: a plugin that never finished opening, blocks, or has died is stopped without one.
const ( // MethodSurface is a generation of the surface: its size in dp, the device scale and the frame // kinds the window draws. It follows plugin.open, and comes again at each resize, scale or caps // change; a host without pixels never sends it. MethodSurface = "plugin.surface" // MethodSurfacePointer is the pointer on a surface, in dp: it replaces plugin.pointer there. MethodSurfacePointer = "plugin.surface.pointer" // MethodPaste is the user's paste chord on a focused surface. MethodPaste = "plugin.paste" // MethodCaret is where the surface's text caret is, for the host's IME: it draws the preedit there // and places the candidate window by it. MethodCaret = "host.surface.caret" // MethodPointerShape is the pointer's shape over the surface. MethodPointerShape = "host.surface.cursor" // MethodClipboardWrite writes text to the clipboard. There is no read: a read is the user's paste. MethodClipboardWrite = "host.clipboard.write" )
The notifications of protocol 10.
const ( MaxSurfaceDatagram = 2048 // under macOS's AF_UNIX SOCK_DGRAM default, net.local.dgram.maxdgram MaxSurfaceFDs = 4 MaxPoolBuffers = 3 // one shown, one pending, one being drawn )
The side channel's limits.
const ( OpBuffers = "buffers" // host → plugin: a generation's pool, an fd per buffer OpBufferAdd = "buffer.add" // plugin → host: a DMA-BUF it exported, an fd per plane OpBufferDrop = "buffer.remove" OpSurfaceAck = "surface.ack" // plugin → host: it holds a generation's plugin.surface and buffers OpFrame = "frame" // plugin → host: present a buffer OpFrameDone = "frame.done" // host → plugin: that frame's outcome OpBufferFree = "buffer.free" // host → plugin: the buffer may be drawn into again OpBufferRefused = "buffer.refused" // host → plugin: a buffer.add not taken ("budget", "size"); not a violation )
The ops.
const MaxDocumentBytes = MaxMessageBytes
MaxDocumentBytes bounds a whole plugin.document as the link encodes it. It is the link's own limit: EncodedSize measures the notification exactly as the link writes it, envelope and all, and the link takes a message of MaxMessageBytes and refuses one byte more. A note whose document is larger is sent as too large, without its text (ADR 1791268009 §2.3).
const MaxMenuTitleBytes = 256
MaxMenuTitleBytes bounds a row's title, in bytes of UTF-8.
const MaxMessageBytes = 1 << 20
MaxMessageBytes bounds one notification either way: a frame of a dialog is a few kilobytes.
const MaxSurfaceKinds = 32
MaxSurfaceKinds bounds plugin.surface's kinds: a window draws a handful.
const MethodMenu = "host.menu"
MethodMenu sets one of the plugin's menu rows (protocol 11).
const MethodNotify = "host.notify"
MethodNotify posts a notification (protocol 8).
const SurfaceFDEnv = "AUTODOC_PLUGIN_SURFACE_FD"
SurfaceFDEnv names the side channel's fd in the plugin's environment.
const WriteTimeout = 2 * time.Second
WriteTimeout bounds one send: a peer that has not read for this long is not answering.
Variables ¶
var ( // ErrClosed is a call made, or waiting, when the link is gone. ErrClosed = errors.New("plugin: the link to the host is closed") // ErrCallTimeout is a call the host did not answer within CallTimeout. Its answer, if it comes, // is dropped. ErrCallTimeout = errors.New("plugin: the host did not answer the call in time") // ErrNoCalls is a call to a host that opened the plugin at a protocol before 3, which has no // host.call. ErrNoCalls = errors.New("plugin: the host speaks a protocol before 3, without host.call") // ErrNoTables is a table sent to a host that opened the plugin at a protocol before 4, which has // no host.table. ErrNoTables = errors.New("plugin: the host speaks a protocol before 4, without tables") )
The errors of Peer.Call besides the host's own answer (a *CallError).
var ErrMenu = errors.New("plugin: a menu row update refused")
ErrMenu is a host.menu the protocol refuses: CheckMenu and ReadMenu say which rule.
var ErrNoMenu = errors.New("plugin: the host speaks a protocol before 11, without menu rows")
ErrNoMenu is a menu row update sent to a host speaking a protocol before 11.
var ErrNoNotify = errors.New("plugin: the host speaks a protocol before 8, without notifications")
ErrNoNotify is a notice sent to a host speaking a protocol before 8.
var ErrNoSession = errors.New("plugin: the host speaks a protocol before 7, without a session slot")
ErrNoSession is a slot set on a host that opened the plugin at a protocol before 7.
var ErrNoSurface = errors.New("plugin: the host gave no pixel surface")
ErrNoSurface is a surface call on a host that gave no pixel surface (protocol 10, [surface]).
var ErrNotice = errors.New("plugin: a notice refused")
ErrNotice is a notice the protocol refuses: CheckNotice and ReadNotice say which rule. Their errors never hold the message.
var ErrParams = errors.New("plugin: malformed parameters")
ErrParams is a notification whose parameters are not the protocol's.
var ErrSessionBounds = errors.New("plugin: a session slot over its bounds")
ErrSessionBounds is a slot over MaxSessionKeys, MaxSessionValue or MaxSessionBytes.
var ErrSurfaceClosed = errors.New("plugin: the side channel is closed")
ErrSurfaceClosed is a side channel whose peer went, or that was closed.
var ErrSurfaceWire = errors.New("plugin: malformed side-channel message")
ErrSurfaceWire is a malformed side-channel message: a violation, which closes the plugin.
Functions ¶
func BufferFreeMsg ¶ added in v0.2.5
BufferFreeMsg is the buffer.free op.
func BuffersMsg ¶ added in v0.2.5
func BuffersMsg(gen int, specs []SurfaceBufferSpec) map[string]any
BuffersMsg is the buffers op: specs[i] is the buffer whose fd is the datagram's i-th.
func CaretParams ¶ added in v0.2.5
CaretParams are host.surface.caret's; the zero Caret sends {}, no caret.
func CheckKinds ¶ added in v0.1.54
CheckKinds is why kinds cannot be the kinds of rows of a table of fields fields ("" when they can): one string per row, each exactly fields bytes, every byte a kind.
func CheckMenu ¶ added in v0.3.1
func CheckMenu(u MenuUpdate) error
CheckMenu is nil when u is an update the protocol takes.
func CheckNotice ¶ added in v0.1.59
CheckNotice is nil when n is a notice the protocol takes.
func CheckSession ¶ added in v0.1.56
CheckSession is nil when values fit a slot's bounds.
func ClipboardWriteParams ¶ added in v0.2.5
ClipboardWriteParams are host.clipboard.write's.
func CloseParams ¶ added in v0.1.56
CloseParams are plugin.close's: its reason from protocol 7, none before.
func CommandArgsParams ¶ added in v0.1.56
CommandArgsParams are plugin.command's with arguments (protocol 7).
func CommandDocParams ¶ added in v0.1.52
CommandDocParams are plugin.command's with the document it runs on (protocol 4): the note as it stood when the command was chosen, carried inside the command so no queue can drop it as it drops a waiting plugin.document (FitCommand sizes it).
func CommandParams ¶ added in v0.1.21
CommandParams are plugin.command's.
func DocumentParams ¶ added in v0.1.21
DocumentParams are plugin.document's as protocols 2 and 3 have it, without the byte offsets. A TooLarge one carries its path, workspace and version alone.
func DocumentParamsAt ¶ added in v0.1.52
DocumentParamsAt are plugin.document's as a plugin speaking protocol is sent it: from protocol 4 on with cursor_byte and selection_bytes, so a plugin written for an earlier one sees what it always has.
func EncodedSize ¶ added in v0.1.21
EncodedSize is the size of the notification method with params, as the link writes it.
func FetchParams ¶ added in v0.1.52
func FetchParams(f TableFetch) []any
FetchParams are plugin.table.fetch's.
func FileConn ¶
FileConn is a net.Conn over two files: what the link reads from and what it writes to. The host makes it from its ends of the pipes it starts a plugin with, and Serve from the plugin's stdin and stdout.
golib's rpc.Client bounds every write with a deadline and refuses a connection that cannot take one, so the files must be pollable: os.Pipe's are, and Serve makes stdin and stdout so (stdio_unix.go). Close closes both.
func FitCommand ¶ added in v0.1.52
FitCommand is plugin.command id carrying d (protocol 4), as it can be sent. The document is sized INSIDE the command, the whole notification as the link writes it, since a document that fits a plugin.document of its own can pass the link's limit once the command's envelope is added: one that does not fit goes as its too-large form, so the link never refuses the command, and the plugin learns the note was too large to send rather than running on a stale copy.
func FocusParams ¶ added in v0.1.21
FocusParams are plugin.focus's.
func FrameDoneMsg ¶ added in v0.2.5
func FrameDoneMsg(d SurfaceFrameDone) map[string]any
FrameDoneMsg is the frame.done op.
func FrameMsg ¶ added in v0.2.5
func FrameMsg(f SurfaceFrame) map[string]any
FrameMsg is the frame op.
func FrameParams ¶
FrameParams are host.frame's. A run's style keys are left out when they are their zero value.
func KeyCode ¶ added in v0.1.55
KeyCode is the golib tui key code of a key the protocol names ("Up", "F5"); false for a name it does not have.
func KeyName ¶ added in v0.1.55
KeyName is the protocol's name for the golib tui key code ("Up", "F5"); "" for a key with no name of its own (a printable key is sent as its text).
func NotifyParams ¶ added in v0.1.59
NotifyParams are host.notify's.
func OpenParams ¶
OpenParams are plugin.open's parameters. A service's (a size of 0 by 0) has no width or height.
func PasteParams ¶ added in v0.2.5
PasteParams are plugin.paste's.
func PointerParams ¶ added in v0.1.54
PointerParams are plugin.pointer's coordinates in the plugin view's frame.
func PointerShapeParams ¶ added in v0.2.5
PointerShapeParams are host.surface.cursor's.
func ReadBufferFreeMsg ¶ added in v0.2.5
ReadBufferFreeMsg reads a buffer.free op.
func ReadClipboardWrite ¶ added in v0.2.5
ReadClipboardWrite reads host.clipboard.write.
func ReadCommand ¶ added in v0.1.21
ReadCommand reads plugin.command: the command's id.
func ReadCommandArgs ¶ added in v0.1.56
ReadCommandArgs reads plugin.command's arguments: nil when it has none.
func ReadPointerShape ¶ added in v0.2.5
ReadPointerShape reads host.surface.cursor.
func ReadResize ¶
ReadResize reads plugin.resize.
func ReadRows ¶ added in v0.1.52
ReadRows reads host.rows as protocol 4 has it: the table's id, its next rows and whether more remain; dropped counts the malformed rows and cells (readRows).
func ReadSession ¶ added in v0.1.56
ReadSession reads host.session: a fresh map, checked against the bounds. Its error never holds a value: a slot holds secrets.
func ReadTableEnd ¶ added in v0.1.52
ReadTableEnd reads host.table_end: the table's id, and why its rows ended ("" when no error).
func ReadTableGone ¶ added in v0.1.52
ReadTableGone reads plugin.table.close: the table's id.
func ResultParams ¶ added in v0.1.40
ResultParams are plugin.result's; error only when it did not run.
func RowsKindsParams ¶ added in v0.1.54
RowsKindsParams are host.rows' with each row's kinds (protocol 5); nil kinds sends none.
func RowsParams ¶ added in v0.1.52
RowsParams are host.rows'.
func Serve ¶
Serve speaks the protocol on the plugin's stdin and stdout until the dialog closes, the host goes or ctx ends. It returns nil when the host closed the dialog. stdout is the protocol's: after Serve starts, os.Stdout is os.Stderr, which the host keeps in the plugin's log.
func SessionParams ¶ added in v0.1.56
SessionParams are host.session's.
func SurfaceParams ¶ added in v0.2.5
func SurfaceParams(s SurfaceInfo) []any
SurfaceParams are plugin.surface's.
func SurfacePointerParams ¶ added in v0.2.5
func SurfacePointerParams(p SurfacePointer) []any
SurfacePointerParams are plugin.surface.pointer's.
func TableEndParams ¶ added in v0.1.52
TableEndParams are host.table_end's; error only when there is one.
func TableGoneParams ¶ added in v0.1.52
TableGoneParams are plugin.table.close's.
func TableParams ¶ added in v0.1.52
TableParams are host.table's; kinds only when t has them.
Types ¶
type Base ¶ added in v0.1.21
type Base struct{}
Base is a Handler that does nothing: a service plugin, which has no dialog, embeds it and adds the optional interfaces it needs.
type CallError ¶ added in v0.1.40
type CallError struct{ Code, Message string }
CallError is a call the host did not run, or whose command failed: its code (CallRefused, CallUnknown, CallFailed) and why.
type Caret ¶ added in v0.2.5
type Caret struct{ X, Y, W, H, Baseline float32 }
Caret is the surface's text caret in dp, frame-local, with its baseline; the zero Caret is none.
type CommandArgser ¶ added in v0.1.56
CommandArgser takes a [[commands]] id with its arguments (protocol 7): `:autodb query …` from the plugin's Lua package. A Handler that is not one is handed Command(id), the arguments dropped.
type Commander ¶ added in v0.1.21
type Commander interface{ Command(id string) }
Commander takes the manifest's [[commands]], by id.
type Document ¶ added in v0.1.21
type Document struct {
// Path is the note's path in its workspace, a slash path under Root; the absolute path of a
// file outside every workspace, whose Workspace and Root are ""; "" for a draft
Path, Workspace string
// Root is the workspace's folder on this machine, absolute: what a plugin reading or writing
// beside the note joins Path to (File). "" outside every workspace, and from a host that does
// not send it (one before ADR 1792457255's lab files).
Root string
Text string
Cursor Position
Selection []Range
Version int
TooLarge bool
// CursorByte and SelectionBytes are Cursor and Selection as byte offsets into Text, each
// selection range [start, end): the host counts them from the same grapheme clusters as the
// columns, so a plugin slicing Text never counts clusters itself. Sent from protocol 4 on;
// before it they are 0 and nil.
CursorByte int
SelectionBytes [][2]int
}
Document is plugin.document: the note open in the editor, as it stands, saved or not. Path is "" when none is open. TooLarge says the note was too large to send: Path, Workspace, Root and Version are set, and the rest is empty. Version rises with every edit, so a plugin can drop a stale answer.
func FitDocument ¶ added in v0.1.21
FitDocument is d as it can be sent to a plugin speaking protocol 2 or 3 (FitDocumentAt).
func FitDocumentAt ¶ added in v0.1.52
FitDocumentAt is d as it can be sent to a plugin speaking protocol: d itself when its plugin.document, in that protocol's form, encodes to at most MaxDocumentBytes, else its too-large form, which keeps its path, workspace and version alone.
func ReadCommandDocument ¶ added in v0.1.52
ReadCommandDocument reads the document a protocol-4 plugin.command carries; false when it carries none (an earlier protocol's, or a plugin without the feed) or the one it carries is malformed.
func ReadDocument ¶ added in v0.1.21
ReadDocument reads plugin.document. A selection entry that is not a map is dropped, and so is a byte range that is not two offsets.
type DocumentReader ¶ added in v0.1.21
type DocumentReader interface{ Document(d Document) }
DocumentReader takes the feed ([feed] document = true): the open note, after each pause in editing. Only the latest waits for it: a newer document replaces one not yet handled. From protocol 4 a command carries the note as it stood when it was chosen, and the reader is handed that one first, just before the command runs.
type FDGuard ¶ added in v0.2.5
type FDGuard struct {
// contains filtered or unexported fields
}
FDGuard owns a received message's fds until Take transfers each: CloseAll closes the rest.
type Focuser ¶ added in v0.1.21
type Focuser interface{ Focus(focused bool) }
Focuser is a non-modal card told it took the keys (true) or gave them back (false).
type Frame ¶
type Frame struct {
// contains filtered or unexported fields
}
Frame is a plugin's dialog as a grid of cells, drawn and then sent whole (Peer.Frame). A cell holds one character as the screen shows it: a grapheme cluster ("é" written as e and a combining accent, a ZWJ emoji, a flag), measured as AutoDoc's screen measures it (golib's tui.StringWidth). A wide character (CJK, most emoji) takes two cells; the second is its continuation, which Rows leaves out, since the host's own measure gives the column back.
func (*Frame) Fill ¶
Fill sets every cell of the rectangle at (x, y), w wide and h high, to r in st, as Set does.
func (*Frame) Rows ¶
Rows are the frame as host.frame sends it: each row's cells, run together where the style is the same, continuations left out. The host segments each run's text into grapheme clusters again, so two cells whose text would join into one cluster (two regional indicators written apart, a character and a variation selector written after it) are sent in runs of their own: the host never joins clusters across runs, and each keeps the column it was given.
func (*Frame) Set ¶
Set puts r at (x, y) in st, in that one cell: a wide r, which needs two, is a space. Outside the frame it does nothing.
func (*Frame) Text ¶
Text writes s from (x, y) rightwards in st, a grapheme cluster at a time, each taking its width: two cells for a wide one. A zero-width cluster (a combining mark on its own) joins the cell before it. A wide cluster that would cross the right edge is a space. Writing over half of a wide cell blanks its other half. It returns the x after the last cluster, past the edge if s runs over it.
type Handler ¶
type Handler interface {
// Open is the dialog opened: its size inside the border, and the theme. p sends frames.
Open(p *Peer, o Open)
// Key is a key typed into the dialog.
Key(k Key)
// Resize is the dialog's new size.
Resize(w, h int)
// Theme is the theme changed.
Theme(t Theme)
// Close is the dialog closing, or the host gone: save what is kept, stop, and return. The host
// waits two seconds before it stops the process. It is called once, and only after Open.
Close()
}
Handler is a dialog plugin. Serve calls its methods one at a time, in the order the host sent them, on one goroutine of its own. A plugin with a clock of its own (a game's gravity) runs it on another goroutine and sends through the Peer, which is safe for concurrent use. A method may Peer.Call: the host's answer never waits behind it.
type Hider ¶ added in v0.1.2
type Hider interface {
Hide()
Show()
}
Hider is a Handler that wants to know its dialog was hidden (Esc, when its manifest says esc = "hide"; a card's panel closed) and shown again: a game pauses. A Handler that is not one keeps running, unseen.
type Key ¶
Key is a key typed into the dialog. Key is its text for a printable key (" " for Space), or one of Up, Down, Left, Right, Enter, Tab, Backspace, Delete, Insert, Home, End, PageUp, PageDown, F1 to F12. Esc is never sent: it closes the dialog.
type Link ¶
type Link struct {
// contains filtered or unexported fields
}
Link is one end of the protocol: notifications both ways over conn, the host's or the plugin's. Incoming ones are passed to on, one at a time in arrival order, from a goroutine of the link's. A burst larger than the link's queue (a paste of keys, say) waits for on to take them rather than failing the link, so on must not wait on the link itself: it never Calls, as the protocol has only notifications (host.call and plugin.result included), and it should hand each one on quickly, as the peer's writes slow to its pace. Notify is safe from any goroutine.
func NewLink ¶
func NewLink(ctx context.Context, conn net.Conn, on func(method string, params []any)) (*Link, error)
NewLink starts the link over conn. It owns conn from here: Close closes it.
type MenuUpdate ¶ added in v0.3.1
MenuUpdate is host.menu's: a row's id, and its new title or enabled, each nil to leave as it is.
func ReadMenu ¶ added in v0.3.1
func ReadMenu(params []any) (MenuUpdate, error)
ReadMenu reads host.menu: each field of its type (an id string; a title string, or none; an enabled bool, or none), then CheckMenu.
type Notice ¶ added in v0.1.59
Notice is host.notify's.
func ReadNotice ¶ added in v0.1.59
ReadNotice reads host.notify: each field of its type (a message string; an id string, or none; an ongoing bool, or none), then CheckNotice. Its error names the rule broken, never the message.
type Open ¶
type Open struct {
Protocol int
Width, Height int
Theme Theme
// Session is the slot as this plugin left it in this run of AutoDoc (protocol 7, session.go);
// nil when it has none.
Session map[string]string
}
Open is plugin.open: the protocol the host speaks, the dialog's size inside its border, and the theme. A service has no dialog: its size is 0 by 0.
type Paster ¶ added in v0.2.5
type Paster interface{ Paste(text string) }
Paster takes the user's paste chord on a focused surface (protocol 10).
type Peer ¶
type Peer struct {
// contains filtered or unexported fields
}
Peer is the host, as a plugin sends to it.
func (*Peer) Call ¶ added in v0.1.40
Call asks the host to run a command of AutoDoc's catalog by its id, with args (protocol 3): one of the ids the manifest's calls = [...] declares. It waits for the host's answer, at most CallTimeout, and returns true, or false and why: a *CallError (refused, unknown, failed), or ErrCallTimeout, ErrClosed or ErrNoCalls. Each call has a number of its own, never one still waiting. It is safe from any goroutine, the Handler's own methods included: the answer is taken off the link as it arrives, never queued behind the method that waits for it.
func (*Peer) Close ¶
Close asks the host to close the dialog: it answers with plugin.close, and Handler.Close follows. A card's panel closes instead, and the plugin runs on: the host answers with plugin.hide.
func (*Peer) CloseReason ¶ added in v0.1.56
CloseReason is why the host is closing the plugin (protocol 7: CloseQuit, CloseOff, …), for Close to read; "" before protocol 7, or when the host closed it without a plugin.close.
func (*Peer) MenuRow ¶ added in v0.3.1
MenuRow (protocol 11) sets one of the plugin's [[menu]] rows, by its command's id: its title, or whether it's enabled, each nil to leave as it is, from the next time the editor's menu opens. As Notify, the negotiated protocol is checked first, then the update.
func (*Peer) Notify ¶ added in v0.1.59
Notify posts n (protocol 8): a toast kept in the history, or with an id one updated in place while Ongoing and ended by a post without it (notify.go). As SetSession, the negotiated protocol is checked first, then n; a nil error means it was sent: the host drops a notice from a run that is stopping, and an ongoing one past MaxActiveNotices.
func (*Peer) Rows ¶ added in v0.1.52
Rows sends the next rows of table id, answering a FetchRows; more says rows remain.
func (*Peer) RowsKinds ¶ added in v0.1.54
RowsKinds is Rows with each row's kinds (protocol 5; see TableOpen.Kinds): kinds[i] is rows[i]'s, one byte per field. Below protocol 5 the rows go without them.
func (*Peer) SetCaret ¶ added in v0.2.5
SetCaret tells the host where the surface's text caret is, in frame-local dp, for its IME: it draws the preedit there and places the candidate window by it. The zero Caret is none.
func (*Peer) SetPointerShape ¶ added in v0.2.5
SetPointerShape asks for the pointer's shape over the surface: "default", "text", "hand", "crosshair", "move", "resize-ew", "resize-ns" or "not-allowed".
func (*Peer) SetSession ¶ added in v0.1.56
SetSession replaces this plugin's session slot (protocol 7). The host keeps it in memory, never on disk, until AutoDoc quits; Open.Session is it at the plugin's next start in this run of AutoDoc. A nil error means it was sent, not that the host kept it: the host drops a slot over the bounds, and one sent after the plugin began to stop, so a plugin sets its slot when it signs in, never in Close.
func (*Peer) Table ¶ added in v0.1.52
Table shows t in AutoDoc's results panel (protocol 4; the manifest declares [table]), replacing the table shown. Give each table a new ID (a query run again is a new table): a fetch for the table shown may still be on its way, so the host refuses a table that reuses the shown table's ID, keeping the one shown. Rows are taken only as the answer to a FetchRows. Keep each message under the link's limit, MaxMessageBytes as EncodedSize measures it: size the rows by their encoded bytes, not their count alone.
t.Kinds go only to a host that opened the plugin at protocol 5 or later; below it the table is sent without them, every cell text, as that host would read it.
func (*Peer) TableEnd ¶ added in v0.1.52
TableEnd ends table id's rows early: no more will come, and err, when not nil, says why (a query that failed on its third page, say). It answers a FetchRows as Rows does.
func (*Peer) WriteClipboard ¶ added in v0.2.5
WriteClipboard writes text to the clipboard. A plugin reads it only by the user's paste (Paster).
type Pointer ¶ added in v0.1.54
type Pointer struct {
Kind, Button string // "press"/"left", or "wheel"/"up"/"down"
X, Y, Count int
}
Pointer is a view-local, zero-based left press or vertical wheel step. Count is capped by the host, and the plugin checks the coordinates against its most recently opened/resized frame.
func ReadPointer ¶ added in v0.1.54
ReadPointer refuses malformed or unsupported pointer actions before they reach a plugin.
type PointerHandler ¶ added in v0.1.54
type PointerHandler interface{ Pointer(Pointer) }
PointerHandler takes view-local left presses and vertical wheel movement (protocol 6).
type Position ¶ added in v0.1.21
type Position struct{ Line, Col int }
Position is a place in a note: its line and column, both from 1. A column counts characters as the editor's cursor does: grapheme clusters (what shows as one character, "é" written as e and a combining accent, say), not bytes or code points.
type Range ¶ added in v0.1.21
type Range struct{ Start, End Position }
Range is a selection, from Start to End.
type Result ¶ added in v0.1.40
Result is plugin.result: the call it answers, and whether the command ran; Error is "<code> <message>" when it did not.
func ReadResult ¶ added in v0.1.40
ReadResult reads plugin.result.
type Style ¶
Style is how a run of a frame looks: its colours, in the themes' vocabulary ("" is "default"), and its attributes.
type Surface ¶ added in v0.2.5
type Surface struct {
// contains filtered or unexported fields
}
Surface is a card's or dialog's pixel surface. Its methods are safe for concurrent use: a plugin renders on a goroutine of its own.
func (*Surface) Buffer ¶ added in v0.2.5
func (s *Surface) Buffer(ctx context.Context) (*SurfaceBuffer, error)
Buffer is a free buffer of the current generation, waiting for one; an error once ctx ends or the surface is gone.
func (*Surface) Close ¶ added in v0.2.5
func (s *Surface) Close()
Close ends the surface, once: no buffer is granted after it, the side channel shuts down, and every mapping is reclaimed, a buffer being drawn into at its Present. Serve calls it as it ends.
func (*Surface) Done ¶ added in v0.2.5
func (s *Surface) Done() <-chan SurfaceFrameDone
Done is each frame's outcome, as the host answers it. A plugin that doesn't read it loses the oldest beyond 64; the credits come through Buffer regardless.
Done is a bounded, lossy view of the outcomes, not the wire's exactly-once delivery: when the handler doesn't keep up, the oldest are dropped. Credits are never dropped: they come through Buffer.
func (*Surface) Info ¶ added in v0.2.5
func (s *Surface) Info() SurfaceInfo
Info is the surface's latest generation.
type SurfaceBuffer ¶ added in v0.2.5
type SurfaceBuffer struct {
ID, Gen int
W, H, Pitch int
Format string
Pix []byte
// contains filtered or unexported fields
}
SurfaceBuffer is one buffer to draw into: Pix is W×H pixels of Format, Pitch bytes a row.
func (*SurfaceBuffer) RGBA ¶ added in v0.2.5
func (b *SurfaceBuffer) RGBA() *image.RGBA
RGBA is the buffer as an image (RGBA8 buffers only).
type SurfaceBufferSpec ¶ added in v0.2.5
type SurfaceBufferSpec struct {
ID, Gen int
Kind, Format string // "memory"; "rgba8" or "bgra8"
W, H, Pitch int
Size int // bytes of the shared object
}
SurfaceBufferSpec is one buffer of a pool: its id (lifetime-unique; the host's even, a plugin's odd), the generation it belongs to, and its layout in pixels.
func ReadBuffersMsg ¶ added in v0.2.5
ReadBuffersMsg reads a buffers op, refusing more buffers than a pool holds or than fds came.
type SurfaceConn ¶ added in v0.2.5
type SurfaceConn struct {
// contains filtered or unexported fields
}
SurfaceConn is one end of the side channel. Send and Recv may run on different goroutines; Close unblocks a Recv.
func NewSurfacePair ¶ added in v0.2.5
func NewSurfacePair() (*SurfaceConn, *os.File, error)
NewSurfacePair is the host's end and the file to hand the plugin as fd 3 (exec.Cmd.ExtraFiles). Both are close-on-exec; ExtraFiles clears it on the child's fd 3 alone.
func OpenPluginSurfaceConn ¶ added in v0.2.5
func OpenPluginSurfaceConn() (*SurfaceConn, bool, error)
OpenPluginSurfaceConn is the plugin's end, from SurfaceFDEnv: close-on-exec first, so a process the plugin starts does not inherit it by accident (hygiene, not a boundary). ok false: no side channel.
func (*SurfaceConn) Close ¶ added in v0.2.5
func (c *SurfaceConn) Close() error
Close shuts the socket down and closes it, once. A reader must be done with it first (Shutdown).
func (*SurfaceConn) Recv ¶ added in v0.2.5
func (c *SurfaceConn) Recv() (map[string]any, *FDGuard, error)
Recv reads one datagram: its map, and a guard owning the fds it carried. Any error has closed them all. A truncated datagram or control message, a record that isn't SCM_RIGHTS, too many fds, or a map that doesn't decode is ErrSurfaceWire: a violation.
func (*SurfaceConn) Send ¶ added in v0.2.5
func (c *SurfaceConn) Send(m map[string]any, fds ...int) error
Send writes m, with fds as SCM_RIGHTS. The fds stay the caller's.
func (*SurfaceConn) Shutdown ¶ added in v0.2.5
func (c *SurfaceConn) Shutdown()
Shutdown ends the socket's traffic, which wakes a Recv blocked on it, and keeps the fd: a reader that loops back into Recv finds it shut, never a reused fd number. Close it once the reader is done.
type SurfaceFrame ¶ added in v0.2.5
SurfaceFrame is a frame: present buffer Buffer of generation Gen. Seq rises strictly. A frame of a generation the host has retired is answered "retired", not refused: it was in transit. Dirty, when not empty, is the part that changed since the buffer's last frame. InputT is the T of the latest input it shows.
func ReadFrameMsg ¶ added in v0.2.5
func ReadFrameMsg(m map[string]any) (SurfaceFrame, error)
ReadFrameMsg reads a frame op.
type SurfaceFrameDone ¶ added in v0.2.5
type SurfaceFrameDone struct {
Seq uint64
Outcome string
WhenNS int64
// InputT is the InputT the frame was presented with: the SDK's own, never on the wire.
InputT int64
}
SurfaceFrameDone is a frame's outcome: "drawn", "superseded", "not_shown", "unsupported", "closed", or "retired" (its generation ended before it arrived). WhenNS is when it was drawn.
func ReadFrameDoneMsg ¶ added in v0.2.5
func ReadFrameDoneMsg(m map[string]any) (SurfaceFrameDone, error)
ReadFrameDoneMsg reads a frame.done op.
type SurfaceHandler ¶ added in v0.2.5
type SurfaceHandler interface {
Surface(s *Surface, info SurfaceInfo)
SurfacePointer(p SurfacePointer)
}
SurfaceHandler draws a pixel surface (protocol 10, [surface]): Surface is each generation of it (the first after Open; again at a resize, a scale or a caps change), and SurfacePointer the pointer on it in dp. Consecutive moves reach it coalesced to the latest, and consecutive scrolls summed. A host that draws no pixels never calls Surface: the plugin draws cells.
type SurfaceInfo ¶ added in v0.2.5
type SurfaceInfo struct {
Gen int
W, H float32
Scale float32
Kinds []SurfaceKind
}
SurfaceInfo is one generation of a surface: Gen matches the buffers the side channel brings for it. W and H are in dp; Scale is device pixels per dp, so a frame of W×Scale by H×Scale pixels is drawn one to one.
func ReadSurface ¶ added in v0.2.5
func ReadSurface(params []any) (SurfaceInfo, error)
ReadSurface reads plugin.surface.
func (SurfaceInfo) Pixels ¶ added in v0.2.5
func (s SurfaceInfo) Pixels() (w, h int)
Pixels is the surface's size in device pixels, rounded up.
type SurfaceKind ¶ added in v0.2.5
SurfaceKind is a frame kind the window draws: "memory" or "dmabuf", its format ("rgba8", "bgra8"), its DRM modifier, and its largest size in pixels.
type SurfacePointer ¶ added in v0.2.5
type SurfacePointer struct {
Kind, Button string
X, Y, DX, DY float32
Count int
Ctrl, Alt, Shift, Super bool
T int64
}
SurfacePointer is the pointer on a surface. Kind is "move", "press", "release", "scroll" or "leave"; Button is "left", "middle" or "right" for a press or release. X and Y are frame-local dp, and may lie outside the surface while a button holds the pointer. DX and DY are a scroll's, in dp. T is when the host dispatched it (Unix nanoseconds), for latency.
func ReadSurfacePointer ¶ added in v0.2.5
func ReadSurfacePointer(params []any) (SurfacePointer, error)
ReadSurfacePointer refuses a pointer event of an unknown kind or button.
type TableFetch ¶ added in v0.1.52
TableFetch is plugin.table.fetch: up to N more rows of the table ID.
func ReadFetch ¶ added in v0.1.52
func ReadFetch(params []any) (TableFetch, error)
ReadFetch reads plugin.table.fetch.
type TableFetcher ¶ added in v0.1.52
type TableFetcher interface {
FetchRows(f TableFetch)
TableClosed(id string)
}
TableFetcher serves the rows of the tables it sends (protocol 4, [table]): FetchRows is the host asking for up to f.N more of table f.ID, answered with Peer.Rows (or Peer.TableEnd); the host asks again only after the answer. TableClosed is the table gone: its panel closed, or a newer table replaced it.
type TableOpen ¶ added in v0.1.52
TableOpen is host.table: a table the host shows in its results panel, by the plugin's id for it. Fields are the columns' titles; each row has a cell per field (a shorter row's last cells are empty, a longer one's extra cells dropped). More says rows remain, for the host to fetch.
Kinds (protocol 5) are each row's cells' kinds: Kinds[i] is row i's, one byte per field (see KindText). nil sends none, and every cell is text. A table's kinds are decided by its host.table: one sent with kinds (an empty list when it has no rows yet) reads them on every later page, and one sent without them reads every cell as text.
func ReadTable ¶ added in v0.1.52
ReadTable reads host.table as protocol 4 has it, with no kinds; dropped counts its malformed rows and cells (readRows).
func ReadTableAt ¶ added in v0.1.54
ReadTableAt reads host.table from a plugin speaking protocol: from 5, with its kinds, which must fit its rows and fields (CheckKinds) or the table is malformed. Below 5 a kinds field is not read.
type TableRows ¶ added in v0.1.54
TableRows is host.rows as protocol 5 reads it: table ID's next rows, their kinds (nil: none sent), and whether more remain.
func ReadTableRows ¶ added in v0.1.54
ReadTableRows reads host.rows from a plugin speaking protocol: from 5, with its kinds, one string per row. Their length is checked against the table's fields by whoever knows them, the host (CheckKinds), since host.rows does not carry the fields. Below 5 a kinds field is not read.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package card is AutoDoc's native UI kit for dialog and card plugins (ADR 1792254331): a plugin describes what its card shows as a Page of blocks and controls, and a Card fits it to the size AutoDoc gives, draws it in the theme's colours, and handles the keys, the focus and the pointer.
|
Package card is AutoDoc's native UI kit for dialog and card plugins (ADR 1792254331): a plugin describes what its card shows as a Page of blocks and controls, and a Card fits it to the size AutoDoc gives, draws it in the theme's colours, and handles the keys, the focus and the pointer. |
|
Package decl runs a golib QML program (tui/decl) as an AutoDoc card or dialog (ADR 1792254331 §4.7–4.10): the program runs inside the plugin on a backend of its own, and every frame it draws is sent to AutoDoc as host.frame.
|
Package decl runs a golib QML program (tui/decl) as an AutoDoc card or dialog (ADR 1792254331 §4.7–4.10): the program runs inside the plugin on a backend of its own, and every frame it draws is sent to AutoDoc as host.frame. |