Root cause: not a lock. When the window is hidden or behind others (always when the app is driven over the control channel), macOS App Nap drops every thread of the process to background priority 4 and defers its timers. On a loaded machine (load avg ~150) a priority-4 process gets no CPU: after Render In to Out + play the UI thread stayed runnable inside the run loop's timer callout, every thread at PRI 4 (`ps -M`), 0 % CPU, control channel silent. Relaunched with App Nap off the same script ran through: render 2–13 s instead of 15 s, every reply immediate (2/2 runs). - apps/filmcraft/src/app_nap.rs: hold a user-initiated, latency-critical NSProcessInfo activity for the life of the process (= NSAppSleepDisabled for a bundle; idle system sleep allowed). - ui.screenshot: a hidden window or sleeping display never presents, so the screenshot event never came and the UI repainted at full speed forever (100 % CPU). It now fails after 10 s with "no frame was presented (window hidden or display asleep)" and polls at 60 Hz meanwhile. Regression test: app_nap::tests (the activity is taken on macOS). The starvation itself needs a hidden window on a loaded machine and is checked with the control-channel script in the commit notes (captions.newTrack/add, markIn/Out, Full, renderInToOut wait, stop, playhead.set, renderBar, ui.screenshot): all replies within 10 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2.7 KiB
Control protocol & MCP
Desktop control channel
filmcraft --control 9876 (or FILMCRAFT_CONTROL_PORT) listens on 127.0.0.1:<port> (loopback only).
One JSON request per line → one JSON reply per line. The app opts out of macOS App Nap
(apps/filmcraft/src/app_nap.rs): a hidden window would otherwise drop the whole process to
background priority, and on a busy machine it would stop answering.
{"id": 1, "method": "engine.execute", "params": {"command": "sequence.addEdit", "params": {"seconds": 3}}}
{"id": 1, "ok": true, "result": {"cuts": 2}}
Methods (handlers in crates/ui-egui/src/control.rs):
| Method | Params | |
|---|---|---|
engine.execute / ui.menu.invoke |
{command, params} |
any engine or UI command id |
engine.commands / ui.menu.list |
– | command registry / menu tree |
ui.inspect |
– | UI state (tool, workspace, dock, timeline view, playback, fps…) |
ui.elements |
{prefix?} |
every on-screen interactive element: id, label, rect |
ui.set |
{tool, workspace, mode, theme, focused, playbackRes, timeline:{pps,scroll,fit}} |
|
ui.panel.show / ui.panel.close |
{panel} |
|
ui.click / ui.move |
{id} or {x,y}, button, count, modifiers |
synthetic pointer input |
ui.drag |
{from, to, steps, modifiers} |
press–move–release |
ui.scroll |
`{id | x,y, dx, dy, modifiers}` |
ui.key / ui.type |
{key} (Cmd+K, Space…) / {text} |
keyboard |
ui.timeline.hit / ui.timeline.locate |
{x,y} / {clip, edge?} |
timeline hit-testing |
ui.playback |
`{action: play | stop |
ui.screenshot |
{path?, panel?} |
PNG of the window or one panel; fails after 10 s when no frame is presented (window hidden, display asleep) |
ui.resize, ui.focus, app.quit |
Element ids are stable, e.g. timeline.clip.<id>, timeline.track.V1.lock, tools.Razor,
project.item.<id>, effects.item.gaussian_blur, panel.tab.Timeline, program.transport.playback.toggle.
Project files, auto-save, crash recovery and preferences commands (file.recover, prefs.set, …) and their
automation ids are listed in project-files.md.
MCP
filmcraft-cli mcp serves MCP on stdio: headless (in-process session; --demo / --project p.fcproj)
or --bridge 127.0.0.1:9876 to drive the running app. Tools: command_list, command_run,
project_inspect, sequence_inspect, media_import, render_frame, ui_inspect, ui_elements,
ui_click, ui_drag, ui_key, ui_type, ui_screenshot, ui_control. .mcp.json registers both.
Time is in ticks (254 016 000 000 per second); commands also accept seconds, frame or timecode.