Skip to content

Authoring pages

Boards are XML. The runtime and the page editor read and write the same format. You do not write C++ to add a keyboard, a speech board, or an AHK cell.

What a cell does when dwell finishes is an action. Every action type, value token, and runtime path: Actions. Every command="…" name: Commands.

Where files live

Location Role
resources/layouts/*.xml Shipped pages. Catalog id = filename stem (qwerty_main.xml → qwerty_main).
%AppData%\Gazer\layouts\ User copies. A file with the same stem overrides the shipped page. Saving a shipped page from the editor writes here and leaves resources/ unchanged.

OpenPage / catalog ids open those pages on the live host.

Document tree

<Page id="…" name="…">
  <Style id="…"/>          <!-- named chrome; omit id for page default -->
  <Dwell id="…"/>          <!-- named timing; omit id for page default -->
  <Zone …/>                <!-- screen-anchored chip -->
  <Grid …>                 <!-- placed rectangle of rows × columns -->
    <Cell …/>
    <SubGrid …>            <!-- nested grid occupying a cell span -->
      <Cell …/>
    </SubGrid>
  </Grid>
</Page>

Child order is free. Grids and zones are painted and hit in document order within a page; attached pages stack newest in front.

Minimal page

<Page id="tools" name="Tools">
  <Grid id="board" desktopMode="true" rows="1" columns="2"
        anchor="Top" offset="0,0" size="800,400" gap="12" margin="16">
    <Cell id="hello" row="0" col="0" label="Speak" speak="Hello"/>
    <Cell id="close" row="0" col="1" label="Close" closePage="true"/>
  </Grid>
</Page>

Put that at %AppData%\Gazer\layouts\tools.xml (or next to other pages) and open it with openPage="tools".

Cells and actions

A cell or zone may have one action attribute. Several actions use child elements. Attribute names are camelCase; element names are PascalCase. They are the same action.

When dwell ends (look away or at another cell, after blink grace), the host flashes the cell and runs that list in order. Consecutive ShowLayers in the list are applied together. actionLoop="true" repeats until the cell is activated again. <Phase> children replace the single-shot list (first dwell enters phase 0; later dwells wrap; the current phase fires on leave).

<Cell row="0" col="0" label="q" send="q"/>
<Cell command="toggleLookToScroll"/>
<Cell openPage="uw_qwerty, true"/>

<Send value="a"/>
<Command value="toggleLookToScroll"/>
<OpenPage value="uw_qwerty, true"/>
<ShowLayers value="1,2"/>
<ClosePage/>
<Speak value="Hello"/>
Name value What happens
Send key[, Down\|Up[, durationMs]] Type a key into the focused window (q, ,, Enter, a,500). A comma key is send=",". Named keys, Shifted punctuation, and composer capture: Send.
Command builtin or mapping name Look up toggleLookToScroll, backspace, settings.dwell.fast, gamepad.a, … Exact builtin, then prefix, then default.json. Commands.
MouseLeftClick (also Middle / Right) default / double / down / up / toggle Click or hold at the current cursor. Empty value is a default click.
MouseMoveToGaze zoom: empty / 0 / N / -1 / -2 Arm dwell-move. Next desktop dwell warps the cursor. Empty follows Settings → Zoom.
MouseLeftClickAtGaze (also Middle / Right) same zoom tokens Arm dwell-move, then one click.
MouseMoveByDirection n/s/e/w/ne/nw/se/sw[, px] Nudge now. Omitted amount uses the mouse-assist step.
MouseMoveToPoint [relative,]x,y Warp (or relative-move) now. Dim tokens allowed.
OpenPage targetId[, true] Attach that catalog page. true saves a breadcrumb for GoBack.
TogglePage targetId[, true] Open if that id is closed; close if it is already attached.
HostPage fragmentId or hostId, fragmentId Swap a src slot on the source page, or the stack page that embeds it. Two ids name the host. Settings swaps Dwell, Zoom, Place, Gaze, Head, Speak, Theme, and Setup this way.
ShowLayers layer[, layer…] Replace this page’s visible layer set (1,2).
ClosePage (empty) Close the page that owns the cell (the host, if this cell is in a src slot).
CloseAllPages / CloseOtherPages (empty) Drop attached pages (all / except the source). Also disable ComboMouse.
GoBack (empty) Restore the last breadcrumb.
Speak text Windows SAPI canned utterance. Composer synthesis is compose.speak.
AHK element body Temp .ahk with a local AutoHotkey install.
Run kind,file[,persist][,key] Spawn a Python (.py) or AutoHotkey (.ahk) file. args is extra argv.

<Action send="a"/> is the same as <Send value="a"/>. Legacy <Action id="Send" value="a"/> still loads.

Full options (keys, zoom tokens, inbound --action, composer capture, editor fields): Actions. Attribute list including chrome, roles, and dims: docs/page-xml.md.

Dimensions

Used by offset, size, dwellOffset, dwellSize, grid tracks, and MouseMoveToPoint.

Token Meaning Example
Integer Pixels 150, 80px
. or / Proportion of the reference axis (width for x, height for y) 0.25, 1/2
h suffix Proportion of the height on either axis (square boards) size="0.25h,0.25h"
A_ScreenWidth / A_ScreenHeight Pixel arithmetic vs the placement surface A_ScreenHeight/9*16
clamp(v, min, max) Bound a pixel expression clamp(1.8*A_ScreenHeight, 1080, A_ScreenWidth)
* / 2* Grid tracks only: share leftover space rowHeights="80,*,120"

Pairs (offset, size) are x,y. Commas inside parentheses do not split the pair. desktopMode="true" uses the virtual desktop as the bounds reference; otherwise the current screen.

Layers

Each grid/zone lists membership (layers="1,2"). The page has a visible set (showLayers on open, then ShowLayers actions). An item is visible if any of its layers is in the visible set. Default both sides: 1.

Typical master page: layer 1 = dock chips, 2 = drawer, 3 = quit confirm. Keyboard shift/symbol boards are extra layers on the same page, not extra OpenPages.

Dwell on a cell

Unspecified dwell uses rapid for Send / modifiers / mapping keys / composer typing, and standard for everything else. Override per page or per cell:

<Dwell id="vert1" scanGrace="200" activation="200,600"/>
<Cell row="0" col="0" dwell="vert1" command="toggleLookToScroll"/>
<Cell row="0" col="1" scanGrace="80" activation="400" send="a"/>

activation is comma-separated step times in ms. The last step repeats while gaze holds. 0 fires immediately after scan grace.

AutoHotkey and script files

Inline snippets stay <AHK>. <Run> starts a real file next to the page (or under %AppData%\Gazer). Absolute paths must stay under the page directory, %AppData%\Gazer, or the app resources folder.

<Cell id="predict" row="0" col="0" label="Predict">
  <Run value="python,scripts/predict.py,persist,predict"/>
</Cell>
<Cell id="snap" row="0" col="1" label="Snap" run="ahk,scripts/snap.ahk"/>

Python is not bundled. Scripts see GAZER_EXE and GAZER_ACTION_PIPE. Talk back with Gazer.exe --action … — do not FileOpen the named pipe write-only.

Drive a running instance

The live process owns a local pipe named Gazer (\\.\pipe\Gazer; GAZER_ACTION_PIPE overrides). A second Gazer.exe forwards --action and exits. The payload is the same action language as page XML: name=value lines, name value lines, or XML elements. # comments and empty lines are ignored. Several --action flags run in order.

Gazer.exe --action openPage=qwerty_main --action showLayers=2
Gazer.exe --action command=toggleLookToScroll
Gazer.exe --action "<Speak value=\"Hello\"/>"
Gazer.exe --action run=python,scripts/predict.py

A second Gazer.exe with no --action sends raise (host to front). --editor on a second instance also queues command=openPageEditor. After OpenPage in one payload, ShowLayers applies to the opened page. Empty pipe reads are ignored.

Forms, composer capture, and raise: Actions — Inbound.

Full schema

Colors, roles, visibility, phases, HostPage slots, and every attribute: docs/page-xml.md. Action runtime: Actions. Command names: Commands. Shipped examples: resources/layouts/main.xml, example_keyboard.xml, example_mouse.xml, example_gamepad.xml, compose.xml.