Generic exec action: run a command with the control's current value #17

Open
opened 2026-11-12 23:18:08 +01:00 by omp-deck · 0 comments
Collaborator

Why

Every action this daemon gains is a decision to support one specific integration. Useful things will always sit outside that set: display brightness, colour temperature, window-manager commands, media players, anything reachable by a shell command but not by WirePlumber.

One generic action that hands a control's value to a command covers all of them at the cost of a single action type, and lets a mapping be tried out before anyone decides whether it deserves first-class support.

Scope

An action that runs a configured command when its control is used, passing the control's current value.

It must work for both control families:

  • Buttons — fire on press. A momentary variant that also fires on release is worth considering, since the release event is already delivered (Action_Mute::on_midi handles value 0 today).
  • Faders and knobs — fire on value change, debounced. A continuous control emits a dense stream of CC events while moving; spawning a process per event is not acceptable. Events during a move must be coalesced so that roughly one invocation happens per gesture, and the final resting value must always be the one the command last saw — a debounce that drops the last event is worse than none.

How the value reaches the command (argument, placeholder substitution, stdin) is a design question, not settled here.

Surface area

  • A new action type registering through the existing factory (includes/Actions/IAction.h); no changes to the MIDI or audio layers are expected.
  • Process spawning is new to this codebase: nothing currently forks. Reaping children, not blocking the event loop, and not letting a slow or hanging command stall MIDI handling all need deliberate handling.
  • Debouncing is also new. It needs a timer source, which in this process means the GLib main loop the WirePlumber core already runs on.

Open questions

  • Value formatting. Raw 0–127, normalised 0.0–1.0, or a configurable output range with a curve? A brightness command wants a percentage, not a MIDI value, so some mapping is likely required to make this useful in practice.
  • Debounce shape and default interval: trailing-edge only, or leading plus trailing so the command reacts at the start of a gesture too.
  • Invocation while the previous run is still going. Skip, queue, or kill the old one.
  • Shell or direct exec? A shell is convenient and is what makes one-liners pleasant; direct exec with an argument list avoids quoting and injection problems entirely. Values are numeric and come from the device, so the risk is modest, but a config file that embeds a shell string is still a sharp edge worth naming.
  • LED feedback. A button with a command has no inherent state. An optional status command whose exit code or output drives the LED would make it a real toggle, at the cost of polling or re-running it — possibly a later addition rather than part of this.
  • Environment. A command run from a daemon does not inherit a session environment; anything touching the user's display or DBus session needs that spelled out.
  • Failures should be visible without being noisy, given a continuous control can fail repeatedly.

Acceptance

  • A command can be bound to a button, a fader, and a knob from the config.
  • The command receives the control's current value in a documented format.
  • Continuous controls are debounced: a full sweep produces a bounded number of invocations, and the last invocation always carries the resting value.
  • A slow or failing command does not block MIDI handling or leave zombie processes behind.
  • README documents the action, the value format, and the debounce behaviour.

Out of scope

  • Replacing existing native actions with shell equivalents; this is an escape hatch, not the preferred path for anything PipeWire can do directly.
  • A scripting or expression language in the config.
## Why Every action this daemon gains is a decision to support one specific integration. Useful things will always sit outside that set: display brightness, colour temperature, window-manager commands, media players, anything reachable by a shell command but not by WirePlumber. One generic action that hands a control's value to a command covers all of them at the cost of a single action type, and lets a mapping be tried out before anyone decides whether it deserves first-class support. ## Scope An action that runs a configured command when its control is used, passing the control's current value. It must work for both control families: - **Buttons** — fire on press. A momentary variant that also fires on release is worth considering, since the release event is already delivered (`Action_Mute::on_midi` handles value `0` today). - **Faders and knobs** — fire on value change, **debounced**. A continuous control emits a dense stream of CC events while moving; spawning a process per event is not acceptable. Events during a move must be coalesced so that roughly one invocation happens per gesture, and the final resting value must always be the one the command last saw — a debounce that drops the last event is worse than none. How the value reaches the command (argument, placeholder substitution, stdin) is a design question, not settled here. ## Surface area - A new action type registering through the existing factory (`includes/Actions/IAction.h`); no changes to the MIDI or audio layers are expected. - Process spawning is new to this codebase: nothing currently forks. Reaping children, not blocking the event loop, and not letting a slow or hanging command stall MIDI handling all need deliberate handling. - Debouncing is also new. It needs a timer source, which in this process means the GLib main loop the WirePlumber core already runs on. ## Open questions - **Value formatting.** Raw 0–127, normalised 0.0–1.0, or a configurable output range with a curve? A brightness command wants a percentage, not a MIDI value, so some mapping is likely required to make this useful in practice. - **Debounce shape and default interval**: trailing-edge only, or leading plus trailing so the command reacts at the start of a gesture too. - **Invocation while the previous run is still going.** Skip, queue, or kill the old one. - **Shell or direct exec?** A shell is convenient and is what makes one-liners pleasant; direct exec with an argument list avoids quoting and injection problems entirely. Values are numeric and come from the device, so the risk is modest, but a config file that embeds a shell string is still a sharp edge worth naming. - **LED feedback.** A button with a command has no inherent state. An optional status command whose exit code or output drives the LED would make it a real toggle, at the cost of polling or re-running it — possibly a later addition rather than part of this. - **Environment.** A command run from a daemon does not inherit a session environment; anything touching the user's display or DBus session needs that spelled out. - **Failures** should be visible without being noisy, given a continuous control can fail repeatedly. ## Acceptance - A command can be bound to a button, a fader, and a knob from the config. - The command receives the control's current value in a documented format. - Continuous controls are debounced: a full sweep produces a bounded number of invocations, and the last invocation always carries the resting value. - A slow or failing command does not block MIDI handling or leave zombie processes behind. - README documents the action, the value format, and the debounce behaviour. ## Out of scope - Replacing existing native actions with shell equivalents; this is an escape hatch, not the preferred path for anything PipeWire can do directly. - A scripting or expression language in the config.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
lucas/nanoKontroller2#17
No description provided.