Package headless
Package headless operates a Viva application without opening a window.
Import path: import "github.com/vivaui/viva/headless"
Package headless operates a Viva application without opening a window.
A Session runs the application's real paint, input routing, focus and
text-input paths through app.Driver, with an animation clock that only moves
when told to, and can turn the current frame into an image. On macOS the
image comes from the window's own Metal pipelines and CoreText
rasterizers, drawing into a texture instead of a drawable, so a headless
screenshot is the pixels the window would show. That renderer is linked only
into builds made with -tags vivahead, so release builds carry none of it;
without the tag sessions still operate and answer queries and Image reports
ErrNoRenderer. Windows (Direct3D 11, GDI text) and Linux (Vulkan, Pango text)
draw their screenshots the same way, with their own renderers,
in any build that links this package; only the macOS one also draws the text being
edited, which the platform's native editor paints over the scene. A session
lays the application out with the platform's text engine, as a window does
(Options.ReferenceText asks for the platform-independent reference metrics).
session, err := headless.Open(config, nil, headless.Options{})
session.ClickLabel("New Host")
session.Type("example.com")
session.Keys("cmd+c")
text := session.Driver().Clipboard()
session.Screenshot("after.png")
Record plays the animation clock and writes the frames whose pixels changed,
so a transition can be reviewed as a sequence rather than a still:
frames, err := session.Record("run/frames", headless.RecordOptions{
Duration: 700 * time.Millisecond,
})
The PNG files and their lengths in manifest.json are what a video tool needs;
frames that repeat are written once, so an application at rest records as one
still frame. Recording uses the same offscreen renderers as Image.
A running application can be driven the same way without any change to its
code: link package headless/auto (behind a build tag, so release builds never
contain it) and start the process with VIVA_HEADLESS=1. RunCanvas then serves
the line protocol documented on Serve on standard input and output instead
of opening a window.
Functions
Run#
func Run(config app.Config, canvas *ui.Canvas) error
Run is the app.UseRunner implementation behind headless/auto: it opens a
Session for config and serves the line protocol on standard input and
output instead of opening a window. VIVA_HEADLESS_SCALE sets the screenshot
scale (default 2).
RunApplication#
func RunApplication(start func(host app.Host) error) error
RunApplication is the app.ApplicationRunner installed by headless/auto. It
preserves every window as a separate Driver and exposes explicit selection
and close commands while reusing the ordinary single-window protocol.
Serve#
func Serve(session *Session, in io.Reader, out io.Writer) error
Serve runs the line protocol on in and out until "quit" or the end of input.
Each request is one line; the reply is zero or more data lines followed by
exactly one final line, "ok ..." or "error ...". Arguments are separated by
spaces; an argument containing spaces is written in double quotes ("New
Host"), with \" and \\ as escapes. Coordinates are logical pixels.
size ok 1400x900 scale=2
move X Y | down X Y | up X Y | click X Y | dblclick X Y | rightclick X Y
scroll X Y DX DY [MODIFIERS] scroll at X,Y; modifiers are shift+ctrl+alt+cmd
drop X Y PATH... drop files from the system onto the point: ok accepted|refused
dragover X Y | dragleave the hover of such a drag, and its end
click "Label" click the center of the control with that label
dblclick "Label" double-click the center of that control
rightclick "Label" right-click the center of that control (opens its context menu)
key SPEC... tab | shift+tab | enter | esc | backspace | left |
pageup | f5 | insert | cmd+a | cmd+c | cmd+x | cmd+v | x ...
ctrl+c and configured alt+x chords go to a focused terminal
keydown NAME | keyup NAME hold and release one key: a named key ("left", "f5"), a
character, or a modifier (shift ctrl alt cmd, rshift rctrl
ralt rcmd). Held modifiers apply to every click, scroll and
key until released. A terminal that asked for key releases
sees the presses and releases; elsewhere keydown acts as key
blur the window loses the keyboard: held keys are released
type TEXT insert the rest of the line into the focused field
select START END select a rune range of the focused field
resize W H
frame ok x=.. y=.. w=.. h=.. (the content area on screen)
windowmove X Y move the window's content area to a screen point, as a drag
of the window would, and report its new frame to OnFrameChange
minimize on|off report the window as minimized or restored, as the window
manager would, and call OnMinimize
maximize on|off report the window as maximized or restored, as the window
manager would, and call OnMaximize
wait MS advance the animation clock
settle [MS] apply background work until quiet for MS (default 100)
waitfor "text" [MS] wait up to MS (default 5000) real ms for text on screen;
that time also passes for the app's timers
shot PATH [now] write a PNG of the interface at rest: transitions in flight
(a dialog fading in) are played to their end first. With
now, write the current frame as it is, so wait MS; shot
PATH now catches an animation MS into it
record DIR MS [FPS] play MS more of animation and write it to DIR as a PNG
sequence and manifest.json, sampling FPS frames a second
(default 60); frames that did not change are written once
with a longer duration, and ok reports the count
find "label" ok id=7 x=.. y=.. w=.. h=.. label=".."
hits [filter] one line per control, then ok
texts [filter] one line per painted text, then ok
layout one line per suspicious spot (text cut off, spilling out of its
control, over other text, off the window; focusable controls that
cross), then ok N problems; it never fails
focus ok id=.. label=".." (id=0 when nothing is focused)
editor ok value=".." selection=a,b (no editor: ok none)
appearance [light|dark|unknown] read or set the system appearance the run reports
clip [TEXT] read or replace the private clipboard
clipimage [PATH|clear] read (ok image N bytes | ok none) or replace the private clipboard's image with a PNG file
openedurl report the last public OpenURL request
openedpath report the last OpenPath request
openfiles PATH...|cancel queue the next native open-dialog result
opendirectory PATH|cancel queue the next native directory-dialog result
savefile PATH|cancel queue the next native save-dialog result
quit
ServeApplication#
func ServeApplication(application *applicationSession, in io.Reader, out io.Writer) error
ServeApplication drives a multi-window headless application. windows lists
live windows, window ID selects the target for ordinary commands, and
requestclose [ID] sends one window its close button, which an application with
an OnCloseRequest may turn into hiding the window. closewindow [ID] closes one window. Closing the final window ends the loop.
Variables
var ErrNoRenderer = errors.New("headless: no offscreen renderer on this platform")
ErrNoRenderer is returned by Image and Screenshot on platforms without an
offscreen renderer.