8.19.26. Driving from outside
Every boost widget that previous tutorials wrote registers a path in
the telemetry tree (DRIVE_WIN/USER, DRIVE_WIN/SPEED,
DRIVE_WIN/RUN_BTN …). That path is also an HTTP endpoint:
the boost layer ships [live_command] handlers (imgui_snapshot,
imgui_force_set, imgui_click, imgui_open, imgui_close,
imgui_focus) that look up the target in the registry. imgui_click
fires a real synthetic mouse click at the widget’s center;
imgui_force_set / imgui_open / imgui_close write the matching
field on the widget’s state struct directly. Either way ImGui sees the
effect as if a real input device — or an external editor — drove it.
This tutorial flips the point of view: instead of writing the daslang side, write the driver — a curl / Python / Bash script that issues JSON commands at a running daslang-live app. Every interaction the user could perform via mouse/keyboard has a curl equivalent, and the two surfaces use one in-memory model.
Source: modules/dasImgui/examples/tutorial/driving_outside.das — a small target app
exposing five widget kinds. The recording is driven entirely by JSON
commands: value writes and container toggles mutate state directly
(no mouse), while imgui_click fires a real synthetic click.
8.19.26.1. Walkthrough
1options gen2
2options _comment_hygiene = true
3options gc
4
5require imgui
6require imgui_app
7require opengl/opengl_boost
8require live/glfw_live
9require live/live_api
10require live/live_commands
11require live/live_vars
12require live_host
13require imgui/imgui_live
14require imgui/imgui_boost_runtime
15require imgui/imgui_boost_v2
16require imgui/imgui_widgets_builtin
17require imgui/imgui_containers_builtin
18require imgui/imgui_visual_aids
19
20let PRESETS : array<string> <- ["Calm", "Mellow", "Active", "Frantic"]
21var PRESET_ROW : table<int; ClickState>
22
23// nolint:STYLE014 — file-header tutorial banner (curl driving recipes) displaced below decls
24// =============================================================================
25// TUTORIAL: driving_outside — every boost widget is also a JSON endpoint.
26//
27// Previous tutorials wrote the daslang side. This one is the inverse view:
28// what the JSON command surface looks like as a programming model in its
29// own right. Every widget the boost layer ships registers a path in the
30// telemetry tree; that path is also addressable from outside via the
31// [live_command] HTTP endpoints `imgui_snapshot` / `imgui_force_set` /
32// `imgui_click` / `imgui_open` / `imgui_close` / `imgui_focus`.
33//
34// The target app is small — slider + button + input + popup + combo — so
35// the driving recipes are easy to spot. Every interaction the user could
36// perform via mouse/keyboard has a curl equivalent.
37//
38// STANDALONE: daslang.exe modules/dasImgui/examples/tutorial/driving_outside.das
39// LIVE: daslang-live modules/dasImgui/examples/tutorial/driving_outside.das
40//
41// DRIVE (curl recipes — pair these with the RST walkthrough):
42//
43// # Snapshot the world (always the first read in any driver)
44// curl -X POST -d '{"name":"imgui_snapshot"}' localhost:9090/command
45//
46// # imgui_force_set — drive a slider value
47// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/SPEED","value":7}}' \
48// localhost:9090/command
49//
50// # imgui_click — fire a button
51// curl -X POST -d '{"name":"imgui_click","args":{"target":"DRIVE_WIN/RUN_BTN"}}' \
52// localhost:9090/command
53//
54// # imgui_force_set — string into a text input
55// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/USER","value":"Alice"}}' \
56// localhost:9090/command
57//
58// # imgui_force_set — combo by selected-index
59// curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/PRESET","value":2}}' \
60// localhost:9090/command
61//
62// # imgui_open / imgui_close — popups, closable windows, tabs
63// curl -X POST -d '{"name":"imgui_open","args":{"target":"DRIVE_WIN/STATUS_POPUP"}}' \
64// localhost:9090/command
65// curl -X POST -d '{"name":"imgui_close","args":{"target":"DRIVE_WIN/STATUS_POPUP"}}' \
66// localhost:9090/command
67// =============================================================================
68
69[export]
70def init() {
71 live_create_window("dasImgui driving_outside tutorial", 880, 620)
72 live_imgui_init(live_window)
73 // Deterministic FirstUseEver layout for the recording: disable imgui.ini so a prior
74 // session's window pos/size can't drift the framing. Tutorial-scoped on purpose.
75 DisableIniPersistence()
76 let io & = unsafe(GetIO())
77 GetStyle().FontScaleMain = 1.5
78}
79
80[export]
81def update() {
82 if (!live_begin_frame()) return
83 begin_frame()
84
85 ImGui_ImplGlfw_NewFrame()
86 apply_synth_io_override()
87 NewFrame()
88
89 SetNextWindowPos(ImVec2(30.0f, 30.0f), ImGuiCond.FirstUseEver)
90 SetNextWindowSize(ImVec2(640.0f, 460.0f), ImGuiCond.FirstUseEver)
91 window(DRIVE_WIN, (text = "driving_outside", closable = false,
92 flags = ImGuiWindowFlags.None)) {
93
94 // ---- A text input — driven by `imgui_force_set` with a string value ----
95 input_text(USER, (text = "User"))
96 text("USER.value = \"{USER.value}\"")
97
98 separator(DR_SEP_1)
99
100 // ---- A slider — driven by `imgui_force_set` with a number value ----
101 slider_int(SPEED, (text = "Speed"))
102 text("SPEED.value = {SPEED.value}")
103
104 separator(DR_SEP_2)
105
106 // ---- A combo — driven by `imgui_force_set` with the selected index ----
107 var preset_label = "(none)"
108 if (PRESET.value >= 0 && PRESET.value < length(PRESETS)) {
109 preset_label = PRESETS[PRESET.value]
110 }
111 combo_select(PRESET, (text = "Preset",
112 preview_value = preset_label,
113 flags = ImGuiComboFlags.None)) {
114 for (i in range(length(PRESETS))) {
115 let is_sel = (i == PRESET.value)
116 if (selectable_label(PRESET_ROW[i], PRESETS[i], is_sel)) {
117 PRESET.value = i
118 }
119 }
120 }
121 text("PRESET = {preset_label} (idx {PRESET.value})")
122
123 separator(DR_SEP_3)
124
125 // ---- A button — fired by `imgui_click` ----
126 if (button(RUN_BTN, (text = "Run"))) {}
127 text("RUN_BTN.click_count = {RUN_BTN.click_count}")
128
129 separator(DR_SEP_4)
130
131 // ---- A popup — opened/closed via `imgui_open` / `imgui_close` ----
132 text("STATUS_POPUP - driven by imgui_open / imgui_close.")
133 popup(STATUS_POPUP, (text = "StatusPopup",
134 flags = ImGuiWindowFlags.None)) {
135 text("Driven from outside via imgui_open.")
136 separator(DR_SEP_5)
137 text("RUN_BTN.click_count = {RUN_BTN.click_count}")
138 }
139 }
140
141 end_of_frame()
142 Render()
143 var w, h : int
144 live_get_framebuffer_size(w, h)
145 glViewport(0, 0, w, h)
146 glClearColor(0.10f, 0.10f, 0.12f, 1.0f)
147 glClear(GL_COLOR_BUFFER_BIT)
148 live_imgui_render()
149
150 live_end_frame()
151}
152
153[export]
154def shutdown() {
155 live_imgui_shutdown()
156 live_destroy_window()
157}
158
159[export]
160def main() {
161 init()
162 while (!exit_requested()) {
163 update()
164 maybe_collect_gc()
165 }
166 shutdown()
167}
8.19.26.1.1. The command surface
Two kinds of command — faithful input (does what a user does) and bypass (does what a user can’t):
Raw synth IO (faithful):
imgui_mouse_pos,imgui_mouse_button,imgui_mouse_play,imgui_key_press,imgui_key_type. The driver pretends to be a mouse or keyboard, feeding events into the ImGui input queue. Used byimgui_playwrightfor cursor-visible recordings.Click a widget by name (faithful):
imgui_click,imgui_focus.imgui_clickresolves the widget by path (or hex_id), warps to its center, and presses/releases through ImGui’s own input path — a real click, so the widget behaves exactly as a user click would (it errors if the target isn’t rendered this frame).imgui_focusforces keyboard focus. No trajectory to script, but the widget must be on screen.Write a value directly (bypass):
imgui_force_set. The framework looks up the widget and queuesstate.has_pending = true+state.pending_value = ...; the render function submits it next frame. Does what a click can’t — an exact value, an off-screen or inactive widget.
Plus the read side and the container channel:
imgui_snapshot— full registry as JSON, the first call in any driver.imgui_open/imgui_close— setstate.pending_open/state.pending_closeon container widgets (popups, closable windows, tabs, tree nodes).
Prefer imgui_click for clicks and imgui_force_set for values —
the first is a faithful click, the second a deterministic value write.
Drop to raw synth IO only when there’s no higher-level counterpart
(drag along a custom trajectory, paste a long string into a focused
input, sustain a chord, …).
8.19.26.1.2. imgui_snapshot — read the world
The first call every driver makes:
curl -X POST -d '{"name":"imgui_snapshot"}' localhost:9090/command
Response shape:
{
"frame": 412,
"globals": {
"DRIVE_WIN": {
"kind": "window",
"bbox": [30, 30, 670, 490],
"hex_id": "0x2c1a8f4b",
"payload": { "open": true, "size": [640, 460], ... }
},
"DRIVE_WIN/SPEED": {
"kind": "slider_int",
"bbox": [...],
"hex_id": "0x...",
"payload": { "value": 5, "bounds": [0, 10], ... }
},
"DRIVE_WIN/RUN_BTN": {
"kind": "button",
"bbox": [...],
"payload": { "click_count": 0 }
},
...
},
"io": {
"mouse_pos": [320, 180],
"active_widget": "..."
}
}
Use it to:
discover what’s on screen and what kind each widget is;
read
bboxfor L1 mouse synthesis (when needed);check
payloadfor current state (test assertions);read
hex_idfor fallback dispatch when the path isn’t stable.
8.19.26.1.3. imgui_force_set — value writes
imgui_force_set is the universal value-write endpoint — slider, checkbox,
combo, color, text input, dock-window position. Type-dispatched on the
value’s JSON shape:
# string into a text input
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/USER","value":"Alice"}}' \
localhost:9090/command
# int into a slider
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/SPEED","value":7}}' \
localhost:9090/command
# int into a combo (selected index)
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/PRESET","value":2}}' \
localhost:9090/command
# array-of-floats into a color picker
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/TINT","value":[0.2,0.7,0.4]}}' \
localhost:9090/command
Under the hood: the registered dispatcher for the widget’s state struct
unpacks the JSON, type-checks it against the state’s value field,
flips has_pending = true, stores pending_value. The render
function picks it up next frame; ImGui submits the new value through
its own UpdateValue path.
8.19.26.1.4. imgui_click — fire a click
imgui_click is a real synthetic mouse click: it resolves the
target to its on-screen bbox, warps the cursor to the center, and
presses then releases the button across one frame — through ImGui’s own
input path, so the widget can’t tell it apart from a hardware click:
curl -X POST -d '{"name":"imgui_click","args":{"target":"DRIVE_WIN/RUN_BTN"}}' \
localhost:9090/command
The button’s render function returns true, click_count
increments, and the daslang side sees both the inline if
(button(...)) and RUN_BTN.clicked / RUN_BTN.click_count as
expected. Pass "button": 1 for a right-click (context menus), 2
for middle. Because it’s a real click, the target must be rendered this
frame — clicking an unrendered widget returns an error (use
imgui_force_set to drive a widget that isn’t on screen).
8.19.26.1.5. imgui_open / imgui_close — containers
Containers expose an open-state channel through state.pending_open
and state.pending_close. imgui_open flips pending_open;
imgui_close flips pending_close. The next frame’s render
function applies the change:
curl -X POST -d '{"name":"imgui_open","args":{"target":"DRIVE_WIN/STATUS_POPUP"}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_close","args":{"target":"DRIVE_WIN/STATUS_POPUP"}}' \
localhost:9090/command
The same channel handles popups, closable windows, tabs (closable-tab
visibility specifically — see tutorial_containers for the tab-item
caveat), tree nodes, and collapsing headers.
8.19.26.1.6. The flow on a single command
Every command runs the same path:
daslang-liveHTTP server receivesPOST /command.Routes by
nameto the registered[live_command]handler.Handler looks up
targetin the registry’s path map (or hex_id reverse map).Either spawns a synthetic-input coroutine (
imgui_click— warp + press/release over a frame) or mutates the matching state struct’s pending field (imgui_force_set/imgui_open/imgui_close); returns{"ok": true, ...}on the HTTP response.Over the next frame(s) the script’s
update()runs; ImGui processes the synthetic input, or the render function applies the pending field, and the updated state is observable from the nextimgui_snapshot.
So commands settle over the next frame or two by design — there’s no
ambiguity about which frame’s state corresponds to a given response.
For test harnesses that need to read the result, the canonical pattern
is: command, then await_quiescent (waits a frame), then
imgui_snapshot.
8.19.26.1.7. Standalone vs live
The HTTP server only exists under daslang-live. Standalone
daslang.exe runs the same script but the live-command endpoints
aren’t bound — drive-from-outside scenarios require the live host.
8.19.26.1.8. Driving from outside (recap)
A complete drive sequence for this tutorial’s app:
# Read the world
curl -X POST -d '{"name":"imgui_snapshot"}' localhost:9090/command
# Write each widget kind
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/USER","value":"Alice"}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/SPEED","value":7}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_force_set","args":{"target":"DRIVE_WIN/PRESET","value":2}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_click","args":{"target":"DRIVE_WIN/RUN_BTN"}}' \
localhost:9090/command
curl -X POST -d '{"name":"imgui_open","args":{"target":"DRIVE_WIN/STATUS_POPUP"}}' \
localhost:9090/command
The recording at the top of this page runs this exact sequence — just
JSON commands. The value writes and container toggles flow through the
state-struct pending channel with no mouse motion; the imgui_click
is a real synthetic click at the button’s center.
8.19.26.1.9. Next steps
Now that the JSON-driven view is explicit, the visual aids tour
walks through every overlay the recordings used: highlight, mouse
trail, cursor sprite, narrate, key HUD, focus rect — all
[live_command]-wrapped so the same curl pattern reaches them.
See also
Full source: modules/dasImgui/examples/tutorial/driving_outside.das
Richer reference: modules/dasImgui/examples/features/io_synth_text.das —
imgui_key_type streams text as synthetic key + char events through the
key timeline; the synthetic keyboard layer in action.
Snapshot contract: imgui_boost_runtime.das’s
g_serializers per-kind payload definitions.
Previous tutorial: Live reload
Boost macros — the macro layer.