Core: lifecycle, layout & runtime
The always-loaded core (lib/tui.sh, lib/tui_api.sh, lib/state.sh): starting the app, building the pane tree, tabs, runtime (factory) widgets, output, background process streaming, live updates, getters, logging and the event/result variables everything else reads. ← API index for the full module list and a task-oriented tour with examples.
Each function has its own page; the summary tables link to them, and the full entries follow each table. Functions return 0 unless their entry says otherwise. Getters print to stdout, so call them in $(...); the fork-free alternatives that set variables are noted on each entry.
Lifecycle
tui.start is the usual entry point. tui.init + tui.run are the manual form for apps that build their UI in code.
| Function | Summary |
|---|---|
tui.start |
Runs a markup-driven app: validates the page, initializes the terminal, loads FILE and runs the main loop until the app quits. |
tui.start_cached |
Like tui.start, but first records every page the app can reach into the page cache, so later page switches replay instead of parsing. |
tui.init |
Takes over the terminal and prepares an empty root pane. |
tui.run |
Runs the main loop (input, dispatch, render, ticks) until tui.stop is called, then restores the terminal. |
tui.stop |
Asks the main loop to exit after the current iteration. |
tui.cleanup |
Restores the terminal: mouse tracking and bracketed paste off, style reset, cursor shown, main screen, unread input drained, original stty settings back. |
tui.require |
Sources an optional bundled library once per process. |
tui.tick.add |
Registers FN to run once per main-loop iteration. |
tui.tick.remove |
Unregisters a tick listener. Unknown names are ignored. |
tui.on_resize |
Marks the terminal as resized; the main loop re-lays out on its next iteration. |
tui.start
tui.start FILE
Runs a markup-driven app: validates the page, initializes the terminal, loads FILE and runs the main loop until the app quits.
Parameters
FILE: the first page (XML).
Returns: 1 when FILE is unreadable, fails validation or fails to load; otherwise returns after the app quits.
Notes
- Validates
FILEand its includes before the terminal is taken over. Errors are printed to stderr and abort the start, unlessTUI_IGNORE_INVALID_XML=1(set bydabt --ignore-invalid-xml).TUI_VALIDATE=0skips validation. - Sets
TUI_THEMES_DIRto the page’sthemes/folder (else the defaults’themes/) when it is not already set. - The terminal is restored on every exit path: normal quit,
INT/TERM, or a load failure. - Uses the page cache for later
tui.gotocalls but does not pre-warm it; seetui.start_cached.
Example
#!/usr/bin/env bash
TUI_APP_NAME=my_app # before sourcing: names the config folder and log file
source /path/to/lib/tui.sh
tui.start "$APP/config/home.xml"
See also: tui.start_cached, tui.init, tui.run
tui.start_cached
tui.start_cached FIRST_PAGE
Like tui.start, but first records every page the app can reach into the page cache, so later page switches replay instead of parsing.
Parameters
FIRST_PAGE: the page to open. Every*.xmlnext to it and every shipped default page (share/defaults/pages/) is warmed too.
Returns: as tui.start.
Notes
- Files whose name starts with
_(fragments for<include>) are skipped. - All warmed pages are validated before the terminal is taken over, not only
FIRST_PAGE. - Only pages that are missing from the on-disk cache or changed since are re-recorded, behind a progress banner. The cache lives in
tui.cache.disk_dir.
See also: tui.cache.warm_with_spinner, tui.load_cached
tui.init
tui.init
Takes over the terminal and prepares an empty root pane.
Notes
- Applies the saved config, switches to raw input (
stty -echo -icanon -isig -ixon -iexten), enters the alternate screen, hides the cursor, and turns on mouse tracking and bracketed paste. - Because of
-isig,ctrl+candctrl+zarrive as keys instead of signals. - Records the run in
app.meta, starts enabled plugins and fires theinithook. tui.startcalls it. Call it yourself only when building the UI in code; pair it withtui.run.
See also: tui.cleanup
tui.run
tui.run
Runs the main loop (input, dispatch, render, ticks) until tui.stop is called, then restores the terminal.
Notes
- Fires the
readyhook once, before the first frame. - Installs
INT/TERMtraps (clean up and exit 1) and aWINCHtrap for resizes. - Polls input every 0.05 s while tick listeners exist, else every 0.2 s (
TUI_INPUT_POLL_TIMEOUT,TUI_INPUT_IDLE_TIMEOUT). - Restores the terminal before it returns, so code after
tui.runprints to the normal screen.
Example
source lib/tui.sh
tui.init
tui.vsplit root top:1 bottom:3
tui.output bottom "hello"
tui.run
See also: tui.start
tui.stop
tui.stop
Asks the main loop to exit after the current iteration.
Notes
- Fires the
quithook first. - Never asks for confirmation. Bind
tui.action.quitinstead to respect theconfirm.quitsetting.
See also: tui.run
tui.cleanup
tui.cleanup
Restores the terminal: mouse tracking and bracketed paste off, style reset, cursor shown, main screen, unread input drained, original stty settings back.
Notes
tui.runand the signal traps already call it. Call it yourself only when you calledtui.initwithouttui.run.
See also: tui.init
tui.require
tui.require LIBRARY
Sources an optional bundled library once per process.
Parameters
LIBRARY:terminal_renderer,terminal_controlsortui_scan.
Returns: 1 and a message on stderr for an unknown name.
Notes
- Callback files are re-sourced on every page visit. Use
tui.requirethere instead ofsourceso a large library is loaded only once.
Example
tui.require terminal_renderer
tui.output stats "$(table_string 'Name|Size' 'a.txt|1k')"
tui.tick.add
tui.tick.add FN
Registers FN to run once per main-loop iteration.
Parameters
FN: function name, called with no arguments.
Notes
- Adding the same
FNtwice is a no-op. - Listeners survive page changes. Remove page-specific listeners with
tui.tick.remove, or usetui.every, which is cleared on page change. - While any listener exists the loop polls input every 0.05 s instead of 0.2 s, so an idle app stays cheap only when nothing is registered.
- Never assign
_TUI_TICK_FNfor this: it is a single slot, and overwriting it stops other tick users such astui.exec. - A listener registered while a plugin loads is removed when that plugin is disabled.
See also: tui.every
tui.tick.remove
tui.tick.remove FN
Unregisters a tick listener. Unknown names are ignored.
Parameters
FN: the function passed totui.tick.add.
tui.on_resize
tui.on_resize
Marks the terminal as resized; the main loop re-lays out on its next iteration.
Notes
- Installed as the
WINCHhandler bytui.run. You rarely call it yourself. - A drag-resize is coalesced: the loop waits until no resize arrived for about 40 ms, then lays out once and fires the
resizehook.
Layout (panes)
Panes form a tree rooted at root. Splits divide a pane among children by integer weight; leaves hold widgets or output. Pane names are used in bash variable names, so use letters, digits and _ only.
| Function | Summary |
|---|---|
tui.hsplit |
Splits PARENT into side-by-side child panes. |
tui.vsplit |
Splits PARENT into stacked child panes, top to bottom. |
tui.grid |
Lays NAME... out as a grid of cells inside PARENT, row by row. |
tui.fixed |
Lays children out as fixed-size cells that flow left to right and wrap, like keys on a keyboard. |
tui.pane_title |
Sets the title drawn in the pane’s top border. |
tui.pane_border |
Sets the border style of a pane. |
tui.pane_pad |
Sets blank columns (HPAD) and rows (VPAD) on each side inside the pane. |
tui.pane_align |
Sets the default horizontal alignment for widgets in the pane. |
tui.pane_valign |
Sets the default vertical anchor for widgets in the pane. Default: top. |
tui.pane_minsize |
Sets the smallest size at which the pane shows its content. Below it, the pane shows a min space = WxH notice instead. |
tui.pane_maxsize |
Caps the size a split gives the pane. |
tui.pane_scroll |
Enables scrolling of the pane’s output and resets its scroll position to the top left. |
tui.pane_strict_fit |
Turns the automatic content-fit check of a leaf pane on (default) or off. |
tui.pane_size |
Stores the usable content size of a pane (inside border and padding) in variables, without a subshell. |
tui.content_area |
Prints the content rectangle of a pane as ROW COL HEIGHT WIDTH (1-based, inside border and padding). |
tui.pane.focus |
Makes PANE the keyboard pane: the target of scroll keys, drawn with a highlighted border. |
tui.relayout |
Recomputes geometry and repaints in one synchronized frame. Use it after changing borders, padding, titles or splits while the app runs. |
tui.clear_pane |
Blanks the pane’s content rectangle on screen. |
tui.render |
Repaints every pane, widget and output now, as one buffered frame. |
tui.redraw |
Same as tui.render. |
tui.hsplit
tui.hsplit PARENT NAME[:WEIGHT]...
Splits PARENT into side-by-side child panes.
Parameters
PARENT: an existing pane (rootat first).NAME[:WEIGHT]: one per child, left to right.WEIGHTis a positive integer share of the width. Default:1.
Notes
- Pane names become part of bash variable names: use letters, digits and
_only. - Each child starts with a
singleborder and no title. The last child absorbs rounding leftovers.
Example
tui.hsplit root nav main:3 # nav gets 1/4 of the width, main 3/4
See also: tui.vsplit, tui.grid, tui.fixed
tui.vsplit
tui.vsplit PARENT NAME[:WEIGHT]...
Splits PARENT into stacked child panes, top to bottom.
Parameters
PARENT: an existing pane.NAME[:WEIGHT]: one per child.WEIGHTis a positive integer share of the height. Default:1.
Notes
- Same rules as
tui.hsplit: identifier-safe names,singleborder by default.
Example
tui.vsplit main header:1 body:5 status:1
See also: tui.hsplit
tui.grid
tui.grid PARENT ROWS COLS FIT ROW_WEIGHTS COL_WEIGHTS NAME...
Lays NAME... out as a grid of cells inside PARENT, row by row.
Parameters
ROWS,COLS: grid size. Either may be""and is then computed from the number of names; both empty gives a near-square grid.FIT: what happens to cells with no name.pack(default when"") keeps them as empty panes;stretchlets the row’s other cells absorb them.ROW_WEIGHTS,COL_WEIGHTS: space-separated weights, e.g."1 2".""= all1.NAME...: cell pane names, row-major.
Notes
- All six leading parameters are positional; pass
""for the ones you skip. - Built from a
tui.vsplitinto rows namedPARENT_row0,PARENT_row1, … and antui.hsplitper row. Blankpackcells are namedPARENT_rowR_cC_blank. tui.get.splitonPARENTprintsv.
Example
tui.grid dash 2 3 pack "" "2 1 1" cpu mem disk net io load
See also: tui.factory.grid, tui.fixed
tui.fixed
tui.fixed PARENT CELL_W CELL_H CHILD[:SPAN[:nl]]...
Lays children out as fixed-size cells that flow left to right and wrap, like keys on a keyboard.
Parameters
CELL_W,CELL_H: size of one cell in columns and rows.CHILD: pane name.SPANmakes it that many cells wide (default1). A third field (any text, e.g.nl) starts a new row before this child.
Notes
- Sizes never stretch with the window. Children that don’t fit get a 0×0 rectangle and are not drawn.
- Children start with no border.
tui.get.splitonPARENTprintsf.
Example
tui.fixed keys 6 3 esc f1 f2 f3 tab:2:nl q w e
See also: tui.grid
tui.pane_title
tui.pane_title PANE TEXT
Sets the title drawn in the pane’s top border.
Notes
- Not drawn when the pane has no border.
- While the app runs, call
tui.relayoutPANEto show the change.
tui.pane_border
tui.pane_border PANE STYLE
Sets the border style of a pane.
Parameters
STYLE:single,double,heavyornone. Any other value drawssingle.
Notes
- A parent pane draws its border only when it was set with this function and there is room for it; leaf panes draw a
singleborder by default. - A border that doesn’t fit (leaf under 3×5 cells) is dropped automatically.
- While the app runs, call
tui.relayoutto apply it; the border changes the content area.
See also: tui.get.border
tui.pane_pad
tui.pane_pad PANE HPAD VPAD
Sets blank columns (HPAD) and rows (VPAD) on each side inside the pane.
Parameters
HPAD,VPAD: cell counts.""leaves that value unchanged.
Notes
- On a parent pane: the gap between its frame and its children. On a leaf: shrinks the area for widgets and output.
- Padding is clamped so at least one row and column remain.
- While the app runs, call
tui.relayoutto apply it.
See also: tui.get.pad, tui.pad
tui.pane_align
tui.pane_align PANE left|center|right|fill
Sets the default horizontal alignment for widgets in the pane.
Notes
- A widget’s own
tui.alignwins. Without either, widgets are left-aligned and buttons centered. - Affects widgets only, not text from
tui.output.
tui.pane_valign
tui.pane_valign PANE top|middle|bottom
Sets the default vertical anchor for widgets in the pane. Default: top.
Notes
- A widget’s
ROWcounts from the anchor: withbottom, row0is the last line and higher rows go up. - A widget’s own
tui.valignwins.
tui.pane_minsize
tui.pane_minsize PANE [MIN_W] [MIN_H]
Sets the smallest size at which the pane shows its content. Below it, the pane shows a min space = WxH notice instead.
Parameters
MIN_W,MIN_H: columns and rows. Empty leaves that value unchanged.
Notes
- Leaf panes also get an automatic minimum from their content unless
tui.pane_strict_fitisfalse.
See also: tui.pane_maxsize
tui.pane_maxsize
tui.pane_maxsize PANE [MAX_W] [MAX_H]
Caps the size a split gives the pane.
Parameters
MAX_W,MAX_H: columns and rows. Empty leaves that value unchanged.
Notes
- Space the cap takes away is passed to the pane’s last sibling. When the capped pane is itself the last child, that space stays empty.
See also: tui.pane_minsize
tui.pane_scroll
tui.pane_scroll PANE none|v|h|both
Enables scrolling of the pane’s output and resets its scroll position to the top left.
Notes
- Scrolling applies to
tui.outputcontent: wheel,j/k, page keys and a draggable scrollbar. - Calling it again, even with the same mode, jumps back to the top.
See also: tui.get.scroll, tui.action.scroll
tui.pane_strict_fit
tui.pane_strict_fit PANE true|false
Turns the automatic content-fit check of a leaf pane on (default) or off.
Notes
- With the check on, a pane whose widgets need more room than it has shows the
min spacenotice.falsefalls back to the explicittui.pane_minsizeonly; use it for panes where clipping is fine (tab headers do this).
tui.pane_size
tui.pane_size PANE
Stores the usable content size of a pane (inside border and padding) in variables, without a subshell.
Returns: 1 for an unknown pane.
Sets: TUI_PANE_ROWS, TUI_PANE_COLS.
Example
tui.pane_size status
printf -v rule '%*s' "$TUI_PANE_COLS" ''
tui.set_text status "${rule// /─}" # a rule exactly as wide as the pane
See also: tui.get.dimensions
tui.content_area
tui.content_area PANE
Prints the content rectangle of a pane as ROW COL HEIGHT WIDTH (1-based, inside border and padding).
Notes
- Does not check that
PANEexists. Prefertui.get.content_area, which returns1for an unknown pane.
tui.pane.focus
tui.pane.focus PANE
Makes PANE the keyboard pane: the target of scroll keys, drawn with a highlighted border.
Parameters
PANE: pane id, or""to clear.
Notes
- Focusing a widget also moves pane focus to the widget’s pane.
- Only the two affected borders are redrawn.
See also: tui.get.pane_focus, tui.action.focus_pane
tui.relayout
tui.relayout [PANE]
Recomputes geometry and repaints in one synchronized frame. Use it after changing borders, padding, titles or splits while the app runs.
Parameters
PANE: a leaf pane to repaint alone, without clearing the screen. Omitted (or a parent pane): lay out and repaint everything.
Notes
- Before
tui.runit only recomputes geometry.
Example
tui.pane_border side double
tui.relayout
tui.clear_pane
tui.clear_pane PANE
Blanks the pane’s content rectangle on screen.
Notes
- Writes to the terminal directly and does not change the pane’s content; the next repaint draws it again. Use
tui.output_clearto empty the content.
tui.render
tui.render
Repaints every pane, widget and output now, as one buffered frame.
Notes
- Expensive. Content functions (
tui.output,tui.set_text,tui.update) already queue their own repaint, coalesced per frame; don’t calltui.renderafter them or from timers. - Does not recompute geometry; use
tui.relayoutafter layout changes.
See also: tui.redraw
tui.redraw
tui.redraw
Same as tui.render.
Tabs
A tab group is a header pane with one button per tab and a content pane that the active tab’s action fills. In markup: <tabs>, see ../guide/grid-layouts-and-tabs.md.
| Function | Summary |
|---|---|
tui.tabs.add |
Registers one tab before the group is built. |
tui.tabs.compact |
Picks the header style of a tab group. Call it before tui.tabs.build. |
tui.tabs.build |
Builds the header row for registered tabs and activates the default tab (or the first). |
tui.tabs.activate |
Makes a tab active: focuses its header button and calls its action as ACTION TAB_ID. |
tui.tabs.add
tui.tabs.add TAB_ID TEXT ACTION [DEFAULT]
Registers one tab before the group is built.
Parameters
TAB_ID: widget id of the tab’s header button.TEXT: header caption.ACTION: function called asACTION TAB_IDwhenever the tab is activated. It fills the content pane.DEFAULT:trueto activate this tab when the group is built. Any other value counts as not default.
See also: tui.tabs.build
tui.tabs.compact
tui.tabs.compact TABS_ID [true|false]
Picks the header style of a tab group. Call it before tui.tabs.build.
Parameters
TABS_ID: the group id passed totui.tabs.build.true(default): one-row, borderless headers styled with.tab_header_compact.false: framed headers (.tab_header), which need 3 rows.
tui.tabs.build
tui.tabs.build TABS_ID HEADER_PANE CONTENT_PANE TAB_ID...
Builds the header row for registered tabs and activates the default tab (or the first).
Parameters
TABS_ID: a name for the group.HEADER_PANE: leaf pane that receives a one-row grid of header buttons.CONTENT_PANE: the pane the tab actions fill.TAB_ID...: tabs registered withtui.tabs.add, in display order.
Notes
- Header cells are named
HEADER_PANE_TAB_ID_celland skip the content-fit check, so long captions clip instead of showing the size notice. - The active tab is shown with the header button’s focus style.
- The framework does not clear
CONTENT_PANEbetween tabs; each action replaces what it needs.
Example
tui.tabs.add tab_log "Log" show_log true
tui.tabs.add tab_conf "Config" show_conf
tui.tabs.compact main_tabs
tui.tabs.build main_tabs tab_bar tab_body tab_log tab_conf
See also: tui.tabs.activate
tui.tabs.activate
tui.tabs.activate TAB_ID
Makes a tab active: focuses its header button and calls its action as ACTION TAB_ID.
Notes
- Does nothing for a tab that is not part of a built group.
Factory (runtime widgets)
Constructors for layouts whose shape is only known at runtime. Ids are generated and tracked per namespace, so one call tears everything down again.
| Function | Summary |
|---|---|
tui.factory.label |
Creates a label with a generated id, tracked under namespace NS. |
tui.factory.button |
Creates a button with a generated id, tracked under namespace NS. ACTION is called as ACTION ID. |
tui.factory.input |
Creates a single-line input with a generated id, tracked under namespace NS. |
tui.factory.checkbox |
Creates a checkbox with a generated id, tracked under namespace NS. |
tui.factory.grid |
Builds a grid of COUNT cells with generated pane ids, for layouts whose size is only known at runtime. |
tui.factory.clear |
Removes every widget and pane created under namespace NS and turns its grid parents back into empty leaf panes. |
tui.factory.label
tui.factory.label NS PANE ROW TEXT
Creates a label with a generated id, tracked under namespace NS.
Sets: _TUI_FACTORY_LAST_ID to the new id (__f_NS_N).
Notes
- The id is not printed: stdout is the screen. Read
_TUI_FACTORY_LAST_IDright after the call. _TUI_FACTORY_LAST_IDand_TUI_FACTORY_GRID_CELLSare the only underscore names app code may read.
See also: tui.label, tui.factory.clear
tui.factory.button
tui.factory.button NS PANE ROW TEXT [ACTION]
Creates a button with a generated id, tracked under namespace NS. ACTION is called as ACTION ID.
Sets: _TUI_FACTORY_LAST_ID.
Example
for f in *.log; do
tui.factory.button files list_pane "$((row++))" "$f" open_file
done
See also: tui.button
tui.factory.input
tui.factory.input NS PANE ROW [PLACEHOLDER] [LABEL] [SUBMIT]
Creates a single-line input with a generated id, tracked under namespace NS.
Sets: _TUI_FACTORY_LAST_ID.
See also: tui.input
tui.factory.checkbox
tui.factory.checkbox NS PANE ROW LABEL [CHECKED] [ACTION]
Creates a checkbox with a generated id, tracked under namespace NS.
Sets: _TUI_FACTORY_LAST_ID.
See also: tui.checkbox
tui.factory.grid
tui.factory.grid NS PARENT COUNT [COLS] [FIT] [ROW_WEIGHTS] [COL_WEIGHTS]
Builds a grid of COUNT cells with generated pane ids, for layouts whose size is only known at runtime.
Parameters
COUNT: number of cells.COLS: columns. Omitted: near-square.FIT,ROW_WEIGHTS,COL_WEIGHTS: as intui.grid.
Sets: _TUI_FACTORY_GRID_CELLS (array of cell ids, in order).
Example
tui.factory.clear tiles
tui.factory.grid tiles board "${#items[@]}" 4
for i in "${!items[@]}"; do
tui.factory.button tiles "${_TUI_FACTORY_GRID_CELLS[$i]}" 0 "${items[$i]}" pick
done
tui.relayout
See also: tui.factory.clear
tui.factory.clear
tui.factory.clear NS
Removes every widget and pane created under namespace NS and turns its grid parents back into empty leaf panes.
Notes
- Safe on a namespace that is empty or already cleared.
- Does not repaint. Call
tui.relayoutafter rebuilding. - Everything is also dropped on page change.
Content and output
Text content of leaf panes. Repaints are queued and coalesced, so several updates in one callback cost one frame.
| Function | Summary |
|---|---|
tui.output |
Replaces the pane’s content with text, or with stdin when no TEXT is given. |
tui.output_append |
Appends lines to the pane’s content, from arguments or stdin. |
tui.output_clear |
Empties the pane’s content and repaints the pane immediately. |
tui.set_text |
Replaces the pane’s content like tui.output, but skips the work when TEXT equals what it set last time. |
tui.output
tui.output PANE [TEXT...]
some_command | tui.output PANE
Replaces the pane’s content with text, or with stdin when no TEXT is given.
Parameters
TEXT...: joined with spaces, then split on newlines. ANSI escapes are kept.
Notes
- The repaint is queued and coalesced with other changes in the same frame.
PANEmust be a valid bash identifier (letters, digits,_): the content is stored in a variable named after it.- Reading stdin (
cmd | tui.output) costs a pipeline fork; for text updated in a loop usetui.set_text. - Don’t mix
tui.outputandtui.set_texton the same pane:tui.set_textskips text it thinks is already shown.
Example
tui.output log "line one"
tui.output log "$(ls -la)"
df -h | tui.output disk
See also: tui.output_append, tui.pane_scroll
tui.output_append
tui.output_append PANE [TEXT...]
some_command | tui.output_append PANE
Appends lines to the pane’s content, from arguments or stdin.
Notes
- Content is kept in memory without a limit. For a long-running log, trim it yourself or use
tui.exec.
See also: tui.output
tui.output_clear
tui.output_clear PANE
Empties the pane’s content and repaints the pane immediately.
tui.set_text
tui.set_text PANE TEXT
Replaces the pane’s content like tui.output, but skips the work when TEXT equals what it set last time.
Notes
- Fork-free. Use it for anything updated from timers or tick functions.
- The cache is per pane and cleared on page change. After changing the pane through another function, the next
tui.set_textwith the old text is skipped; don’t mix them on one pane.
Example
refresh() { tui.sys.cpu; tui.set_text cpu "CPU ${TUI_SYS_CPU}%"; }
tui.every 1 refresh
Process streaming (tui.exec)
| Function | Summary |
|---|---|
tui.exec |
Runs a shell command in a pseudo-terminal and streams its output into a pane while the UI stays responsive. |
tui.exec.cancel_pane |
Kills and dismisses every tui.exec instance targeting PANE, running or finished, and empties the pane. |
tui.exec
tui.exec CMD OUT_PANE [CTL_PANE]
Runs a shell command in a pseudo-terminal and streams its output into a pane while the UI stays responsive.
Parameters
CMD: command line, run byscript -q -e -c CMD.OUT_PANE: pane that shows the output.CTL_PANE: optional pane that gets Cancel, Save Output, View Command, Retry and Back buttons plus an input box that sends a line to the process’s stdin.
Returns: 1 when CMD or OUT_PANE is empty.
Notes
- Several instances may run at once, also into the same pane. Each gets an instance id (
e1,e2, …); later instances in a shared pane print a── [eN] started ──separator. - Needs the util-linux
script. BSD/macOSscripttakes different options. - Running instances are killed on page change and on exit.
- Save Output writes
exec_output_<id>_<timestamp>.loginto the current directory. - To restart cleanly in a pane (one process at a time), call
tui.exec.cancel_panefirst.
Example
tui.exec "ping -c 5 example.com" out ctl
tui.exec.cancel_pane
tui.exec.cancel_pane PANE
Kills and dismisses every tui.exec instance targeting PANE, running or finished, and empties the pane.
Example
on_visit_shell() {
tui.exec.cancel_pane term # one shell per visit, not one more per visit
tui.exec "bash -i" term
}
Live updates
Timers, clocks, watches and the system samplers share one tick listener, cost no fork per update, and are cleared on page change and exit.
| Function | Summary |
|---|---|
tui.every |
Calls FN repeatedly on a timer from the main loop. |
tui.after |
Calls FN ID once, SEC seconds from now. |
tui.every.cancel |
Removes a timer created by tui.every, tui.after, tui.clock or tui.monitor. Unknown ids are ignored. |
tui.every.pause |
Stops a timer from firing until tui.every.resume is called. |
tui.every.resume |
Resumes a paused timer. It fires on the next loop iteration, then at its normal interval. |
tui.every.clear |
Removes every timer, including clocks and monitors. Watches from tui.watch keep running. |
tui.every.list |
Prints one line per timer. |
tui.clock |
Shows a live clock in a pane, updated on every whole second. |
tui.watch |
Runs a shell command every SEC seconds in the background and shows its latest output in a pane, like watch. |
tui.watch.stop |
Stops a watch and kills its background process. Unknown ids are ignored. |
tui.watch.now |
Restarts a watch so its command runs again right away. |
tui.monitor |
Shows a ready-made system dashboard in a pane: CPU, memory and swap gauges, a CPU history sparkline, memory in MB, load average and uptime. |
tui.sys.cpu |
Samples CPU usage since the previous call, fork-free. |
tui.sys.mem |
Samples memory and swap usage from /proc/meminfo, fork-free. |
tui.sys.load |
Reads the load averages from /proc/loadavg. |
tui.sys.uptime |
Reads the system uptime from /proc/uptime. |
tui.hist.push |
Appends a value to a named rolling series, dropping the oldest beyond MAX. |
tui.hist.get |
Prints a series, oldest first. |
tui.every
tui.every SEC FN [ID]
Calls FN repeatedly on a timer from the main loop.
Parameters
SEC: interval in seconds; decimals allowed (0.25). Values below0.05are raised to0.05.FN: function, called asFN ID.ID: timer id fortui.every.canceland friends. Default:FN.
Notes
- The first call happens on the next loop iteration, not after
SEC. Usetui.afterfor a delayed start. - Registering an existing
IDagain replaces that timer and resumes it if paused. Pass distinct IDs to run one function on two timers. - Timers are removed on page change. Register them again from the page’s
on_visit. - After a terminal resize every timer runs on the next iteration, so size-dependent output re-fits at once.
FNruns inside the main loop: keep it fork-free (tui.set_text,tui.sys.*). Slow work belongs intui.watch.- Timers registered while a plugin loads are removed when that plugin is disabled.
Example
refresh_stats() {
tui.sys.cpu; tui.sys.mem
tui.set_text stats "CPU ${TUI_SYS_CPU}% MEM ${TUI_SYS_MEM_PCT}%"
}
tui.every 2 refresh_stats
See also: tui.after, tui.tick.add, tui.watch
tui.after
tui.after SEC FN [ID]
Calls FN ID once, SEC seconds from now.
Parameters
- As
tui.every.
Notes
- The timer is removed before
FNruns, soFNmay schedule itself again with the sameID. - Cancelled by page change like every timer.
Example
tui.notify "Saved" success
tui.after 3 clear_status
tui.every.cancel
tui.every.cancel ID
Removes a timer created by tui.every, tui.after, tui.clock or tui.monitor. Unknown ids are ignored.
tui.every.pause
tui.every.pause ID
Stops a timer from firing until tui.every.resume is called.
tui.every.resume
tui.every.resume ID
Resumes a paused timer. It fires on the next loop iteration, then at its normal interval.
tui.every.clear
tui.every.clear
Removes every timer, including clocks and monitors. Watches from tui.watch keep running.
tui.every.list
tui.every.list
Prints one line per timer.
Output: ID<TAB>INTERVALms<TAB>FN, e.g. refresh_stats 2000ms refresh_stats.
tui.clock
tui.clock PANE [FORMAT] [FONT] [ID]
Shows a live clock in a pane, updated on every whole second.
Parameters
FORMAT:strftimeformat. Default:%H:%M:%S.FONT: a banner font (block5,seg3,box3,blk3,half2) for big digits, styled with the pane’s colors. Empty: plain text.ID: timer id. Default:clock_PANE.
Notes
- Stops itself when
PANEno longer exists. - Loads
terminal_renderer.shon first use of a font.
Example
tui.clock header_clock "%a %H:%M" seg3
tui.watch
tui.watch PANE CMD [SEC] [ID]
Runs a shell command every SEC seconds in the background and shows its latest output in a pane, like watch.
Parameters
CMD: shell code, evaluated withevalin a background subshell.SEC: interval. Default:2. Minimum0.05.ID: watch id. Default:watch_PANE.
Returns: 1 when PANE or CMD is empty.
Notes
- The UI never waits for
CMD. Only the newest complete output is shown; stderr is discarded. - Starting a watch with an existing
IDrestarts it. - Stopped on page change, on exit, and when
PANEno longer exists.
Example
tui.watch disk "df -h /" 5
See also: tui.watch.stop, tui.watch.now
tui.watch.stop
tui.watch.stop ID
Stops a watch and kills its background process. Unknown ids are ignored.
tui.watch.now
tui.watch.now ID
Restarts a watch so its command runs again right away.
Returns: 1 for an unknown id.
Example
on_refresh() { tui.watch.now watch_disk; }
tui.monitor
tui.monitor PANE [SEC] [ID]
Shows a ready-made system dashboard in a pane: CPU, memory and swap gauges, a CPU history sparkline, memory in MB, load average and uptime.
Parameters
SEC: refresh interval. Default:1.ID: timer id. Default:monitor_PANE.
Notes
- Linux only: reads
/proc. - Gauges size themselves to the pane width.
tui.sys.cpu
tui.sys.cpu
Samples CPU usage since the previous call, fork-free.
Sets: TUI_SYS_CPU (integer percent).
Returns: 1 when /proc/stat can’t be read.
Notes
- The first call measures since boot. Call it once to prime, then read it on a timer.
- Linux only.
tui.sys.mem
tui.sys.mem
Samples memory and swap usage from /proc/meminfo, fork-free.
Sets: TUI_SYS_MEM_PCT, TUI_SYS_MEM_USED_MB, TUI_SYS_MEM_TOTAL_MB, TUI_SYS_SWAP_PCT.
Notes
- “Used” is total minus available, so file cache does not count as used. Linux only.
tui.sys.load
tui.sys.load
Reads the load averages from /proc/loadavg.
Sets: TUI_SYS_LOAD1, TUI_SYS_LOAD5, TUI_SYS_LOAD15 (decimal strings, e.g. 0.42).
tui.sys.uptime
tui.sys.uptime
Reads the system uptime from /proc/uptime.
Sets: TUI_SYS_UPTIME (seconds), TUI_SYS_UPTIME_STR (2d 03h 14m).
tui.hist.push
tui.hist.push NAME VALUE [MAX]
Appends a value to a named rolling series, dropping the oldest beyond MAX.
Parameters
VALUE: one word; values are stored space-separated.MAX: series length. Default:60.
Example
tui.sys.cpu; tui.hist.push cpu "$TUI_SYS_CPU" 120
See also: tui.hist.get
tui.hist.get
tui.hist.get NAME
Prints a series, oldest first.
Output: values separated by spaces, no trailing newline. Charts such as linechart expect commas: v=$(tui.hist.get cpu); linechart "cpu:${v// /,}".
Getters
Read-only. Pane getters return 1 for an unknown pane.
| Function | Summary |
|---|---|
tui.get.dimensions |
Prints the size of the terminal or of a pane. |
tui.get.terminal |
Prints the terminal size as ROWS COLS. |
tui.get.position |
Prints the top-left corner of a pane as ROW COL (1-based). Returns 1 for an unknown pane. |
tui.get.rect |
Prints the outer rectangle of a pane as ROW COL HEIGHT WIDTH. Returns 1 for an unknown pane. |
tui.get.content_area |
Prints the usable rectangle inside border and padding as ROW COL HEIGHT WIDTH. Returns 1 for an unknown pane. |
tui.get.border |
Prints the border actually drawn: single, double, heavy or none. Returns 1 for an unknown pane. |
tui.get.title |
Prints the pane title (empty when none). Returns 1 for an unknown pane. |
tui.get.pad |
Prints the pane padding as HPAD VPAD. Returns 1 for an unknown pane. |
tui.get.split |
Prints how a pane is split: h, v, f (fixed) or empty for a leaf. Returns 1 for an unknown pane. |
tui.get.children |
Prints the child pane ids on one line, space-separated (empty for a leaf). Returns 1 for an unknown pane. |
tui.get.parent |
Prints the id of the pane that contains PANE. Returns 1 for root or an unknown pane. |
tui.get.panes |
Prints every pane id, one per line; with leaves, only panes without children. |
tui.get.lines |
Prints the number of output lines a pane holds (0 for none or an unknown pane). |
tui.get.scroll |
Prints the scroll state as MODE V_OFFSET H_OFFSET LINES MAX_WIDTH, e.g. v 12 0 240 80. Returns 1 for an unknown pane. |
tui.get.style |
Prints one resolved style field of a pane or widget. |
tui.get.widgets |
Prints widget ids in draw order, one per line: all of them, or only those in PANE. |
tui.get.type |
Prints the widget type: label, button, input, checkbox, password, textarea, list, table, select or progress. Returns 1 for an unknown id. |
tui.get.pane |
Prints the pane a widget lives in. Returns 1 for an unknown id. |
tui.get.action |
Prints the action function of a widget (empty when none or unknown). |
tui.get.checked |
Prints nothing; returns 0 when ID is a checked checkbox, else 1. |
tui.get.focused |
Prints the id of the focused widget (empty when none). |
tui.get.hovered |
Prints the id of the widget (default) or pane under the mouse pointer (empty when none). |
tui.get.pane_focus |
Prints the keyboard pane: the scroll target with the highlighted border (empty when none). |
tui.get.page |
Prints the absolute path of the current page file (empty before a page is loaded). |
tui.get.pages |
Prints the pages linked by a <button page="…"> so far, sorted, one per line as PAGE<TAB>TITLE. |
tui.get.event |
Prints the current input event on one line: type=… key=… x=… y=… pane=… widget=… button=…. |
tui.get.classes |
Prints every key the loaded theme defines, sorted, one per line. |
tui.get.class.style |
Prints one field of a theme class, without a pane or widget. |
tui.get.dimensions
tui.get.dimensions [-r|--rows] [-c|--columns] [--content] [PANE]
Prints the size of the terminal or of a pane.
Parameters
-r,--rows/-c,--columns: print only that number.--content: the pane’s usable area inside border and padding.PANE: omitted = the terminal.
Output: ROWS COLS, or one number with -r/-c.
Returns: 1 for an unknown pane.
Example
read -r rows cols <<<"$(tui.get.dimensions --content chart)"
cols=$(tui.get.dimensions -c) # terminal width
See also: tui.pane_size (same numbers without a subshell)
tui.get.terminal
tui.get.terminal
Prints the terminal size as ROWS COLS.
tui.get.position
tui.get.position PANE
Prints the top-left corner of a pane as ROW COL (1-based). Returns 1 for an unknown pane.
tui.get.rect
tui.get.rect PANE
Prints the outer rectangle of a pane as ROW COL HEIGHT WIDTH. Returns 1 for an unknown pane.
tui.get.content_area
tui.get.content_area PANE
Prints the usable rectangle inside border and padding as ROW COL HEIGHT WIDTH. Returns 1 for an unknown pane.
tui.get.border
tui.get.border PANE
Prints the border actually drawn: single, double, heavy or none. Returns 1 for an unknown pane.
Notes
- This is the effective style: a border that does not fit, or an unset border on a parent pane, prints
none.
tui.get.title
tui.get.title PANE
Prints the pane title (empty when none). Returns 1 for an unknown pane.
tui.get.pad
tui.get.pad PANE
Prints the pane padding as HPAD VPAD. Returns 1 for an unknown pane.
tui.get.split
tui.get.split PANE
Prints how a pane is split: h, v, f (fixed) or empty for a leaf. Returns 1 for an unknown pane.
Notes
- A pane built with
tui.gridprintsv: a grid is a vertical split into rows.
tui.get.children
tui.get.children PANE
Prints the child pane ids on one line, space-separated (empty for a leaf). Returns 1 for an unknown pane.
tui.get.parent
tui.get.parent PANE
Prints the id of the pane that contains PANE. Returns 1 for root or an unknown pane.
tui.get.panes
tui.get.panes [leaves]
Prints every pane id, one per line; with leaves, only panes without children.
tui.get.lines
tui.get.lines PANE
Prints the number of output lines a pane holds (0 for none or an unknown pane).
tui.get.scroll
tui.get.scroll PANE
Prints the scroll state as MODE V_OFFSET H_OFFSET LINES MAX_WIDTH, e.g. v 12 0 240 80. Returns 1 for an unknown pane.
tui.get.style
tui.get.style ID FIELD [STATE]
Prints one resolved style field of a pane or widget.
Parameters
FIELD:fg,bgormods.STATE:normal(default),focus,border,title,hover,checked,unchecked. A state with no rules falls back tonormal.
Returns: 2 and a message on stderr for any other FIELD.
See also: tui.style.sgr (fork-free), tui.get.class.style
tui.get.widgets
tui.get.widgets [PANE]
Prints widget ids in draw order, one per line: all of them, or only those in PANE.
tui.get.type
tui.get.type ID
Prints the widget type: label, button, input, checkbox, password, textarea, list, table, select or progress. Returns 1 for an unknown id.
tui.get.pane
tui.get.pane ID
Prints the pane a widget lives in. Returns 1 for an unknown id.
tui.get.action
tui.get.action ID
Prints the action function of a widget (empty when none or unknown).
tui.get.checked
tui.get.checked ID
Prints nothing; returns 0 when ID is a checked checkbox, else 1.
Notes
- Use it in a condition:
if tui.get.checked chk_a; then ....tui.getprints the value as0/1.
tui.get.focused
tui.get.focused
Prints the id of the focused widget (empty when none).
tui.get.hovered
tui.get.hovered [widget|pane]
Prints the id of the widget (default) or pane under the mouse pointer (empty when none).
tui.get.pane_focus
tui.get.pane_focus
Prints the keyboard pane: the scroll target with the highlighted border (empty when none).
tui.get.page
tui.get.page
Prints the absolute path of the current page file (empty before a page is loaded).
tui.get.pages
tui.get.pages
Prints the pages linked by a <button page="…"> so far, sorted, one per line as PAGE<TAB>TITLE.
Notes
PAGEis the path as written in thepageattribute;TITLEis the button caption.- The command palette uses this list to offer page commands.
tui.get.event
tui.get.event
Prints the current input event on one line: type=… key=… x=… y=… pane=… widget=… button=….
Notes
- Meant for debugging. In handlers read the
TUI_EVENT_*variables directly (see the module intro).
tui.get.classes
tui.get.classes
Prints every key the loaded theme defines, sorted, one per line.
Notes
- Pseudo-states appear as
CLASS_focus,CLASS_hover, … - Fork-free alternative:
tui.class.names.
tui.get.class.style
tui.get.class.style CLASS FIELD [STATE]
Prints one field of a theme class, without a pane or widget.
Parameters
CLASS: class name without the dot.FIELD:fg,bgormods.STATE:normal(default) or a pseudo-state such asfocus; falls back to the plain class.
Returns: 2 for an invalid FIELD.
Example
accent=$(tui.get.class.style brand fg)
See also: tui.class.style (fork-free)
Logging and perf
| Function | Summary |
|---|---|
tui.log |
Appends a line to the app’s log file. stdout is the screen, so logs go to a file. |
tui.log.debug |
Same as tui.log MSG debug. See tui.log. |
tui.log.info |
Same as tui.log MSG info. See tui.log. |
tui.log.warn |
Same as tui.log MSG warn. See tui.log. |
tui.log.error |
Same as tui.log MSG error. See tui.log. |
tui.log.file |
Prints the path tui.log writes to today. |
tui.perf.mean_render_ms |
Prints the mean frame render time in milliseconds over the last SECONDS. |
tui.log
tui.log MSG [LEVEL]
Appends a line to the app’s log file. stdout is the screen, so logs go to a file.
Parameters
MSG: the message.LEVEL: free text shown in brackets. Default:info.
Output: nothing on screen. The file gets [HH:MM:SS] [LEVEL] MSG.
Notes
- The file is
~/.config/DABT/apps/<TUI_APP_NAME>/logs/<yyyy-mm-dd>_<TUI_APP_NAME>.log(dabtwhen no app name is set). SetTUI_LOG_DIRbefore sourcingtui.shto use another folder.tui.log.fileprints the current path. - One file per day; an app running past midnight continues in the next day’s file.
- Fork-free once the folder exists. A log folder that can’t be created or written is ignored silently; logging never fails the caller.
- Logs are never rotated or deleted.
Example
tui.log "export started: $file"
tui.log.warn "config missing, using defaults"
See also: tui.log.debug, tui.log.info, tui.log.warn, tui.log.error
tui.log.debug
tui.log.debug MSG
Same as tui.log MSG debug. See tui.log.
tui.log.info
tui.log.info MSG
Same as tui.log MSG info. See tui.log.
tui.log.warn
tui.log.warn MSG
Same as tui.log MSG warn. See tui.log.
tui.log.error
tui.log.error MSG
Same as tui.log MSG error. See tui.log.
tui.log.file
tui.log.file
Prints the path tui.log writes to today.
Output: e.g. /home/me/.config/DABT/apps/my_app/logs/2026-09-24_my_app.log.
Notes
- The file may not exist yet: it is created by the first log line.
Example
tui.view "Log" "$(tail -n 200 "$(tui.log.file)" 2>/dev/null)"
tui.perf.mean_render_ms
tui.perf.mean_render_ms SECONDS
Prints the mean frame render time in milliseconds over the last SECONDS.
Output: an integer, or nothing when tracking is off or no frame fell in the window.
Notes
- Tracking is off by default. Set
_TUI_PERF_TRACKING=1to turn it on (used by the debug page andtools/debug/).
Event and result variables
| Variable | Set by | Meaning |
|---|---|---|
TUI_EVENT_TYPE |
dispatcher | key, mouse (wheel and drag included), paste. |
TUI_EVENT_KEY |
dispatcher | Normalised key name (ctrl+q, wheel:down). |
TUI_EVENT_X, TUI_EVENT_Y |
mouse | Pointer column / row (1-based). |
TUI_EVENT_PANE, TUI_EVENT_WIDGET |
dispatcher | Pane / widget under the pointer or focused. |
TUI_EVENT_BUTTON, TUI_EVENT_RAWBTN |
mouse | Button name / raw SGR button code. |
TUI_EVENT_COUNT |
dispatcher | Number of identical scroll events merged into this dispatch. |
TUI_EVENT_PASTE |
paste | Bracketed-paste text. |
TUI_PANE_ROWS, TUI_PANE_COLS |
tui.pane_size |
Pane content size. |
TUI_SGR, TUI_FG, TUI_BG, TUI_MODS |
tui.class.sgr, tui.style.sgr |
Resolved style. |
TUI_CLASSES |
tui.class.names |
Sorted theme keys. |
TUI_SYS_* |
tui.sys.* |
System samples. |
_TUI_FACTORY_LAST_ID, _TUI_FACTORY_GRID_CELLS |
tui.factory.* |
Last generated ids (public by documented exception). |
Settings read when tui.sh is sourced: TUI_APP_NAME (config folder and log name, default dabt), TUI_HOME, TUI_LOG_DIR, TUI_CONFIG_FILE, TUI_VALIDATE=0 (skip page validation), TUI_IGNORE_INVALID_XML=1 (start despite validation errors).
