Skip to content

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:

txt
command {"field":"value"}
DirectionCommandPurpose
Child to GlimpsestatusReplaces the panel status items for this applet.
Child to GlimpsepopoverReplaces the popover content for this applet.
Glimpse to childinitAnnounces the applet instance name and configured options.
Glimpse to childeventReports clicks, scrolls, popover opens/closes, and control changes.

The normal flow is:

  1. Glimpse starts the child process from command.
  2. Glimpse sends one init line.
  3. The child prints a status line, and optionally a popover line.
  4. Glimpse sends event lines when the user interacts with status items or popover controls.
  5. The child prints new status or popover lines whenever its state changes.

Unknown commands and invalid JSON are ignored and logged.

Common Messages

MessageDirectionPurposePayload shape
initGlimpse to childStartup data for this applet instance.{"instance":"name","options":{...}}
statusChild to GlimpseCurrent panel items.{"items":[...]}
popoverChild to GlimpseCurrent popover tree.{"root":{...}}
eventGlimpse to childUser interaction or popover lifecycle event.{"id":"...","type":"...","source":"...",...}

Your child process sends:

txt
status {"items":[{"id":"cpu","label":"42%","icon":{"name":"cpu-symbolic"},"tooltip":"CPU usage"}]}
popover {"root":{"type":"section","data":{"title":"System","children":[]}}}

Glimpse sends:

txt
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.

txt
status {"items":[
  {"id":"cpu","icon":{"name":"cpu-symbolic"},"label":"12%","tooltip":"CPU"},
  {"id":"mem","icon":{"name":"memory-symbolic"},"label":"51%","tooltip":"Memory"}
]}
FieldDefaultMeaning
idunsetOptional event id. Add it if you want clicks or scrolls.
iconunsetOptional icon, either {"name":"icon-name"} or {"path":"/path/to/image.png"}.
labelunsetOptional text in the panel.
tooltipunsetOptional 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.

txt
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:

FieldMeaning
typeComponent name, such as section, row, button, or meter.
dataComponent 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.

SourceEventPayload
Status item with idclick, scrollbutton or delta_y.
Popover buttonclickbutton = "left".
Popover switchtoggleactive = true or false.
Popover toggle_buttontoggleactive = true or false.
Popover checkboxtoggleactive = true or false.
Popover sliderchangenumeric value.
Interactive meterchangenumeric value.
Popover selectchangeselected item id, label, and index.
Popover lifecycleopen, closeid popover.

link_button opens its URI through GTK and does not emit an applet event.

Example event:

txt
event {"id":"volume","type":"change","source":"popover","value":0.72}

Shell Starter

sh
#!/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
done

This shape is event-driven. For polling, run a background loop and keep reading events in the foreground.

How-To: CPU Temperature

sh
#!/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
done

Config:

toml
# ~/.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:

txt
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:

txt
event {"id":"toggle-vpn","type":"click","source":"popover","button":"left"}

Run your command, then print updated status and popover lines.

Best Practices

PracticeWhy
Print status immediatelyThe panel should not sit empty while your script warms up.
Keep panel labels shortLong labels make the panel jump and crowd other applets.
Put detail in the popoverThe panel is for glanceable state; popovers are for explanations and controls.
Use stable idsEvents are easier to handle when ids do not change between updates.
Throttle pollingMost system stats do not need sub-second updates.
Send complete updatesTreat each status or popover line as the current truth.
Use variants sparinglywarning and danger should mean something needs attention.
Validate JSON before runningBad JSON is ignored and logged.
Keep stderr quietUse stderr for useful diagnostics, not a constant stream.
Set only the environment you needUse env_clear and env when a child process should not inherit the shell environment.
Prefer one script per concernA small CPU applet is easier to maintain than one giant script for everything.

See Also

PageCovers
Exec AppletApplet config and options.
ComponentsPopover component fields and component types.
Exec SDKSDK installation and language examples.