Skip to content

Adopt shared qmlcomponents module; multi-backend video input - #67

Open
jcelerier wants to merge 3 commits into
developfrom
claude/qmlcomponents-adoption-nfnz0k
Open

Adopt shared qmlcomponents module; multi-backend video input#67
jcelerier wants to merge 3 commits into
developfrom
claude/qmlcomponents-adoption-nfnz0k

Conversation

@jcelerier

Copy link
Copy Markdown
Contributor

Adopts the shared ca.qc.sat.qmlcomponents module (submodule at qml/ca/qc/sat/qmlcomponents) and generalises the input path to multiple backends.

What moved

  • Shared components: deleted the duplicated DarkStyle/LightStyle, CustomButton/ComboBox/Label/TextField/Switch, AboutDialog, AppMenuBar and LogView; the app now imports them from the module. appStyle.*Theme.*; the injected appStyle context property and the DarkStyle/LightStyle instances are gone — Theme.dark is driven from the OS colour scheme instead. qml/livepose/qmldir now only registers the app-specific RunView.
  • Input refactor (RunView.qml): the Camera/Video-file two-button toggle is replaced by the shared InputSourceSelector. The widget is pure presentation; this view owns every Score.* call. A single Input device occupies the existing alpha1 live lane (Camera/NDI/Spout/Syphon) and is routed to the current Video Mapper in port; the video file keeps the alpha2 lane. Sync backends (Camera/Syphon) reuse the enumerated DeviceSettings; async backends (NDI/Spout) use {Path:name}. restartIfRunning() and the model-selection / camera behaviour are preserved.
  • Gating: the host computes the protocol allow-list — LIVEPOSE_ADVANCED_IO (documented, off, in environment) reveals NDI/Spout/Syphon, platform-filtered by the widget.
  • CI: actions/checkout now uses submodules: recursive.

Notes

  • The submodule .gitmodules URL points at https://github.com/sat-mtl/qmlcomponents.git, gitlink pinned to main.
  • Persistence restores the last source for a sync device backend (reconnects Input on launch as the camera path did) and the last video path. Restoring a non-default backend into the selector's combo on launch is a minor follow-up (the selector is intentionally stateless); host-side state is restored regardless.
  • The LogView "Test Log" button (a dev affordance) was dropped with the move to the shared LogView.
  • Validated with qmllint -I qml (Score.*/Score.UI/Util are host-provided and do not resolve locally, as expected) and the module's qmltestrunner suite (17/17).

Generated by Claude Code

@jcelerier jcelerier linked an issue Jun 10, 2026 that may be closed by this pull request
- Add qmlcomponents as a submodule at qml/ca/qc/sat/qmlcomponents and pull
  submodules in the build workflow.
- Replace the duplicated DarkStyle/LightStyle, Custom* controls, AboutDialog,
  AppMenuBar and LogView with the shared module; appStyle.* -> Theme.*, drop
  the injected appStyle/DarkStyle-LightStyle and drive Theme.dark from the OS
  colour scheme instead.
- RunView: replace the Camera/Video-file toggle with InputSourceSelector and
  own the Score lifecycle in the host. A single 'Input' device occupies the
  alpha1 live lane (Camera/NDI/Spout/Syphon) and is routed to the Video Mapper
  'in' port; the video file keeps the alpha2 lane. Sync backends reuse the
  enumerated DeviceSettings, async backends use {Path:name}. restartIfRunning
  and the model selection behaviour are preserved.
- environment: document LIVEPOSE_ADVANCED_IO (off) to reveal NDI/Spout/Syphon.
The shared AboutDialog now takes a partnerLogos data list instead of fixed
SAT/ossia image properties; pass the SAT + ossia footer logos that way.
@jcelerier
jcelerier force-pushed the claude/qmlcomponents-adoption-nfnz0k branch from 44ec22a to 527b272 Compare June 18, 2026 15:38
@jcelerier
jcelerier changed the base branch from master to develop June 18, 2026 15:39
The Camera backend is declared enumerate: "sync", so reenumerate() set
enumerate = true and read enumerator.devices in the same tick. That holds
on desktop: setting the property runs score's reprocess() directly
(QMetaObject::invokeMethod defaults to AutoConnection), and the V4L2 and
AVFoundation enumerators call back from inside it, so the list is already
full when it is read.

It cannot hold in a browser. There, enumerating cameras means
navigator.mediaDevices.enumerateDevices(), which hands out neither ids nor
labels until camera permission has been granted -- so score's wasm
enumerator asks for permission and rescans on a timer. The prompt is raised
by that very call, and by the time the user accepts, the combo has been
built from an empty list and nothing ever reads it again: the devices
property has no NOTIFY. The cameras never appear.

Connect deviceAdded/deviceRemoved before enabling enumeration and drop the
one-shot read. Inline backends deliver through the same signal during the
assignment, so desktop behaviour is unchanged, while sources that arrive
later now show up on their own. This also handles the wasm camera's
anonymous-then-named transition: the enumerator removes the placeholder and
re-adds the device once its label is known.

Two consequences of building the list from the signal:

- deviceRemoved carries only the name, while a camera's label needs its
  category too (one category per device, one name per mode), so sourceLabels
  maps one to the other.
- the saved source can no longer be restored synchronously at startup;
  pendingSourceRestore defers it to whenever the device turns up.

The descriptor's enumerate field stays as it is: it also selects the
settings shape (snapshot vs {Path: name}), which is a separate axis from
discovery timing -- a wasm camera is discovered asynchronously yet still
needs its enumerated DeviceSettings.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019oPj1zRcxSQX7FNni7EHM6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

support generic video input

1 participant