Commit graph

57 commits

Author SHA1 Message Date
d2c0c2159f feat(installer): WiX v5 MSI scaffold for Wild Dragon TeamsISO
Some checks failed
CI / build-and-test (push) Failing after 34s
Adds installer/TeamsISO.Installer.wixproj (WixToolset.Sdk 5.0.2 + WixToolset.UI.wixext) plus Package.wxs that produces a per-machine x64 MSI bundling the Release publish output of TeamsISO.App.

Layout: Program Files\\Wild Dragon\\TeamsISO\\, Start Menu shortcut under Programs\\Wild Dragon\\TeamsISO, ARP entry pointing at https://wilddragon.net for both help and about, NoRepair set so users uninstall+install for upgrades. MajorUpgrade is wired so upgrade-in-place from older versions works; downgrade is blocked with a friendly message.

NDI runtime presence is searched (HKLM environment NDI_RUNTIME_DIR_V6) and surfaced as the NDIRUNTIMEDIR property — the install no longer prompts via the deprecated VBScript custom action; instead the WPF app's existing first-run NDI check pops the install link dialog if the runtime is missing. Operators can stage the app before NDI rolls out.

Build:

    dotnet publish src/TeamsISO.App/TeamsISO.App.csproj -c Release -r win-x64 -o publish/TeamsISO

    dotnet build installer/TeamsISO.Installer.wixproj -c Release

Verified locally: MSI builds clean (0 warnings, 0 errors), produces TeamsISO-Setup-1.0.0-alpha.0.msi (336 KB), summary info reads correctly via WindowsInstaller COM API. Property table contains ARPHELPLINK/ARPURLINFOABOUT/Manufacturer/ProductName/UpgradeCode as expected.
2026-05-08 00:16:26 -04:00
0b24fbb529 test(ndi): seed requires=ndi integration tests against real NDI runtime
Replaces the previously-skipped placeholder with 8 integration tests that exercise the production P/Invoke shim against the installed NDI 6 runtime: runtime version probe + prefix assertion (catches future SDK rebrandings), finder lifecycle on default + custom groups (incl. whitespace tolerance + multi-group), sender lifecycle on default + custom groups, and a loopback-discovery test that creates a uniquely-named sender and asserts a same-process finder sees it within 5 s.

