Exec Line Protocol
The exec line protocol is a bidirectional stream over standard input and standard output. Glimpse starts the child process, sends setup and event lines to the child's stdin, and reads status and popover lines from the child's stdout.
Each protocol line starts with a command name, a space, and a JSON object:
command {"field":"value"}| Direction | Command | Purpose |
|---|---|---|
| Child to Glimpse | status | Replaces the panel status items for this applet. |
| Child to Glimpse | popover | Replaces the popover content for this applet. |
| Glimpse to child | init | Announces the applet instance name and configured options. |
| Glimpse to child | event | Reports clicks, scrolls, popover opens/closes, and control changes. |
The normal flow is:
- Glimpse starts the child process from
command. - Glimpse sends one
initline. - The child prints a
statusline, and optionally apopoverline. - Glimpse sends
eventlines when the user interacts with status items or popover controls. - The child prints new
statusorpopoverlines whenever its state changes.
Unknown commands and invalid JSON are ignored and logged.
Common Messages
| Message | Direction | Purpose | Payload shape |
|---|---|---|---|
init | Glimpse to child | Startup data for this applet instance. | {"instance":"name","options":{...}} |
status | Child to Glimpse | Current panel items. | {"items":[...]} |
popover | Child to Glimpse | Current popover tree. | {"root":{...}} |
event | Glimpse to child | User interaction or popover lifecycle event. | {"id":"...","type":"...","source":"...",...} |
Your child process sends:
status {"items":[{"id":"cpu","label":"42%","icon":{"name":"cpu-symbolic"},"tooltip":"CPU usage"}]}
popover {"root":{"type":"section","data":{"title":"System","children":[]}}}Glimpse sends:
init {"instance":"sysinfo","options":{"interval":5,"unit":"celsius"}}
event {"id":"cpu","type":"click","source":"status","button":"left"}Status Messages
Status items are shown directly in the panel.
status {"items":[
{"id":"cpu","icon":{"name":"cpu-symbolic"},"label":"12%","tooltip":"CPU"},
{"id":"mem","icon":{"name":"memory-symbolic"},"label":"51%","tooltip":"Memory"}
]}| Field | Default | Meaning |
|---|---|---|
id | unset | Optional event id. Add it if you want clicks or scrolls. |
icon | unset | Optional icon, either {"name":"icon-name"} or {"path":"/path/to/image.png"}. |
label | unset | Optional text in the panel. |
tooltip | unset | Optional hover text. |
Left-click opens the popover when the applet has popover content. Right-click opens the context menu if available.
Popover Messages
A popover message replaces this applet's popover content. The payload has a root field containing a component tree.
popover {"root":{"type":"section","data":{
"title":"System",
"children":[
{"type":"row","data":{"spacing":8,"children":[{"type":"label","data":{"text":"CPU"}},{"type":"badge","data":{"label":"42%"}}]}},
{"type":"meter","data":{"label":"Memory","value":0.51,"text":"51%"}},
{"type":"level_bar","data":{"value":0.7,"min":0.0,"max":1.0,"mode":"continuous"}},
{"type":"toggle_button","data":{"id":"focus","label":"Focus","active":false}},
{"type":"picture","data":{"path":"/home/me/.cache/avatar.png","content_fit":"cover"}},
{"type":"overlay","data":{"child":{"type":"label","data":{"text":"Preview"}},"overlays":[{"type":"badge","data":{"label":"Live","halign":"end","valign":"start"}}]}},
{"type":"list_box","data":{"children":[{"type":"label","data":{"text":"First"}},{"type":"badge","data":{"label":"Second"}}]}},
{"type":"tree_expander","data":{"child":{"type":"label","data":{"text":"Nested"}},"hide_expander":true,"indent_for_depth":true,"indent_for_icon":true}},
{"type":"menu_button","data":{"label":"More","icon":"open-menu-symbolic","popover":{"type":"label","data":{"text":"Menu content"}}}},
{"type":"link_button","data":{"uri":"https://example.com/docs","label":"Docs"}},
{"type":"expander","data":{"label":"More","expanded":false,"child":{"type":"label","data":{"text":"Extra details"}}}}
]
}}}The root object uses this structure:
| Field | Meaning |
|---|---|
type | Component name, such as section, row, button, or meter. |
data | Component fields. The expected fields depend on type. |
Send a complete popover update whenever the content changes. Read Components for valid component types and fields.
Interactive Events
Interactive status items and popover components send event lines back to the child process.
| Source | Event | Payload |
|---|---|---|
Status item with id | click, scroll | button or delta_y. |
Popover button | click | button = "left". |
Popover switch | toggle | active = true or false. |
Popover toggle_button | toggle | active = true or false. |
Popover checkbox | toggle | active = true or false. |
Popover slider | change | numeric value. |
Interactive meter | change | numeric value. |
Popover select | change | selected item id, label, and index. |
| Popover lifecycle | open, close | id popover. |
link_button opens its URI through GTK and does not emit an applet event.
Example event:
event {"id":"volume","type":"change","source":"popover","value":0.72}Shell Starter
#!/bin/sh
printf 'status {"items":[{"id":"load","label":"starting","icon":{"name":"utilities-system-monitor-symbolic"}}]}\n'
while IFS= read -r line; do
case "$line" in
init\ *)
printf 'status {"items":[{"id":"load","label":"ready","icon":{"name":"utilities-system-monitor-symbolic"}}]}\n'
;;
event\ *)
printf 'popover {"root":{"type":"section","data":{"title":"System","children":[{"type":"row","data":{"spacing":8,"children":[{"type":"label","data":{"text":"Last event"}},{"type":"badge","data":{"label":"seen"}}]}}]}}}\n'
;;
esac
doneThis shape is event-driven. For polling, run a background loop and keep reading events in the foreground.
How-To: CPU Temperature
#!/bin/sh
while true; do
temp="$(sensors | rg 'Package id 0' | rg -o '[0-9]+\\.[0-9]+°C' | head -n1)"
[ -n "$temp" ] || temp="n/a"
printf 'status {"items":[{"id":"cpu","icon":{"name":"temperature-symbolic"},"label":"%s","tooltip":"CPU temperature"}]}\n' "$temp"
sleep 5
doneConfig:
# ~/.config/glimpse/applets/cpu-temp.toml
id = "cpu-temp"
type = "exec"
[exec]
command = ["sh", "-c", "~/.config/glimpse/scripts/cpu-temp"]How-To: Toggle A Command
Use a button and handle its click event:
popover {"root":{"type":"section","data":{"title":"VPN","children":[{"type":"button","data":{"id":"toggle-vpn","label":"Toggle VPN","icon":"network-vpn-symbolic","variant":"primary"}}]}}}Your script receives:
event {"id":"toggle-vpn","type":"click","source":"popover","button":"left"}Run your command, then print updated status and popover lines.
Best Practices
| Practice | Why |
|---|---|
| Print status immediately | The panel should not sit empty while your script warms up. |
| Keep panel labels short | Long labels make the panel jump and crowd other applets. |
| Put detail in the popover | The panel is for glanceable state; popovers are for explanations and controls. |
| Use stable ids | Events are easier to handle when ids do not change between updates. |
| Throttle polling | Most system stats do not need sub-second updates. |
| Send complete updates | Treat each status or popover line as the current truth. |
| Use variants sparingly | warning and danger should mean something needs attention. |
| Validate JSON before running | Bad JSON is ignored and logged. |
| Keep stderr quiet | Use stderr for useful diagnostics, not a constant stream. |
| Set only the environment you need | Use env_clear and env when a child process should not inherit the shell environment. |
| Prefer one script per concern | A small CPU applet is easier to maintain than one giant script for everything. |
See Also
| Page | Covers |
|---|---|
| Exec Applet | Applet config and options. |
| Components | Popover component fields and component types. |
| Exec SDK | SDK installation and language examples. |