playbit / docs

Automation

Run a wasm app with:

/Applications/Playbit.app/Contents/MacOS/Playbit \
    --remote-control program.wasm

In this mode:

One JSON object per line.

Fatal Errors

The process terminates with an error on stderr when:

screenshot is the only recoverable command. It writes either a success result or an error result to stdout.

Window Readiness

There is no window_opened event.

Commands that require a window are held until the main window is:

Window-dependent commands:

wait does not require a window.

Queue Semantics

wait

fence

Event commands

screenshot

When stdin is closed, the app exits after the last queued command completes.

Command Shape

All input lines have this base shape:

interface InputMessage {
    command: string
    id?: string
}

id is opaque. It is only echoed back by screenshot results.

Wait

{"command":"wait","ms":250}

Fence

{"command":"fence"}

Screenshot

{"command":"screenshot","id":"s1","format":"png","rect":{"x":0,"y":0,"width":640,"height":480}}

Fields:

Rules:

JSON screenshot file format:

interface RendererPackageScreenshot {
    windowObjectId: number
    shapes: ShapeItem[]
    texts: TextItem[]
    instructions: InstructionItem[]
    clips: ClipItem[]
}

interface Vec4 {
    x: number
    y: number
    z: number
    w: number
}

interface U16x4 {
    x: number
    y: number
    z: number
    w: number
}

interface ShapeItem {
    bounds: Vec4
    cornerRadius: U16x4
    fillColor: string
    fillColors: [string, string, string, string]
    strokeColor: string
    strokeWidth: number
    flags: number
    blur: number
    opacity: number
}

interface TextItem {
    bounds: Vec4
    textPlan: number
}

interface InstructionItem {
    kind: "shape" | "text" | "clip_push" | "clip_pop"
    runLength?: number
}

interface ClipItem {
    bounds: Vec4
}

Notes:

Example:

{
    "windowObjectId": 123,
    "shapes": [
        {
            "bounds": { "x": 24, "y": 24, "z": 16, "w": 16 },
            "cornerRadius": { "x": 0, "y": 0, "z": 0, "w": 0 },
            "fillColor": "0xFFC4E87000000000",
            "fillColors": [
                "0x0000000000000000",
                "0x0000000000000000",
                "0x0000000000000000",
                "0x0000000000000000"
            ],
            "strokeColor": "0x0000000000000000",
            "strokeWidth": 0,
            "flags": 0,
            "blur": 0,
            "opacity": 1
        }
    ],
    "texts": [],
    "instructions": [
        { "kind": "shape", "runLength": 1 }
    ],
    "clips": []
}

Stdout result:

interface ScreenshotSuccessResult {
    id?: string
    path: string
}

interface ScreenshotErrorResult {
    id?: string
    error: string
}

Examples:

{"id":"s1","path":"/var/folders/.../shot-1.png"}
{"id":"s1","error":"no renderer package available"}

Event Commands

Common fields for all event commands:

interface EventCommand extends InputMessage {
    objectId?: number
    timestamp?: number
    clientId?: number
    deviceId?: number
    modifiers?: string
}

Defaults:

objectId handling:

modifiers is a space-separated list of:

Pointer Events

Commands:

Shape:

interface PointerEventCommand extends EventCommand {
    command:
        | "pointer_enter" | "pointer_leave" | "pointer_down"
        | "pointer_up" | "pointer_move" | "pointer_cancel"
    pointerId?: number
    flags?: string
    buttons?: number[]
    button?: number
    kind?: "mouse" | "touch" | "trackpad" | "pen"
    clickCount?: number
    x: number
    y: number
    dx?: number
    dy?: number
}

Defaults:

flags is a space-separated list of:

buttons and button use 1-based numbering in JSON:

Example:

{"command":"pointer_move","x":120,"y":80,"dx":4,"dy":-2,"flags":"primary"}

Scroll

interface ScrollCommand extends EventCommand {
    command: "scroll"
    kind?: "mouse" | "touch" | "trackpad" | "pen"
    phase?: "changed" | "began" | "ended" | "momentum"
    flags?: string
    x?: number
    y?: number
    dx: number
    dy: number
    wheelZ?: number
}

Defaults:

flags is a space-separated list of:

Example:

{"command":"scroll","x":120,"y":220,"dx":0,"dy":-48,"flags":"precise"}

Gestures

gesture_pan

{"command":"gesture_pan","phase":"changed","x":120,"y":220,"dx":16,"dy":0}

gesture_pinch

{"command":"gesture_pinch","phase":"changed","x":120,"y":220,"scale":1.05}

gesture_rotate

{"command":"gesture_rotate","phase":"changed","x":120,"y":220,"rotation":0.1}

Shared fields:

gesture_pan defaults dx=0, dy=0.

gesture_pinch defaults scale=1.0.

gesture_rotate defaults rotation=0.

Keyboard

Commands:

Two payload forms are supported.

Keyed form:

{"command":"key_down","key":"Tab","deviceKey":"Tab"}

IME/text form:

{"command":"key_down","text":"a"}

Shape:

interface KeyboardEventCommand extends EventCommand {
    command: "key_down" | "key_up"
    flags?: string
}

interface KeyedKeyboardEventCommand extends KeyboardEventCommand {
    key: string
    deviceKey: string
}

interface TextKeyboardEventCommand extends KeyboardEventCommand {
    command: "key_down"
    text: string
}

Rules:

Keyboard key names are case-insensitive and use the PBSysKeyboardKey variant suffix name, for example:

Examples

Pipe a whole command stream:

{
    printf '%s\n' '{"command":"pointer_move","x":120,"y":80}'
    printf '%s\n' '{"command":"pointer_down","x":120,"y":80,"buttons":[1],"button":1}'
    printf '%s\n' '{"command":"pointer_up","x":120,"y":80,"buttons":[],"button":1}'
    printf '%s\n' '{"command":"fence"}'
    printf '%s\n' '{"command":"screenshot","id":"shot1"}'
} | /Applications/Playbit.app/Contents/MacOS/Playbit \
    --remote-control program.wasm

Delay before a screenshot:

{
    printf '%s\n' '{"command":"wait","ms":250}'
    printf '%s\n' '{"command":"screenshot","id":"shot2","format":"png"}'
} | /Applications/Playbit.app/Contents/MacOS/Playbit \
    --remote-control program.wasm

Write the latest render commands as JSON:

printf '%s\n' \
    '{"command":"screenshot","id":"pkg1","format":"json","output_file":"/tmp/render-package.json"}' |
    /Applications/Playbit.app/Contents/MacOS/Playbit \
        --remote-control program.wasm