All marked [Trait('requires', 'ndi')] so the existing CI filter (Category!=ndi&requires!=ndi) excludes them. Run locally with: dotnet test --filter requires=ndi. Today: 8/8 pass against NDI 6.2 on Windows 11.
2026-05-08 00:11:01 -04:00
6cac486fbe feat(ui): rebrand to Wild Dragon + Microsoft Teams layout
Some checks failed
CI / build-and-test (push) Failing after 33s
Replaces the Stone theme with Wild Dragon branding (canvas #0A0A0A, accent cyan #97EDF0, secondary #9AE0FD, coral alert #FB819C — sourced from wilddragon.net) and reorganizes MainWindow into a Microsoft Teams-style three-column layout: a 72px left rail (logo + Participants/Settings nav + engine-status indicator), a center content area (header + participants card), and a right settings panel.

Adds InitialsConverter so participant avatars render real initials (Brendon Power -> 'BP', '(Local)' -> 'L') instead of a generic glyph. Drops the obsolete StoneTheme.xaml; the project now ships exactly one theme dictionary.

Typography: Inter (with Segoe UI Variable Display fallback) for the sans stack, JetBrains Mono (Cascadia Mono fallback) for machine names and timecodes — matching the wilddragon.net site.

Verified live against the running Teams meeting: app launches, participant 'Brendon Power' displays with avatar, settings panel surfaces NDI groups + Hide-Local toggle, engine status pill shows green/live.
2026-05-08 00:08:39 -04:00
53c06a9af9 feat(ui): single-instance enforcement via per-user named mutex
Two simultaneous TeamsISO processes contend over the NDI runtime, the same default sender names, and %APPDATA%\\TeamsISO\\config.json — observed during testing when launchers / shortcuts produced duplicate windows. Add a Local namespace per-user-keyed mutex (Local\\WildDragon.TeamsISO.SingleInstance.<username>) at startup; if a second instance can't claim it, broadcast a registered window message ('WildDragon.TeamsISO.BringToFront') and Shutdown(0). The running instance subscribes to that message via ComponentDispatcher.ThreadFilterMessage and surfaces its main window when received.

Per-user keying lets two different Windows users on the same machine each run their own TeamsISO. Mutex is released and disposed on OnExit.

Verified: Start-Process the exe twice in a row -> only one process remains, with the original window surfaced.
2026-05-07 23:59:47 -04:00
b542d01835 feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.

Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.

Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
909237f454 feat(ndi): plumb NDI groups (discovery + output) through the engine
Adds an NdiGroupSettings record carrying optional comma-separated NDI group lists for the finder and the senders. Extends INdiInterop.CreateFinder / CreateSender with optional groups arguments and populates NDIlib_find_create_t.p_groups and NDIlib_send_create_t.p_groups via P/Invoke. IsoController reads the settings on construction, threads DiscoveryGroups into NdiDiscoveryService and OutputGroups into IsoPipelineConfig, and exposes SetGroupSettingsAsync for runtime updates (group changes apply on next process restart so live pipelines aren't orphaned).

This unblocks the 'transcoder' topology where Teams broadcasts NDI on a private group (e.g. teamsiso-input) and TeamsISO re-emits clean normalized streams on Public — keeping raw, wrong-framerate Teams sources off the production network.

EngineConfig schema is JSON-back-compat: existing config.json files (no NdiGroups field) deserialize with NdiGroups=null and load as NdiGroupSettings.Default. UI surface for these settings comes in a follow-up.

Tests: 72/72 passing (was 69) — added IsoController coverage that group settings are read from ConfigStore on startup, passed to the finder, threaded into per-pipeline config, and round-trip through SetGroupSettingsAsync/Save/Load.
2026-05-07 23:48:49 -04:00
fa8d2a8fad feat(console): add --list-sources diagnostic flag
Some checks failed
CI / build-and-test (push) Failing after 27s
Enumerates every raw NDI source string visible to the local NDI finder for ~5 seconds and prints unique ones, then exits. Bypasses NdiSourceParser so it surfaces sources that the parser would otherwise reject — useful for confirming the on-the-wire format Teams (or any other producer) is actually emitting on a given setup.

Used directly to diagnose the Teams 'MS Teams' vs. 'Teams' brand-prefix mismatch fixed in the previous commit; left in as a permanent debug tool for future setup issues.
2026-05-07 23:33:44 -04:00
ca124540a7 fix(parser): accept 'MS Teams' brand prefix from current Teams NDI broadcasts
The new Microsoft Teams desktop client (observed against a live meeting on Teams 26106.1906.4665.7308) emits NDI source strings of the form

    WOOGLIN (MS Teams - Brendon Power)

    WOOGLIN (MS Teams - (Local))

    WOOGLIN (MS Teams - Active Speaker)

rather than the legacy 'MACHINE (Teams - ...)' shape NdiSourceParser was written to. As a result every Teams source was rejected and TeamsISO showed zero participants in real meetings.

Refactor the parser to recognize 'Teams', 'MS Teams', and (defensively) 'Microsoft Teams' as brand prefixes — longest first so 'MS Teams' isn't shadowed. Also recognize reserved suffix tokens after a dash ('Active Speaker' / 'Audio' / 'Audio Mix' / 'Screen Share') so the new active-speaker output is correctly classified as ActiveSpeaker rather than misread as a participant named 'Active Speaker'.

Tests: kept all legacy cases, added MS Teams + Microsoft Teams variants and the new dash-prefixed reserved-suffix cases. 69/69 unit tests passing; verified end-to-end against a live Teams meeting where TeamsISO.exe now shows '(Local)' and 'Brendon Power' in the Participants DataGrid.
2026-05-07 23:33:43 -04:00
d90ebb826f fix(ndi): match the NDI 6 WIN64 runtime banner in version probe
Some checks failed
CI / build-and-test (push) Failing after 26s
Shipping NDI 6 reports its version as a build banner of the form

    NDI SDK WIN64 13:07:00 Jun  2 2025 6.2.0.3

not the 'NDI SDK for Windows v6.x.x.x' format the prefix constant assumed. As a result NdiRuntimeProbe raised a spurious mismatch alert on every supported install. Update ExpectedRuntimeVersionPrefix to 'NDI SDK WIN64' which is the stable architecture token in the new banner; the trailing four-part version remains available for a stricter major-version check if that becomes useful.
2026-05-07 15:15:03 -04:00
d14a33a0a3 fix(ndi): resolve Processing.NDI.Lib.x64 via NDI_RUNTIME_DIR_V6 env var
The NDI 6 installer sets NDI_RUNTIME_DIR_V6 (and V5/V4 for back-compat) but does not always add the runtime directory to PATH, so a default DllImport(Processing.NDI.Lib.x64) failed with 0x8007007E (DllNotFoundException) on otherwise correctly installed machines, killing both TeamsISO.exe and TeamsISO.Console at preflight.

Add NdiNativeLibraryResolver, registered from NdiNative's static ctor, that resolves the DLL by trying NDI_RUNTIME_DIR_V6 / V5 / V4 in order, NativeLibrary.Load-ing the file from disk before the runtime falls back to its default search algorithm. Static-ctor registration (rather than [ModuleInitializer]) sidesteps CA2255 under TreatWarningsAsErrors and still guarantees the resolver is in place before the first P/Invoke fires.
2026-05-07 15:14:54 -04:00
0f03c272ad fix(build): use forward-slash paths in TeamsISO.sln so Windows .slnf resolves
MSBuild on Windows compares solution-filter project paths against .sln entries as raw strings, so the forward-slash entries in TeamsISO.Windows.slnf / TeamsISO.Linux.slnf were being rejected (MSB5028) against the .sln's backslash entries. Linux dotnet normalizes separators so CI happened to be green. Switch the .sln to forward slashes so both platforms agree; Visual Studio accepts either form.
2026-05-07 15:14:42 -04:00
e3321ff279 feat(ui): wire DI bootstrap in App.xaml.cs and add Windows solution filter
Some checks failed
CI / build-and-test (push) Failing after 36s
2026-05-07 15:41:58 +00:00
d64b110550 feat(ui): add MainWindow XAML with participants DataGrid, settings sidebar, alert banner
Some checks failed
CI / build-and-test (push) Failing after 34s
2026-05-07 15:40:49 +00:00
8c441318d8 feat(ui): add MainViewModel with live participants collection and dispatcher marshalling
Some checks failed
CI / build-and-test (push) Failing after 34s
2026-05-07 15:40:06 +00:00
fbb73bcf04 feat(ui): add MVVM helpers and per-component view models (Participant, GlobalSettings, AlertBanner)
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:39:46 +00:00
2c6fbdf861 docs: add Phase C WPF UI plan
Some checks failed
CI / build-and-test (push) Failing after 32s
2026-05-07 15:38:57 +00:00
c6f23e1885 docs: update plan backlog after Phase B-2 completion
Some checks failed
CI / build-and-test (push) Failing after 32s
2026-05-07 15:38:24 +00:00
368920734d fix(console): disambiguate System.Console from TeamsISO.Console namespace
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:38:02 +00:00
b2dafb7aed feat(console): add TeamsISO.Console headless smoke runner
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:37:44 +00:00
88841780af feat(pipeline): add managed BGRA nearest-neighbor scaler with aspect modes
Some checks failed
CI / build-and-test (push) Failing after 27s
2026-05-07 15:37:07 +00:00
af37b4d9e1 refactor(interop): NdiRuntimeProbe now matches by prefix to handle NDI runtime version strings
Some checks failed
CI / build-and-test (push) Failing after 24s
2026-05-07 15:36:26 +00:00
da5818b690 feat(interop): add NdiInteropPInvoke production INdiInterop implementation
Some checks failed
CI / build-and-test (push) Failing after 26s
2026-05-07 15:35:59 +00:00
90b0951a42 fix(interop): rename create-settings structs to avoid name collision with P/Invoke methods
Some checks failed
CI / build-and-test (push) Failing after 26s
2026-05-07 15:35:15 +00:00
6f09ca35ef feat(interop): add NDI 6 native bindings, handle types, and version constants
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:34:54 +00:00
60b12eb637 docs: add Phase B-2 NDI interop plan
Some checks failed
CI / build-and-test (push) Failing after 27s
2026-05-07 15:34:11 +00:00
54cb4ac3d4 docs: update plan backlog after Phase B-1 completion
Some checks failed
CI / build-and-test (push) Failing after 30s
2026-05-07 15:28:52 +00:00
cd5e852a30 feat(controller): add IIsoController and IsoController implementation
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:28:27 +00:00
49b6dfb9ed feat(pipeline): add IsoPipeline with lifecycle and restart supervisor
Some checks failed
CI / build-and-test (push) Failing after 29s
2026-05-07 15:26:54 +00:00
e318514202 feat(interop): add NdiRuntimeProbe with version-mismatch result
Some checks failed
CI / build-and-test (push) Failing after 27s
2026-05-07 15:24:31 +00:00
798a5abd64 feat(pipeline): add ExponentialBackoff policy
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:24:13 +00:00
aecbda674d feat(pipeline): add NdiSender with channel-based input
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:23:51 +00:00
ead5e79935 feat(pipeline): add NdiReceiver with channel-based output
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:23:26 +00:00
f1513ddaf5 docs: add Phase B-1 pipeline-orchestration plan
Some checks failed
CI / build-and-test (push) Failing after 25s
2026-05-07 15:22:56 +00:00
38f7db888e feat(domain): default global framerate to 59.94p (user's primary production target)
Some checks failed
CI / build-and-test (push) Failing after 25s
2026-05-07 15:21:58 +00:00
381cac66e6 docs: add Phase A test playbook stub and plan backlog
Some checks failed
CI / build-and-test (push) Failing after 26s
2026-05-07 15:16:37 +00:00
27dc0f90c7 feat(logging): add EngineLogging.CreateConsole helper
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:16:17 +00:00
f2b4e881e4 ci: enforce 80% line coverage gate on TeamsISO.Engine
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:16:11 +00:00
5c039025fd feat(pipeline): add FrameProcessor with closest-frame timing and slate fallback
Some checks failed
CI / build-and-test (push) Failing after 24s
2026-05-07 15:15:19 +00:00
970f04861d feat(pipeline): add SolidFrameRenderer slate and IFrameScaler/PassthroughFrameScaler
Some checks failed
CI / build-and-test (push) Failing after 25s
2026-05-07 15:14:37 +00:00
1b280e3e77 feat(discovery): add NdiDiscoveryService with diff-based event emission
Some checks failed
CI / build-and-test (push) Failing after 22s
2026-05-07 15:14:15 +00:00
cef9018b6d feat(discovery): add ParticipantTracker with rename heuristic
Some checks failed
CI / build-and-test (push) Failing after 22s
2026-05-07 15:13:42 +00:00
c07a668672 test(fakes): add FakeNdiInterop and FakeFrameClock; feat(discovery): add DiscoveryEvent
Some checks failed
CI / build-and-test (push) Failing after 24s
2026-05-07 15:13:00 +00:00
f562303b47 feat(pipeline,interop): add RawFrame, ProcessedFrame, IFrameClock and INdiInterop test seam
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:12:36 +00:00
3f8b5f1a7b feat(persistence): add ConfigStore with atomic JSON writes and corruption-safe load
Some checks failed
CI / build-and-test (push) Failing after 22s
2026-05-07 15:12:01 +00:00
464f559576 feat(domain): add Participant, IsoAssignment, IsoOutput, IsoHealthStats, FrameProcessingSettings, EngineConfig, EngineAlert
Some checks failed
CI / build-and-test (push) Failing after 22s
2026-05-07 15:11:32 +00:00
aaf3184a8e feat(discovery): add NdiSource record and Teams source string parser
Some checks failed
CI / build-and-test (push) Failing after 22s
2026-05-07 15:11:00 +00:00
b07e3e78e0 feat(domain): add core enums (NdiSourceKind, IsoState, AspectMode, AudioMode, TargetFramerate, TargetResolution)
Some checks failed
CI / build-and-test (push) Failing after 23s
2026-05-07 15:10:29 +00:00
5ac0a50afe ci: add Forgejo Actions build-and-test workflow
Some checks failed
CI / build-and-test (push) Has been cancelled
2026-05-07 15:10:08 +00:00
f21e818b28 chore: scaffold WPF app and integration test projects
- TeamsISO.App: hand-rolled net8.0-windows WPF csproj since the WPF
  template isn't shipped on linux-arm64 .NET SDK; UI is a placeholder
  for Phase C.
- TeamsISO.Engine.IntegrationTests: cross-platform xunit project with a
  skipped scaffold fact tagged [Trait("requires", "ndi")] for Phase B.
- TeamsISO.Linux.slnf: solution filter for non-Windows CI that excludes
  the WPF project (which can only build on Windows).
2026-05-07 15:09:56 +00:00
d0f05263af test(engine): scaffold TeamsISO.Engine.Tests xUnit project 2026-05-07 15:08:38 +00:00