diff --git a/.forgejo/workflows/ci.yml b/.forgejo/workflows/ci.yml index f60d9ef..9a16e0c 100644 --- a/.forgejo/workflows/ci.yml +++ b/.forgejo/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: CI on: push: @@ -21,15 +21,15 @@ jobs: echo "$HOME/.dotnet" >> $GITHUB_PATH echo "DOTNET_ROOT=$HOME/.dotnet" >> $GITHUB_ENV - - name: Restore (Linux solution filter — excludes Windows-only WPF app) - run: dotnet restore TeamsISO.Linux.slnf + - name: Restore (Linux solution filter — excludes Windows-only WPF app) + run: dotnet restore Dragon-ISO.Linux.slnf - name: Build (Release, treat warnings as errors) - run: dotnet build TeamsISO.Linux.slnf --configuration Release --no-restore + run: dotnet build Dragon-ISO.Linux.slnf --configuration Release --no-restore - name: Test (excluding requires=ndi) run: > - dotnet test TeamsISO.Linux.slnf + dotnet test Dragon-ISO.Linux.slnf --configuration Release --no-build --logger "trx;LogFileName=test-results.trx" @@ -47,7 +47,7 @@ jobs: -reports:"**/coverage.cobertura.xml" \ -targetdir:coverage-report \ -reporttypes:"Cobertura;TextSummary" \ - -assemblyfilters:"+TeamsISO.Engine;-TeamsISO.Engine.NdiInterop" + -assemblyfilters:"+Dragon-ISO.Engine;-Dragon-ISO.Engine.NdiInterop" - name: Enforce coverage threshold (80%) run: | diff --git a/.gitignore b/.gitignore index b50fcb5..fc9d3d1 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,9 @@ Thumbs.db # Local Claude session metadata .claude/ + +# Build / test output logs +*.log +full-output.txt +test-output.txt +test-run.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b0c80..6f91c19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,62 +1,62 @@ -# Changelog +# Changelog -All notable changes to TeamsISO are documented here. The format follows +All notable changes to Dragon-ISO are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.0.0] — 2026-05-17 +## [1.0.0] — 2026-05-17 First general release. Windows-only, .NET 8 WPF, NDI 6. ### Engine -- **Participant discovery** over NDI with name cleanup — strips the +- **Participant discovery** over NDI with name cleanup — strips the "MS Teams - " / "(Teams) " prefixes and surfaces the operator-friendly display name. - **Per-participant ISO outputs** with normalized framerate, resolution, aspect mode, and audio routing. Each ISO is an individually-addressable NDI source. -- **NDI Groups** support — discovery and sender. One-click "Apply +- **NDI Groups** support — discovery and sender. One-click "Apply transcoder topology" pins Teams' raw broadcasts to a private - `teamsiso-input` group while TeamsISO re-emits on `Public`. -- **Self-healing finder** — if the NDI runtime stalls (zero discovered + `Dragon-ISO-input` group while Dragon-ISO re-emits on `Public`. +- **Self-healing finder** — if the NDI runtime stalls (zero discovered sources past a startup grace period, or sources go from present to empty and stay that way), the engine rebuilds the finder automatically. -- **Real-time recording** — per-output raw BGRA stream + `manifest.json` +- **Real-time recording** — per-output raw BGRA stream + `manifest.json` + an FFmpeg `convert.cmd` script for post-production conversion to H.264 MKV. Recording is opt-in globally and per-participant. -### UI — "Studio Terminal" +### UI — "Studio Terminal" - **Dark and light themes** with a runtime swap and a system-follow mode. The Wild Dragon mark, the participants-grid watermark, and every accent brush respond to the active theme. - **Header**: brand mark, theme toggle, settings gear. - **Transport strip**: session timer, participant count, live ISO count, - control-surface URL — at-a-glance status. + control-surface URL — at-a-glance status. - **Participants table**: 24px state LED, 106px live thumbnail preview, name + caption, 5-bar audio meter, **inline-editable output name**, CFG button (per-row override editor), ISO enable pill. -- **Settings drawer** — slide-over from the right with OUTPUT / NETWORK / +- **Settings drawer** — slide-over from the right with OUTPUT / NETWORK / APP tabs. -- **Ctrl+K command palette** — fuzzy search across Quick / Teams / +- **Ctrl+K command palette** — fuzzy search across Quick / Teams / Presets / Output / Network / App categories. -- **Live preview thumbnails** in the participants table; right-click → - Open preview… spawns a non-modal floating window suitable for a +- **Live preview thumbnails** in the participants table; right-click → + Open preview… spawns a non-modal floating window suitable for a secondary monitor. ### Output name template - New default: **the speaker's display name** (`{name}`). Per-participant overrides are inline-editable in the table. Empty-name fallback to - `TEAMSISO_{guid}` keeps the NDI sender uniquely identifiable while a + `Dragon-ISO_{guid}` keeps the NDI sender uniquely identifiable while a participant's display name resolves upstream. - Available tokens: `{name}`, `{guid}`, `{machine}`, `{timestamp}`. ### Operator presets - Save current per-participant ISO assignments + custom output names to - `%LOCALAPPDATA%\TeamsISO\presets.json`. Optional auto-apply on next + `%LOCALAPPDATA%\Dragon-ISO\presets.json`. Optional auto-apply on next launch. ### Teams orchestration @@ -71,16 +71,16 @@ First general release. Windows-only, .NET 8 WPF, NDI 6. - REST + WebSocket on `127.0.0.1:9755` for Bitfocus Companion / Stream Deck / custom controllers. - OSC on UDP `127.0.0.1:9000` for TouchOSC. -- Self-contained HTML control panel at `/ui` — open from any phone on +- Self-contained HTML control panel at `/ui` — open from any phone on the LAN. ### Diagnostics & installer -- Rolling daily Serilog logs under `%LOCALAPPDATA%\TeamsISO\logs\`. -- Diagnostic bundle export — zips logs + config + presets for bug reports. +- Rolling daily Serilog logs under `%LOCALAPPDATA%\Dragon-ISO\logs\`. +- Diagnostic bundle export — zips logs + config + presets for bug reports. - Forgejo-backed update check (manual or silent-on-launch, throttled to 24h). - WiX MSI installer with proper Add/Remove Programs metadata, Start Menu + Desktop shortcuts, and in-place upgrade. -[1.0.0]: https://forge.wilddragon.net/zgaetano/teamsiso/releases/tag/v1.0.0 +[1.0.0]: https://forge.wilddragon.net/zgaetano/Dragon-ISO/releases/tag/v1.0.0 diff --git a/Dragon-ISO.Linux.slnf b/Dragon-ISO.Linux.slnf new file mode 100644 index 0000000..d939629 --- /dev/null +++ b/Dragon-ISO.Linux.slnf @@ -0,0 +1,12 @@ +{ + "solution": { + "path": "Dragon-ISO.sln", + "projects": [ + "src/Dragon-ISO.Engine/Dragon-ISO.Engine.csproj", + "src/Dragon-ISO.Engine.NdiInterop/Dragon-ISO.Engine.NdiInterop.csproj", + "src/Dragon-ISO.Console/Dragon-ISO.Console.csproj", + "src/tests/Dragon-ISO.Engine.Tests/Dragon-ISO.Engine.Tests.csproj", + "src/tests/Dragon-ISO.Engine.IntegrationTests/Dragon-ISO.Engine.IntegrationTests.csproj" + ] + } +} diff --git a/Dragon-ISO.Windows.slnf b/Dragon-ISO.Windows.slnf new file mode 100644 index 0000000..2ae1d64 --- /dev/null +++ b/Dragon-ISO.Windows.slnf @@ -0,0 +1,14 @@ +{ + "solution": { + "path": "Dragon-ISO.sln", + "projects": [ + "src\\Dragon-ISO.Engine\\Dragon-ISO.Engine.csproj", + "src\\Dragon-ISO.Engine.NdiInterop\\Dragon-ISO.Engine.NdiInterop.csproj", + "src\\Dragon-ISO.Console\\Dragon-ISO.Console.csproj", + "src\\Dragon-ISO.App\\Dragon-ISO.App.csproj", + "src\\tests\\Dragon-ISO.Engine.Tests\\Dragon-ISO.Engine.Tests.csproj", + "src\\tests\\Dragon-ISO.Engine.IntegrationTests\\Dragon-ISO.Engine.IntegrationTests.csproj", + "src\\tests\\Dragon-ISO.App.Tests\\Dragon-ISO.App.Tests.csproj" + ] + } +} diff --git a/TeamsISO.sln b/Dragon-ISO.sln similarity index 74% rename from TeamsISO.sln rename to Dragon-ISO.sln index 3093684..8f5bd7b 100644 --- a/TeamsISO.sln +++ b/Dragon-ISO.sln @@ -1,72 +1,72 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31903.59 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{46E05E34-8A87-4986-87D3-FE0DE4E05F44}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine", "src\TeamsISO.Engine\TeamsISO.Engine.csproj", "{F0D24EAE-9225-4DC4-B3D2-6966077287A0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine.NdiInterop", "src\TeamsISO.Engine.NdiInterop\TeamsISO.Engine.NdiInterop.csproj", "{E737E54B-73DE-4F74-909C-1F0F5CF82AC6}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DBDF4A1D-4215-42D5-B456-2CE7159DF848}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine.Tests", "src\tests\TeamsISO.Engine.Tests\TeamsISO.Engine.Tests.csproj", "{F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.App", "src\TeamsISO.App\TeamsISO.App.csproj", "{80DCE039-3BBC-4D3F-B44B-51F324591C29}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Engine.IntegrationTests", "src\tests\TeamsISO.Engine.IntegrationTests\TeamsISO.Engine.IntegrationTests.csproj", "{A85E331D-026E-4BDE-B89C-0CC4C95001CE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.Console", "src\TeamsISO.Console\TeamsISO.Console.csproj", "{C3254998-9428-4264-A8FB-EAC9E1F9F432}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamsISO.App.Tests", "src\tests\TeamsISO.App.Tests\TeamsISO.App.Tests.csproj", "{B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {F0D24EAE-9225-4DC4-B3D2-6966077287A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F0D24EAE-9225-4DC4-B3D2-6966077287A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F0D24EAE-9225-4DC4-B3D2-6966077287A0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F0D24EAE-9225-4DC4-B3D2-6966077287A0}.Release|Any CPU.Build.0 = Release|Any CPU - {E737E54B-73DE-4F74-909C-1F0F5CF82AC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E737E54B-73DE-4F74-909C-1F0F5CF82AC6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E737E54B-73DE-4F74-909C-1F0F5CF82AC6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E737E54B-73DE-4F74-909C-1F0F5CF82AC6}.Release|Any CPU.Build.0 = Release|Any CPU - {F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}.Release|Any CPU.Build.0 = Release|Any CPU - {80DCE039-3BBC-4D3F-B44B-51F324591C29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {80DCE039-3BBC-4D3F-B44B-51F324591C29}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80DCE039-3BBC-4D3F-B44B-51F324591C29}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80DCE039-3BBC-4D3F-B44B-51F324591C29}.Release|Any CPU.Build.0 = Release|Any CPU - {A85E331D-026E-4BDE-B89C-0CC4C95001CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A85E331D-026E-4BDE-B89C-0CC4C95001CE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A85E331D-026E-4BDE-B89C-0CC4C95001CE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A85E331D-026E-4BDE-B89C-0CC4C95001CE}.Release|Any CPU.Build.0 = Release|Any CPU - {C3254998-9428-4264-A8FB-EAC9E1F9F432}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C3254998-9428-4264-A8FB-EAC9E1F9F432}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C3254998-9428-4264-A8FB-EAC9E1F9F432}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C3254998-9428-4264-A8FB-EAC9E1F9F432}.Release|Any CPU.Build.0 = Release|Any CPU - {B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {F0D24EAE-9225-4DC4-B3D2-6966077287A0} = {46E05E34-8A87-4986-87D3-FE0DE4E05F44} - {E737E54B-73DE-4F74-909C-1F0F5CF82AC6} = {46E05E34-8A87-4986-87D3-FE0DE4E05F44} - {DBDF4A1D-4215-42D5-B456-2CE7159DF848} = {46E05E34-8A87-4986-87D3-FE0DE4E05F44} - {F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8} = {DBDF4A1D-4215-42D5-B456-2CE7159DF848} - {80DCE039-3BBC-4D3F-B44B-51F324591C29} = {46E05E34-8A87-4986-87D3-FE0DE4E05F44} - {A85E331D-026E-4BDE-B89C-0CC4C95001CE} = {DBDF4A1D-4215-42D5-B456-2CE7159DF848} - {C3254998-9428-4264-A8FB-EAC9E1F9F432} = {46E05E34-8A87-4986-87D3-FE0DE4E05F44} - {B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F} = {DBDF4A1D-4215-42D5-B456-2CE7159DF848} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{46E05E34-8A87-4986-87D3-FE0DE4E05F44}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dragon-ISO.Engine", "src\Dragon-ISO.Engine\Dragon-ISO.Engine.csproj", "{F0D24EAE-9225-4DC4-B3D2-6966077287A0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dragon-ISO.Engine.NdiInterop", "src\Dragon-ISO.Engine.NdiInterop\Dragon-ISO.Engine.NdiInterop.csproj", "{E737E54B-73DE-4F74-909C-1F0F5CF82AC6}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DBDF4A1D-4215-42D5-B456-2CE7159DF848}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dragon-ISO.Engine.Tests", "src\tests\Dragon-ISO.Engine.Tests\Dragon-ISO.Engine.Tests.csproj", "{F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dragon-ISO.App", "src\Dragon-ISO.App\Dragon-ISO.App.csproj", "{80DCE039-3BBC-4D3F-B44B-51F324591C29}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dragon-ISO.Engine.IntegrationTests", "src\tests\Dragon-ISO.Engine.IntegrationTests\Dragon-ISO.Engine.IntegrationTests.csproj", "{A85E331D-026E-4BDE-B89C-0CC4C95001CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dragon-ISO.Console", "src\Dragon-ISO.Console\Dragon-ISO.Console.csproj", "{C3254998-9428-4264-A8FB-EAC9E1F9F432}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dragon-ISO.App.Tests", "src\tests\Dragon-ISO.App.Tests\Dragon-ISO.App.Tests.csproj", "{B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F0D24EAE-9225-4DC4-B3D2-6966077287A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F0D24EAE-9225-4DC4-B3D2-6966077287A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F0D24EAE-9225-4DC4-B3D2-6966077287A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F0D24EAE-9225-4DC4-B3D2-6966077287A0}.Release|Any CPU.Build.0 = Release|Any CPU + {E737E54B-73DE-4F74-909C-1F0F5CF82AC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E737E54B-73DE-4F74-909C-1F0F5CF82AC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E737E54B-73DE-4F74-909C-1F0F5CF82AC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E737E54B-73DE-4F74-909C-1F0F5CF82AC6}.Release|Any CPU.Build.0 = Release|Any CPU + {F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8}.Release|Any CPU.Build.0 = Release|Any CPU + {80DCE039-3BBC-4D3F-B44B-51F324591C29}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80DCE039-3BBC-4D3F-B44B-51F324591C29}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80DCE039-3BBC-4D3F-B44B-51F324591C29}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80DCE039-3BBC-4D3F-B44B-51F324591C29}.Release|Any CPU.Build.0 = Release|Any CPU + {A85E331D-026E-4BDE-B89C-0CC4C95001CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A85E331D-026E-4BDE-B89C-0CC4C95001CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A85E331D-026E-4BDE-B89C-0CC4C95001CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A85E331D-026E-4BDE-B89C-0CC4C95001CE}.Release|Any CPU.Build.0 = Release|Any CPU + {C3254998-9428-4264-A8FB-EAC9E1F9F432}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3254998-9428-4264-A8FB-EAC9E1F9F432}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3254998-9428-4264-A8FB-EAC9E1F9F432}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3254998-9428-4264-A8FB-EAC9E1F9F432}.Release|Any CPU.Build.0 = Release|Any CPU + {B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {F0D24EAE-9225-4DC4-B3D2-6966077287A0} = {46E05E34-8A87-4986-87D3-FE0DE4E05F44} + {E737E54B-73DE-4F74-909C-1F0F5CF82AC6} = {46E05E34-8A87-4986-87D3-FE0DE4E05F44} + {DBDF4A1D-4215-42D5-B456-2CE7159DF848} = {46E05E34-8A87-4986-87D3-FE0DE4E05F44} + {F8DBD7AB-E160-4B75-88FC-BAECDD4D44E8} = {DBDF4A1D-4215-42D5-B456-2CE7159DF848} + {80DCE039-3BBC-4D3F-B44B-51F324591C29} = {46E05E34-8A87-4986-87D3-FE0DE4E05F44} + {A85E331D-026E-4BDE-B89C-0CC4C95001CE} = {DBDF4A1D-4215-42D5-B456-2CE7159DF848} + {C3254998-9428-4264-A8FB-EAC9E1F9F432} = {46E05E34-8A87-4986-87D3-FE0DE4E05F44} + {B5A6F1E7-3D2C-4F89-9A55-7E1B2A4C8D6F} = {DBDF4A1D-4215-42D5-B456-2CE7159DF848} + EndGlobalSection +EndGlobal diff --git a/TeamsISO.Linux.slnf b/TeamsISO.Linux.slnf deleted file mode 100644 index 8b9ed27..0000000 --- a/TeamsISO.Linux.slnf +++ /dev/null @@ -1,12 +0,0 @@ -{ - "solution": { - "path": "TeamsISO.sln", - "projects": [ - "src/TeamsISO.Engine/TeamsISO.Engine.csproj", - "src/TeamsISO.Engine.NdiInterop/TeamsISO.Engine.NdiInterop.csproj", - "src/TeamsISO.Console/TeamsISO.Console.csproj", - "src/tests/TeamsISO.Engine.Tests/TeamsISO.Engine.Tests.csproj", - "src/tests/TeamsISO.Engine.IntegrationTests/TeamsISO.Engine.IntegrationTests.csproj" - ] - } -} diff --git a/TeamsISO.Windows.slnf b/TeamsISO.Windows.slnf deleted file mode 100644 index bc2b109..0000000 --- a/TeamsISO.Windows.slnf +++ /dev/null @@ -1,14 +0,0 @@ -{ - "solution": { - "path": "TeamsISO.sln", - "projects": [ - "src\\TeamsISO.Engine\\TeamsISO.Engine.csproj", - "src\\TeamsISO.Engine.NdiInterop\\TeamsISO.Engine.NdiInterop.csproj", - "src\\TeamsISO.Console\\TeamsISO.Console.csproj", - "src\\TeamsISO.App\\TeamsISO.App.csproj", - "src\\tests\\TeamsISO.Engine.Tests\\TeamsISO.Engine.Tests.csproj", - "src\\tests\\TeamsISO.Engine.IntegrationTests\\TeamsISO.Engine.IntegrationTests.csproj", - "src\\tests\\TeamsISO.App.Tests\\TeamsISO.App.Tests.csproj" - ] - } -} diff --git a/build-and-test.ps1 b/build-and-test.ps1 index 2a417c8..ada50ea 100644 --- a/build-and-test.ps1 +++ b/build-and-test.ps1 @@ -1,36 +1,29 @@ -# Quick build + test verification for TeamsISO. -# -# Run from the repo root: -# pwsh -ExecutionPolicy Bypass -File .\build-and-test.ps1 -# -# Builds TeamsISO.Windows.slnf in Release with TreatWarningsAsErrors=true -# (the Directory.Build.props default), then runs unit tests excluding the -# requires=ndi tier (those need a live NDI runtime). - $ErrorActionPreference = 'Stop' -if (-not (Test-Path 'TeamsISO.Windows.slnf')) { - throw "Run from the TeamsISO repo root." +if (-not (Test-Path 'Dragon-ISO.Windows.slnf')) { + throw "Run from the Dragon-ISO repo root." } +$env:Path = "C:\Program Files\dotnet;$env:Path" + Write-Host "=== dotnet --version ===" -ForegroundColor Cyan dotnet --version Write-Host "" Write-Host "=== Restore ===" -ForegroundColor Cyan -dotnet restore TeamsISO.Windows.slnf +dotnet restore Dragon-ISO.Windows.slnf if ($LASTEXITCODE -ne 0) { throw "Restore failed." } Write-Host "" Write-Host "=== Build (Release, TreatWarningsAsErrors=true) ===" -ForegroundColor Cyan -dotnet build TeamsISO.Windows.slnf --configuration Release --no-restore --nologo +dotnet build Dragon-ISO.Windows.slnf --configuration Release --no-restore --nologo if ($LASTEXITCODE -ne 0) { - throw "Build failed. Most likely cause for this batch: System.Windows.Automation needs an explicit Reference. If so, add to src/TeamsISO.App/TeamsISO.App.csproj inside an : " + throw "Build failed. Most likely cause for this batch: System.Windows.Automation needs an explicit Reference. If so, add to src/Dragon-ISO.App/Dragon-ISO.App.csproj inside an : " } Write-Host "" Write-Host "=== Tests (excluding requires=ndi) ===" -ForegroundColor Cyan -dotnet test TeamsISO.Windows.slnf ` +dotnet test Dragon-ISO.Windows.slnf ` --configuration Release ` --no-build ` --nologo ` diff --git a/docs/CONTROL-SURFACE.md b/docs/CONTROL-SURFACE.md index 0d8cc7d..57b0641 100644 --- a/docs/CONTROL-SURFACE.md +++ b/docs/CONTROL-SURFACE.md @@ -1,17 +1,17 @@ -# TeamsISO Control Surface — REST API +# Dragon-ISO Control Surface — REST API -TeamsISO can expose a localhost HTTP server so external controllers +Dragon-ISO can expose a localhost HTTP server so external controllers (Bitfocus Companion, Stream Deck plugins, Bome MIDI Translator, custom node-RED flows, command-line scripts) can drive it without a UI binding. ## Enabling -1. Open TeamsISO → Settings → DISPLAY tab. +1. Open Dragon-ISO → Settings → DISPLAY tab. 2. Tick "Control surface (Stream Deck / Companion)". 3. Default port is **9755**; change it via the port textbox if needed. -4. By default the server binds to `127.0.0.1` only — it is NOT reachable +4. By default the server binds to `127.0.0.1` only — it is NOT reachable from the LAN. -5. To allow other machines on the same network to drive TeamsISO (the +5. To allow other machines on the same network to drive Dragon-ISO (the "headless host PC + thin client" scenario), tick the nested "LAN-reachable" checkbox underneath. The settings panel will display the LAN URL (e.g. `http://192.168.1.42:9755/ui`) with a Copy button. @@ -31,20 +31,20 @@ netsh http add urlacl url=http://+:9755/ user=Everyone ``` Also confirm the Windows Firewall is letting inbound traffic to that port -through — `New-NetFirewallRule -DisplayName "TeamsISO Control Surface" -Direction Inbound -Protocol TCP -LocalPort 9755 -Action Allow` -in an elevated PowerShell, or add it through Windows Defender Firewall → -Advanced Settings → Inbound Rules. +through — `New-NetFirewallRule -DisplayName "Dragon-ISO Control Surface" -Direction Inbound -Protocol TCP -LocalPort 9755 -Action Allow` +in an elevated PowerShell, or add it through Windows Defender Firewall → +Advanced Settings → Inbound Rules. ## Authentication -None — by design. In localhost-only mode, the loopback bind is the +None — by design. In localhost-only mode, the loopback bind is the security model: any process on the operator's machine can hit these endpoints, the same threat model as a Stream Deck's USB connection. In LAN-reachable mode, the assumption is a closed/trusted network (a production-control LAN, a dedicated show subnet, a private vlan). Any machine that can route to the host on the listener port can drive -TeamsISO. **Do not enable LAN-reachable mode on an untrusted network.** +Dragon-ISO. **Do not enable LAN-reachable mode on an untrusted network.** ## Response shape @@ -58,7 +58,7 @@ specific fields. Errors return HTTP 4xx/5xx with `{"error": "..."}`. ### `GET /ui` Self-contained HTML control panel. Open this in a browser to drive -TeamsISO from a phone, tablet, or second monitor. Lists participants live +Dragon-ISO from a phone, tablet, or second monitor. Lists participants live via the same `/ws` WebSocket the rest of the doc describes, and posts to the REST endpoints when you click. Single page, no external dependencies, loads in <50KB. @@ -70,7 +70,7 @@ alive?" probes. ```json { - "product": "TeamsISO", + "product": "Dragon-ISO", "version": "1.0.0.0", "endpoints": ["GET /participants", "POST /participants/{id}/iso", ...] } @@ -89,7 +89,7 @@ Snapshot of the current participant list as the UI sees it. "isOnline": true, "isEnabled": false, "customName": null, - "stateLabel": "—" + "stateLabel": "—" } ] } @@ -103,7 +103,7 @@ Enable or disable an ISO by participant Id. Body or query string: { "enabled": true, "customName": "Host" } ``` -`enabled` is optional — omitting it toggles the current state. `customName` +`enabled` is optional — omitting it toggles the current state. `customName` is optional and overrides the auto-generated NDI output name. ```sh @@ -174,7 +174,7 @@ Toggle per-output recording on or off. Body or query string: ``` `directory` is optional when `enabled=false`. Already-running ISOs are not -retroactively recorded — the operator should disable + re-enable a +retroactively recorded — the operator should disable + re-enable a participant to start recording it. ### `POST /recording/marker` @@ -191,8 +191,8 @@ curl -X POST 'http://127.0.0.1:9755/recording/marker?label=Guest+answer' ### `POST /notes` Append a timestamped line to today's show-notes file at -`%LOCALAPPDATA%\TeamsISO\Notes\.md`. Body or query string carries -`text`. Each line is prefixed with `**HH:mm:ss** —`; the file is markdown so +`%LOCALAPPDATA%\Dragon-ISO\Notes\.md`. Body or query string carries +`text`. Each line is prefixed with `**HH:mm:ss** —`; the file is markdown so it renders nicely in any editor. ```sh @@ -204,7 +204,7 @@ curl -X POST 'http://127.0.0.1:9755/notes?text=guest+segment+starts' Roll every active recording into a new chunk. Each running pipeline is disabled (recorder finalizes its `manifest.json`), waits ~150ms, then re- enabled (recorder opens a fresh subdirectory keyed by display name + -timestamp). Useful for chaptering between show segments — a Stream Deck +timestamp). Useful for chaptering between show segments — a Stream Deck button mapped to this gives operators "next segment" without losing the already-recorded footage. @@ -217,7 +217,7 @@ Response: { "ok": true, "action": "roll-recording", "rolled": 4 } ``` -## WebSocket — live state push +## WebSocket — live state push For controllers that want to light a button when an ISO goes LIVE without polling, connect to: @@ -240,37 +240,37 @@ snapshot is pushed within 250ms. Format: } ``` -Client→server messages are ignored for v1 — all commands go through REST. +Client→server messages are ignored for v1 — all commands go through REST. ## OSC over UDP Same command surface, different transport. Enable the OSC bridge in the -DISPLAY tab (default port **9000** — TouchOSC's default). Bound to +DISPLAY tab (default port **9000** — TouchOSC's default). Bound to `127.0.0.1` by default; honors the same LAN-reachable toggle as the REST -surface — when LAN mode is on, OSC binds to `0.0.0.0` so a TouchOSC tablet +surface — when LAN mode is on, OSC binds to `0.0.0.0` so a TouchOSC tablet on the same network can talk to the host directly. Address vocabulary: ``` -/teamsiso/iso "DisplayName" {0|1} — toggle/set ISO by display name -/teamsiso/iso/by-id "guid" {0|1} — toggle/set by Id -/teamsiso/preset "Name" — apply preset -/teamsiso/teams/mute — UIA toggle mute -/teamsiso/teams/camera — UIA toggle camera -/teamsiso/teams/leave — UIA leave -/teamsiso/teams/share — UIA share tray -/teamsiso/teams/raise-hand — UIA raise hand -/teamsiso/refresh-discovery — rebuild NDI finder -/teamsiso/stop-all — disable every ISO -/teamsiso/recording {0|1} — recording on/off (default dir) -/teamsiso/recording/marker "Label" — drop a marker on every active recording -/teamsiso/recording/roll — roll every active recording into a new chunk -/teamsiso/notes "Free-form note" — append a timestamped line to today's notes +/Dragon-ISO/iso "DisplayName" {0|1} — toggle/set ISO by display name +/Dragon-ISO/iso/by-id "guid" {0|1} — toggle/set by Id +/Dragon-ISO/preset "Name" — apply preset +/Dragon-ISO/teams/mute — UIA toggle mute +/Dragon-ISO/teams/camera — UIA toggle camera +/Dragon-ISO/teams/leave — UIA leave +/Dragon-ISO/teams/share — UIA share tray +/Dragon-ISO/teams/raise-hand — UIA raise hand +/Dragon-ISO/refresh-discovery — rebuild NDI finder +/Dragon-ISO/stop-all — disable every ISO +/Dragon-ISO/recording {0|1} — recording on/off (default dir) +/Dragon-ISO/recording/marker "Label" — drop a marker on every active recording +/Dragon-ISO/recording/roll — roll every active recording into a new chunk +/Dragon-ISO/notes "Free-form note" — append a timestamped line to today's notes ``` -Companion → Surfaces → "Generic OSC" supports outbound OSC; bind a button -press to e.g. `/teamsiso/iso "Jane" 1`. TouchOSC layouts can use the same +Companion → Surfaces → "Generic OSC" supports outbound OSC; bind a button +press to e.g. `/Dragon-ISO/iso "Jane" 1`. TouchOSC layouts can use the same addresses on the same UDP port. ## Bitfocus Companion recipe @@ -292,7 +292,7 @@ on the appropriate endpoint above. ## Future work -- **HTTPS / token auth** — for deployments that don't have a closed +- **HTTPS / token auth** — for deployments that don't have a closed network, layer TLS termination + a shared bearer token in front of the HttpListener. Out of scope for v1; the LAN-reachable mode is a trusted-network feature only. diff --git a/docs/REAL-TIME-RECORDING.md b/docs/REAL-TIME-RECORDING.md index b78ac16..1163368 100644 --- a/docs/REAL-TIME-RECORDING.md +++ b/docs/REAL-TIME-RECORDING.md @@ -1,4 +1,4 @@ -# Real-time H.264 recording +# Real-time H.264 recording The default recorder (`RawBgraRecorderSink`) writes uncompressed BGRA to disk and ships a `convert.cmd` for post-recording FFmpeg encoding. That's safe @@ -7,32 +7,32 @@ and ships a `convert.cmd` for post-recording FFmpeg encoding. That's safe For long shows or operators on slower disks, the engine ships a **`MediaFoundationRecorderSink`** that encodes to H.264 in real time using -Windows Media Foundation. Inline encoding cuts disk pressure ~10× and +Windows Media Foundation. Inline encoding cuts disk pressure ~10× and produces a finished `.mp4` without the convert step. It's behind a build flag because activating it requires adding a NuGet dependency. The structural code is already in -`src/TeamsISO.Engine/Pipeline/MediaFoundationRecorderSink.cs`. +`src/Dragon-ISO.Engine/Pipeline/MediaFoundationRecorderSink.cs`. -## Status — May 2026 +## Status — May 2026 **Activation deferred.** The Vortice.MediaFoundation 3.6.2 NuGet package -is referenced from `TeamsISO.Engine.csproj`, but the `MF_AVAILABLE` symbol +is referenced from `Dragon-ISO.Engine.csproj`, but the `MF_AVAILABLE` symbol is *not* defined. The scaffold in -`src/TeamsISO.Engine/Pipeline/MediaFoundationRecorderSink.cs` was written +`src/Dragon-ISO.Engine/Pipeline/MediaFoundationRecorderSink.cs` was written against an older Vortice API and needs a port pass before activation: -- `MFVersion` → not on `MediaFactory` in 3.6.2; pass the SDK version +- `MFVersion` → not on `MediaFactory` in 3.6.2; pass the SDK version directly to `MFStartup`. -- `MediaFactory.MF_LOW_LATENCY` → relocated to a different attribute +- `MediaFactory.MF_LOW_LATENCY` → relocated to a different attribute constants class. -- `IMFAttributes.SetUINT32` → replaced with a generic `Set` overload. +- `IMFAttributes.SetUINT32` → replaced with a generic `Set` overload. - `IMFMediaType.MajorType` / `.SubType` / `.AvgBitrate` properties - → now use `SetGUID(MFAttributeKeys.MajorType, ...)` etc. -- `VideoFormatGuids.RGB32` → renamed (likely `Rgb32`). -- `IMFMediaBuffer.Lock(out IntPtr, out int, out int)` → explicit out-param + → now use `SetGUID(MFAttributeKeys.MajorType, ...)` etc. +- `VideoFormatGuids.RGB32` → renamed (likely `Rgb32`). +- `IMFMediaBuffer.Lock(out IntPtr, out int, out int)` → explicit out-param signature, no longer returns a locked-buffer wrapper. -- `IMFSinkWriter.Finalize_` → renamed (likely `Finalize`). +- `IMFSinkWriter.Finalize_` → renamed (likely `Finalize`). Until the port lands, the `RawBgraRecorderSink` is the only IRecorderSink production uses. The raw recorder is reliable and FFmpeg post-processing @@ -45,7 +45,7 @@ disk pressure during the show. reference implementation lives in the Vortice samples repo under `samples/MediaFoundationSamples`. -2. **Define the `MF_AVAILABLE` build symbol** in `TeamsISO.Engine.csproj`: +2. **Define the `MF_AVAILABLE` build symbol** in `Dragon-ISO.Engine.csproj`: ```xml @@ -71,10 +71,10 @@ disk pressure during the show. ## What the MF recorder produces For each enabled ISO with recording on: -- `//output.mp4` — H.264 video at the engine's +- `//output.mp4` — H.264 video at the engine's configured resolution / framerate, target bitrate ~0.07 bits/pixel (~7 Mbps for 1080p30, ~3 Mbps for 720p30). -- `//markers.txt` — tab-separated marker offsets +- `//markers.txt` — tab-separated marker offsets from `IIsoController.AddRecordingMarker`. Manually chapter the .mp4 with `mp4chaps -c -i markers.txt output.mp4` (mp4chaps from the `mp4v2` tools). @@ -91,5 +91,5 @@ For each enabled ISO with recording on: | Reliable on legacy GPUs | Yes | Yes (MF falls back to software encoder if no hw H.264) | If your target machines have NVIDIA NVENC / Intel QuickSync, MF will use -the hardware encoder transparently — that's the path that gives you +the hardware encoder transparently — that's the path that gives you multi-stream realtime H.264 with low CPU. diff --git a/docs/RELEASING.md b/docs/RELEASING.md index a1617d4..f4312ab 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -1,4 +1,4 @@ -# Releasing TeamsISO +# Releasing Dragon-ISO The release workflow at `.forgejo/workflows/release.yml` runs on **annotated tag pushes matching `v*.*.*`**. It builds, tests, publishes, packages an MSI, and uploads the @@ -11,26 +11,26 @@ MSI as a release asset. separate Windows runner. Register one with `forgejo-runner register` against a Windows host that has the .NET 8 SDK + WiX SDK access (the WiX SDK pulls itself via NuGet at build time, so no separate install). -- The repository's **Create release on tag push** setting on (default), or skip it — +- The repository's **Create release on tag push** setting on (default), or skip it — the workflow will create the release if one doesn't exist. ## Cutting a release ```sh # Bump the version in Directory.Build.props if you haven't already. -git tag -a v1.0.0 -m "TeamsISO 1.0.0" +git tag -a v1.0.0 -m "Dragon-ISO 1.0.0" git push origin v1.0.0 ``` The workflow will: -1. Restore + build `TeamsISO.Windows.slnf` in Release with the tag's version. -2. Run unit tests (the `requires=ndi` integration tier is skipped — it needs a +1. Restore + build `Dragon-ISO.Windows.slnf` in Release with the tag's version. +2. Run unit tests (the `requires=ndi` integration tier is skipped — it needs a real NDI runtime which a CI runner won't have). -3. Publish `TeamsISO.App` and `TeamsISO.Console` for `win-x64`, +3. Publish `Dragon-ISO.App` and `Dragon-ISO.Console` for `win-x64`, framework-dependent (.NET 8 Desktop runtime is the user's responsibility). -4. Build `installer/TeamsISO.Installer.wixproj`, producing - `TeamsISO-Setup-.msi`. +4. Build `installer/Dragon-ISO.Installer.wixproj`, producing + `Dragon-ISO-Setup-.msi`. 5. Upload the MSI as a workflow artifact (downloadable from the run page). 6. Attach the MSI to the GitHub-style Release for the tag, creating the release first if it doesn't exist. Pre-release flag is set automatically when the @@ -39,13 +39,13 @@ The workflow will: ## Code signing The release workflow has optional signtool integration. It runs only when the -signing-cert secrets are configured on the repository — without them, builds +signing-cert secrets are configured on the repository — without them, builds remain unsigned and produce a SmartScreen warning on first launch. ### Enabling signing -Set these secrets on `forge.wilddragon.net/zgaetano/teamsiso` -→ Settings → Actions → Secrets: +Set these secrets on `forge.wilddragon.net/zgaetano/Dragon-ISO` +→ Settings → Actions → Secrets: | Secret | Required | Notes | | --- | --- | --- | @@ -56,7 +56,7 @@ Set these secrets on `forge.wilddragon.net/zgaetano/teamsiso` When all three are present, the workflow: 1. Decodes the PFX to a temp file on the runner before building. -2. Signs `publish/TeamsISO/TeamsISO.exe` after publish, before MSI build, so the +2. Signs `publish/Dragon-ISO/Dragon-ISO.exe` after publish, before MSI build, so the binary embedded in the MSI is signed too. 3. Signs the produced MSI itself after WiX builds it. 4. Wipes the temp PFX from disk. @@ -70,7 +70,7 @@ which is what current Microsoft / SmartScreen guidance requires. per-publisher over time; brand-new OV certs still trip the warning until enough downloads accumulate. - **EV (Extended Validation, ~$300/yr, hardware token).** SmartScreen-trusted - immediately. Token-based — to use one in CI you'll need to either (a) keep + immediately. Token-based — to use one in CI you'll need to either (a) keep the runner on a host with the token plugged in, or (b) move to a cloud signing service like Azure Trusted Signing or DigiCert KeyLocker. diff --git a/src/TeamsISO.App/AboutWindow.xaml b/src/Dragon-ISO.App/AboutWindow.xaml similarity index 96% rename from src/TeamsISO.App/AboutWindow.xaml rename to src/Dragon-ISO.App/AboutWindow.xaml index 2a06d13..9a2535d 100644 --- a/src/TeamsISO.App/AboutWindow.xaml +++ b/src/Dragon-ISO.App/AboutWindow.xaml @@ -1,9 +1,9 @@ - - @@ -62,7 +62,7 @@ Margin="0,0,0,16" RenderOptions.BitmapScalingMode="HighQuality"/> - @@ -146,12 +146,12 @@ Click="OnOpenLogs" Padding="14,6" Margin="0,0,8,0" - ToolTip="Open %LOCALAPPDATA%\TeamsISO\Logs in Explorer"/> + ToolTip="Open %LOCALAPPDATA%\DragonISO\Logs in Explorer"/> - + @@ -226,20 +226,20 @@ function paintTopology(t) { topoLabel.textContent = 'Teams hidden from LAN'; } else if (t.mode === 'public') { topoDot.className = 'dot amber'; - topoLabel.textContent = 'Public — raw Teams visible'; + topoLabel.textContent = 'Public — raw Teams visible'; } else { topoDot.className = 'dot gray'; topoLabel.textContent = 'Unknown'; } - const sends = (t.senders || []).join(', ') || '—'; - const recvs = (t.receivers || []).join(', ') || '—'; - topoDetail.textContent = 'send: ' + sends + ' · recv: ' + recvs; + const sends = (t.senders || []).join(', ') || '—'; + const recvs = (t.receivers || []).join(', ') || '—'; + topoDetail.textContent = 'send: ' + sends + ' · recv: ' + recvs; } async function applyTopology() { const r = await post('/topology/apply'); if (r && r.ok) { - topoBanner.textContent = '✓ ' + (r.note || 'Applied. Restart Microsoft Teams for it to take effect.'); + topoBanner.textContent = '✓ ' + (r.note || 'Applied. Restart Microsoft Teams for it to take effect.'); topoBanner.classList.add('show'); setTimeout(() => topoBanner.classList.remove('show'), 8000); } @@ -250,7 +250,7 @@ async function restoreTopology() { if (!confirm('Restore default NDI groups? Teams will broadcast on public again after you restart it.')) return; const r = await post('/topology/restore'); if (r && r.ok) { - topoBanner.textContent = '✓ Defaults restored. Restart Microsoft Teams for it to take effect.'; + topoBanner.textContent = '✓ Defaults restored. Restart Microsoft Teams for it to take effect.'; topoBanner.classList.add('show'); setTimeout(() => topoBanner.classList.remove('show'), 8000); } @@ -282,15 +282,15 @@ const openPanels = new Set(); function shortFps(v) { for (const [k, label] of FRAMERATE_OPTS) if (k === v) return label; - return v || '—'; + return v || '—'; } function shortRes(v) { for (const [k, label] of RESOLUTION_OPTS) if (k === v) return label; - return v || '—'; + return v || '—'; } function shortAudio(v) { for (const [k, label] of AUDIO_OPTS) if (k === v) return label; - return v || '—'; + return v || '—'; } function buildSelect(opts, current) { @@ -323,21 +323,21 @@ function render(participants) { const row = document.createElement('div'); row.className = 'participant-row'; const stateColor = p.isEnabled ? 'cyan' : (p.isOnline ? 'gray' : 'coral'); - // Live preview tile — cache-bust with a 1s-bucket query param so the + // Live preview tile — cache-bust with a 1s-bucket query param so the // browser refreshes the image without flickering on every WS message. const bust = Math.floor(Date.now() / 1000); const previewUrl = '/participants/' + p.id + '/thumbnail.jpg?t=' + bust; row.innerHTML = """" + """" + - """" + + """" + ""
"" + ""
"" + ""
"" + ""
"" + ""
"" + """" + - """" + + """" + """" + ""
""; const img = row.querySelector('img.preview'); @@ -346,7 +346,7 @@ function render(participants) { const subEl = row.querySelector('.sub'); subEl.textContent = (p.stateLabel || (p.isOnline ? 'online' : 'offline')) + - (p.customName ? ' · ' + p.customName : ''); + (p.customName ? ' · ' + p.customName : ''); if (isOverride) { const pill = document.createElement('span'); pill.className = 'ovr-pill'; @@ -354,10 +354,10 @@ function render(participants) { subEl.appendChild(pill); } row.querySelector('.cfg-caption').textContent = - shortFps(eff.framerate) + ' · ' + shortRes(eff.resolution) + ' · ' + shortAudio(eff.audio); + shortFps(eff.framerate) + ' · ' + shortRes(eff.resolution) + ' · ' + shortAudio(eff.audio); const enableBtn = row.querySelector('.enable-btn'); enableBtn.className = 'enable-btn ' + (p.isEnabled ? 'live' : ''); - enableBtn.textContent = p.isEnabled ? '● LIVE' : 'Enable'; + enableBtn.textContent = p.isEnabled ? '● LIVE' : 'Enable'; enableBtn.onclick = () => post('/participants/iso', { displayName: p.displayName, enabled: !p.isEnabled, @@ -419,7 +419,7 @@ function render(participants) { } function connect() { - setConn('gray', 'connecting…'); + setConn('gray', 'connecting…'); const ws = new WebSocket( (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/ws'); ws.onopen = () => { setConn('green', 'live'); fetchTopology(); }; @@ -430,7 +430,7 @@ function connect() { } catch (e) { console.warn(e); } }; ws.onclose = () => { - setConn('coral', 'disconnected — retry in 3s'); + setConn('coral', 'disconnected — retry in 3s'); setTimeout(connect, 3000); }; ws.onerror = () => setConn('coral', 'error'); @@ -438,7 +438,7 @@ function connect() { connect(); // Re-poll topology every 30s in case the operator changes the machine NDI -// config externally (NDI Access Manager, manual edit). Cheap — one HTTP GET. +// config externally (NDI Access Manager, manual edit). Cheap — one HTTP GET. setInterval(fetchTopology, 30000); diff --git a/src/TeamsISO.App/Services/ControlSurface/Endpoints/HomeEndpoints.cs b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/HomeEndpoints.cs similarity index 88% rename from src/TeamsISO.App/Services/ControlSurface/Endpoints/HomeEndpoints.cs rename to src/Dragon-ISO.App/Services/ControlSurface/Endpoints/HomeEndpoints.cs index bacab7e..dfb0608 100644 --- a/src/TeamsISO.App/Services/ControlSurface/Endpoints/HomeEndpoints.cs +++ b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/HomeEndpoints.cs @@ -1,19 +1,19 @@ -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; -// GET / — server info + endpoint catalogue. Returned as the JSON +// GET / — server info + endpoint catalogue. Returned as the JSON // homepage when a Companion / Stream Deck plugin first probes the // surface; humans see it via curl http://127.0.0.1:9755/. public sealed partial class ControlSurfaceServer { private object GetServerInfo() { - // Best-effort engine snapshot — wrapped in TryRead so a transient + // Best-effort engine snapshot — wrapped in TryRead so a transient // controller error doesn't 500 the homepage poll. var settings = TryRead(() => _controller.GlobalSettings); var groups = TryRead(() => _controller.GroupSettings); return new { - product = "TeamsISO", + product = "Dragon-ISO", version = typeof(ControlSurfaceServer).Assembly.GetName().Version?.ToString() ?? "unknown", engine = new { diff --git a/src/TeamsISO.App/Services/ControlSurface/Endpoints/NotesEndpoints.cs b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/NotesEndpoints.cs similarity index 67% rename from src/TeamsISO.App/Services/ControlSurface/Endpoints/NotesEndpoints.cs rename to src/Dragon-ISO.App/Services/ControlSurface/Endpoints/NotesEndpoints.cs index 3060ffd..b8b5cde 100644 --- a/src/TeamsISO.App/Services/ControlSurface/Endpoints/NotesEndpoints.cs +++ b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/NotesEndpoints.cs @@ -1,11 +1,11 @@ -using System.Collections.Specialized; +using System.Collections.Specialized; using System.Text.Json; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; -// /notes/* route handlers — append-only operator show-notes file. +// /notes/* route handlers — append-only operator show-notes file. // -// POST /notes (body: { "text": "..." }) → AppendNote +// POST /notes (body: { "text": "..." }) → AppendNote public sealed partial class ControlSurfaceServer { private object AppendNote(JsonElement body, NameValueCollection query) diff --git a/src/TeamsISO.App/Services/ControlSurface/Endpoints/ParticipantsEndpoints.cs b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/ParticipantsEndpoints.cs similarity index 91% rename from src/TeamsISO.App/Services/ControlSurface/Endpoints/ParticipantsEndpoints.cs rename to src/Dragon-ISO.App/Services/ControlSurface/Endpoints/ParticipantsEndpoints.cs index 4c7232d..d7752bd 100644 --- a/src/TeamsISO.App/Services/ControlSurface/Endpoints/ParticipantsEndpoints.cs +++ b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/ParticipantsEndpoints.cs @@ -1,24 +1,24 @@ -using System.Collections.Specialized; +using System.Collections.Specialized; using System.Text.Json; -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; // /participants/* route handlers. Anything that reads or writes // participant + per-pipeline state lives here. // -// GET /participants → GetParticipants -// POST /participants/{id}/iso → ToggleIsoByIdAsync -// POST /participants/iso → ToggleIsoByNameAsync -// POST /participants/{id}/override → SetIsoOverrideByIdAsync -// DELETE /participants/{id}/override → ClearIsoOverrideByIdAsync +// GET /participants → GetParticipants +// POST /participants/{id}/iso → ToggleIsoByIdAsync +// POST /participants/iso → ToggleIsoByNameAsync +// POST /participants/{id}/override → SetIsoOverrideByIdAsync +// DELETE /participants/{id}/override → ClearIsoOverrideByIdAsync public sealed partial class ControlSurfaceServer { private object GetParticipants() { var vm = _viewModel(); if (vm is null) return new { participants = Array.Empty() }; - // Synchronously snapshot on the UI thread — ObservableCollection + // Synchronously snapshot on the UI thread — ObservableCollection // isn't safe to enumerate from this request handler's thread-pool // task, and the ParticipantViewModel property reads chase // data-binding state. @@ -57,7 +57,7 @@ public sealed partial class ControlSurfaceServer } /// - /// POST /participants/{id}/override — set or replace the per-pipeline + /// POST /participants/{id}/override — set or replace the per-pipeline /// override. Body fields: framerate (enum string), resolution (enum /// string), aspect (enum string), audio (enum string). All fields are /// optional; missing fields fall back to the current global value. @@ -87,7 +87,7 @@ public sealed partial class ControlSurfaceServer } }; } - /// DELETE /participants/{id}/override — pipeline reverts to global settings. + /// DELETE /participants/{id}/override — pipeline reverts to global settings. private async Task ClearIsoOverrideByIdAsync(string path) { var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); @@ -151,7 +151,7 @@ public sealed partial class ControlSurfaceServer if (vm is null || dispatcher is null) return new { ok = false, error = "view-model not ready" }; - // Look up the VM and snapshot its current state on the UI thread — + // Look up the VM and snapshot its current state on the UI thread — // ObservableCollection enumeration and view-model property reads // both need to happen there. var lookup = await dispatcher.InvokeAsync(() => diff --git a/src/TeamsISO.App/Services/ControlSurface/Endpoints/PresetsEndpoints.cs b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/PresetsEndpoints.cs similarity index 86% rename from src/TeamsISO.App/Services/ControlSurface/Endpoints/PresetsEndpoints.cs rename to src/Dragon-ISO.App/Services/ControlSurface/Endpoints/PresetsEndpoints.cs index 09ebdc1..102a4fa 100644 --- a/src/TeamsISO.App/Services/ControlSurface/Endpoints/PresetsEndpoints.cs +++ b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/PresetsEndpoints.cs @@ -1,10 +1,10 @@ -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; // /presets/* route handlers. // -// POST /presets/refresh-discovery → RefreshDiscovery -// POST /presets/stop-all → StopAllAsync -// POST /presets/{name}/apply → ApplyPresetAsync +// POST /presets/refresh-discovery → RefreshDiscovery +// POST /presets/stop-all → StopAllAsync +// POST /presets/{name}/apply → ApplyPresetAsync public sealed partial class ControlSurfaceServer { private object RefreshDiscovery() @@ -20,7 +20,7 @@ public sealed partial class ControlSurfaceServer var dispatcher = System.Windows.Application.Current?.Dispatcher; if (dispatcher is null) return new { ok = false, error = "dispatcher not ready" }; - // Snapshot the enabled set on the UI thread — ObservableCollection + // Snapshot the enabled set on the UI thread — ObservableCollection // isn't safe to enumerate from a thread-pool task, and reading the // IsEnabled property indirectly walks the data-binding system. var enabled = await dispatcher.InvokeAsync(() => @@ -50,7 +50,7 @@ public sealed partial class ControlSurfaceServer if (vm is null || dispatcher is null) return new { ok = false, error = "view-model not ready" }; - // Snapshot participants on the UI thread — ObservableCollection + // Snapshot participants on the UI thread — ObservableCollection // enumeration and ParticipantViewModel state reads both need to // happen there. PresetApplier marshals subsequent property writes // via the dispatcher. diff --git a/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/TeamsEndpoints.cs b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/TeamsEndpoints.cs new file mode 100644 index 0000000..dd60254 --- /dev/null +++ b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/TeamsEndpoints.cs @@ -0,0 +1,22 @@ +namespace DragonISO.App.Services; + +// /teams/* route handlers — UIAutomation-driven in-call controls. +// +// POST /teams/mute → InvokeTeams(ToggleMute, "mute") +// POST /teams/camera → InvokeTeams(ToggleCamera, "camera") +// POST /teams/leave → InvokeTeams(LeaveCall, "leave") +// POST /teams/share → InvokeTeams(OpenShareTray, "share") +// POST /teams/raise-hand → InvokeTeams(ToggleRaiseHand, "raise-hand") +public sealed partial class ControlSurfaceServer +{ + private object InvokeTeams(Func invoke, string action) + { + var result = invoke(); + return new + { + ok = result == TeamsControlBridge.InvokeResult.Invoked, + action, + result = result.ToString(), + }; + } +} diff --git a/src/TeamsISO.App/Services/ControlSurface/Endpoints/ThumbnailEndpoint.cs b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/ThumbnailEndpoint.cs similarity index 96% rename from src/TeamsISO.App/Services/ControlSurface/Endpoints/ThumbnailEndpoint.cs rename to src/Dragon-ISO.App/Services/ControlSurface/Endpoints/ThumbnailEndpoint.cs index ba07c4e..8ad2568 100644 --- a/src/TeamsISO.App/Services/ControlSurface/Endpoints/ThumbnailEndpoint.cs +++ b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/ThumbnailEndpoint.cs @@ -1,8 +1,8 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; -// GET /participants/{id}/thumbnail.bmp — small BMP of the latest +// GET /participants/{id}/thumbnail.bmp — small BMP of the latest // processed frame. Used by the embedded HTML control panel for live // preview tiles with a cache-busting query param at ~1Hz. // diff --git a/src/TeamsISO.App/Services/ControlSurface/Endpoints/TopologyEndpoints.cs b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/TopologyEndpoints.cs similarity index 78% rename from src/TeamsISO.App/Services/ControlSurface/Endpoints/TopologyEndpoints.cs rename to src/Dragon-ISO.App/Services/ControlSurface/Endpoints/TopologyEndpoints.cs index c4a1b1a..8df0bd6 100644 --- a/src/TeamsISO.App/Services/ControlSurface/Endpoints/TopologyEndpoints.cs +++ b/src/Dragon-ISO.App/Services/ControlSurface/Endpoints/TopologyEndpoints.cs @@ -1,19 +1,19 @@ -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; -// /topology/* route handlers — read + apply / restore the machine NDI +// /topology/* route handlers — read + apply / restore the machine NDI // access-manager config so the operator can flip transcoder topology // without leaving the web UI. // -// GET /topology → GetTopology -// POST /topology/apply → ApplyTopologyAsync -// POST /topology/restore → RestoreTopologyAsync +// GET /topology → GetTopology +// POST /topology/apply → ApplyTopologyAsync +// POST /topology/restore → RestoreTopologyAsync public sealed partial class ControlSurfaceServer { /// /// Report the current NDI machine topology. "mode" is "hidden" when /// local senders are confined to the private group (raw Teams sources /// invisible to the rest of the LAN), "public" otherwise. Reads the - /// machine NDI config file directly — no caching, so the result + /// machine NDI config file directly — no caching, so the result /// reflects whatever state the file is in right now (including /// manual edits). /// @@ -37,9 +37,9 @@ public sealed partial class ControlSurfaceServer } /// - /// Apply the transcoder topology: machine senders → teamsiso-input, - /// receivers → public + teamsiso-input; engine groups updated to - /// match (discover from teamsiso-input, broadcast on public). Operator + /// Apply the transcoder topology: machine senders → Dragon-ISO-input, + /// receivers → public + Dragon-ISO-input; engine groups updated to + /// match (discover from Dragon-ISO-input, broadcast on public). Operator /// MUST restart Teams afterward for it to read the new NDI config. /// private async Task ApplyTopologyAsync() @@ -51,7 +51,7 @@ public sealed partial class ControlSurfaceServer } // Mirror what the WPF settings VM does so the engine groups + // machine config stay in lockstep. - var ourGroups = new TeamsISO.Engine.Domain.NdiGroupSettings( + var ourGroups = new DragonISO.Engine.Domain.NdiGroupSettings( DiscoveryGroups: NdiAccessManagerConfig.TranscoderInputGroup, OutputGroups: "public"); await _controller.SetGroupSettingsAsync(ourGroups, CancellationToken.None); @@ -76,7 +76,7 @@ public sealed partial class ControlSurfaceServer { return new { ok = false, error = result.ErrorMessage, configPath = result.ConfigPath }; } - var ourGroups = new TeamsISO.Engine.Domain.NdiGroupSettings( + var ourGroups = new DragonISO.Engine.Domain.NdiGroupSettings( DiscoveryGroups: null, OutputGroups: null); await _controller.SetGroupSettingsAsync(ourGroups, CancellationToken.None); diff --git a/src/TeamsISO.App/Services/ControlSurface/WebSocketHub.cs b/src/Dragon-ISO.App/Services/ControlSurface/WebSocketHub.cs similarity index 90% rename from src/TeamsISO.App/Services/ControlSurface/WebSocketHub.cs rename to src/Dragon-ISO.App/Services/ControlSurface/WebSocketHub.cs index bcdad60..ebaac2c 100644 --- a/src/TeamsISO.App/Services/ControlSurface/WebSocketHub.cs +++ b/src/Dragon-ISO.App/Services/ControlSurface/WebSocketHub.cs @@ -1,18 +1,18 @@ -using System.Net.WebSockets; +using System.Net.WebSockets; using System.Text; using System.Text.Json; using Microsoft.Extensions.Logging; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; -// GET /ws — upgrades to WebSocket and pushes participant-list snapshots +// GET /ws — upgrades to WebSocket and pushes participant-list snapshots // at 4Hz with diffing (no push when nothing changed). Lets controllers // stay live-synced without polling /participants. // // Lifecycle: -// • Server's accept loop upgrades the request and hands the socket here. -// • HandleWebSocketAsync owns the connection until the client closes. -// • The Start() method wires a 4Hz DispatcherTimer that calls +// • Server's accept loop upgrades the request and hands the socket here. +// • HandleWebSocketAsync owns the connection until the client closes. +// • The Start() method wires a 4Hz DispatcherTimer that calls // PushSnapshotIfChangedAsync to fan out to every connected client. public sealed partial class ControlSurfaceServer { @@ -20,7 +20,7 @@ public sealed partial class ControlSurfaceServer /// Owns a single client connection until it closes. Sends an immediate /// snapshot on connect (so the client doesn't have to wait up to 250ms /// for the next push tick), then sits in a receive loop draining any - /// incoming text — we ignore client→server messages for v1 since all + /// incoming text — we ignore client→server messages for v1 since all /// commands are REST. The receive loop is the canonical way to detect /// graceful close: when WebSocket.ReceiveAsync returns CloseReceived, /// we close back and remove the client. @@ -33,7 +33,7 @@ public sealed partial class ControlSurfaceServer try { - // Initial snapshot — fetch synchronously on the UI thread so the + // Initial snapshot — fetch synchronously on the UI thread so the // ObservableCollection isn't enumerated cross-thread. await SendAsync(ws, await GetSnapshotJsonAsync()); @@ -68,7 +68,7 @@ public sealed partial class ControlSurfaceServer /// Dispatcher-tick handler. Reads the current participants snapshot, /// and if it differs from what we last pushed, broadcasts the new /// JSON to every connected client. Diffing on the JSON string is - /// cheap and saves wire bytes when nothing's actually changing — + /// cheap and saves wire bytes when nothing's actually changing — /// typical operator workflow has long periods of no state churn /// between meetings. /// diff --git a/src/TeamsISO.App/Services/ControlSurfaceServer.cs b/src/Dragon-ISO.App/Services/ControlSurfaceServer.cs similarity index 84% rename from src/TeamsISO.App/Services/ControlSurfaceServer.cs rename to src/Dragon-ISO.App/Services/ControlSurfaceServer.cs index 6421e9a..1f6eb0b 100644 --- a/src/TeamsISO.App/Services/ControlSurfaceServer.cs +++ b/src/Dragon-ISO.App/Services/ControlSurfaceServer.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Net; @@ -8,39 +8,39 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Windows.Threading; using Microsoft.Extensions.Logging; -using TeamsISO.App.ViewModels; -using TeamsISO.Engine.Controller; -using TeamsISO.Engine.Domain; +using DragonISO.App.ViewModels; +using DragonISO.Engine.Controller; +using DragonISO.Engine.Domain; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Localhost-only HTTP control surface. Lets external controllers (Bitfocus /// Companion, Stream Deck plugins, Bome MIDI Translator, custom node-RED flows, -/// etc.) drive TeamsISO without needing to embed a UI binding. +/// etc.) drive Dragon-ISO without needing to embed a UI binding. /// -/// Bound to 127.0.0.1 by default — exposing this to LAN would require auth, and -/// the typical operator workflow is "Stream Deck on the same machine as TeamsISO". +/// Bound to 127.0.0.1 by default — exposing this to LAN would require auth, and +/// the typical operator workflow is "Stream Deck on the same machine as Dragon-ISO". /// If a future user needs LAN access, add a token check + bind to a configurable /// address; both are deliberately punted for v1. /// /// Endpoints (all return application/json): /// -/// GET / — server info + endpoint list -/// GET /participants — list of {id, displayName, isOnline, isEnabled} -/// POST /participants/{id}/iso — body {"enabled":bool,"customName":string?} -/// POST /participants/iso — body {"displayName":string,"enabled":bool} (look up by name) -/// POST /presets/{name}/apply — apply a saved preset -/// POST /presets/refresh-discovery — rebuild NDI finder -/// POST /presets/stop-all — disable every running ISO -/// POST /teams/mute — toggle mute via UIA -/// POST /teams/camera — toggle camera via UIA -/// POST /teams/leave — leave the call via UIA -/// POST /teams/share — open share tray via UIA -/// POST /teams/raise-hand — toggle raise hand via UIA -/// POST /recording — body {"enabled":bool,"directory":string?} +/// GET / — server info + endpoint list +/// GET /participants — list of {id, displayName, isOnline, isEnabled} +/// POST /participants/{id}/iso — body {"enabled":bool,"customName":string?} +/// POST /participants/iso — body {"displayName":string,"enabled":bool} (look up by name) +/// POST /presets/{name}/apply — apply a saved preset +/// POST /presets/refresh-discovery — rebuild NDI finder +/// POST /presets/stop-all — disable every running ISO +/// POST /teams/mute — toggle mute via UIA +/// POST /teams/camera — toggle camera via UIA +/// POST /teams/leave — leave the call via UIA +/// POST /teams/share — open share tray via UIA +/// POST /teams/raise-hand — toggle raise hand via UIA +/// POST /recording — body {"enabled":bool,"directory":string?} /// -/// All POST bodies are optional — endpoints that take parameters accept them +/// All POST bodies are optional — endpoints that take parameters accept them /// either via JSON body or via query string (?enabled=true&customName=Host). /// This is friendly to Companion's "URL with query string" mode. /// @@ -93,11 +93,11 @@ public sealed partial class ControlSurfaceServer : IAsyncDisposable /// TCP port to listen on. /// /// When true, binds to all interfaces (http://+:port/) so other - /// machines on the LAN can reach the control surface — typical for + /// machines on the LAN can reach the control surface — typical for /// "headless show machine + thin client controller" setups. When false /// (default), binds to 127.0.0.1 only. /// - /// LAN binding requires either running TeamsISO as Administrator OR a + /// LAN binding requires either running Dragon-ISO as Administrator OR a /// one-time URL ACL reservation at the OS level: /// netsh http add urlacl url=http://+:9755/ user=Everyone /// If neither is in place the listener throws AccessDeniedException @@ -136,7 +136,7 @@ public sealed partial class ControlSurfaceServer : IAsyncDisposable // ObservableCollection-backed Participants list without thread races. 4Hz // is fast enough that operators see immediate feedback when they flip an // ISO on the Stream Deck without us spamming the wire when nothing's - // changing — the snapshot serializer dedupes against the previous push. + // changing — the snapshot serializer dedupes against the previous push. var dispatcher = System.Windows.Application.Current?.Dispatcher; if (dispatcher is not null) { @@ -205,7 +205,7 @@ public sealed partial class ControlSurfaceServer : IAsyncDisposable var res = ctx.Response; // Tracks whether we should call res.Close() in the finally. WebSocket // upgrades transfer ownership of the connection to the WebSocket - // instance — closing the response here would tear down the freshly- + // instance — closing the response here would tear down the freshly- // upgraded socket immediately. So we skip the finally close on that // path. var closeResponseInFinally = true; @@ -235,7 +235,7 @@ public sealed partial class ControlSurfaceServer : IAsyncDisposable var body = await ReadBodyAsync(req); - // GET /ui — embedded HTML control panel. Served as text/html + // GET /ui — embedded HTML control panel. Served as text/html // rather than JSON so a browser renders it directly. if (req.HttpMethod == "GET" && path == "/ui") { @@ -247,7 +247,7 @@ public sealed partial class ControlSurfaceServer : IAsyncDisposable return; } - // GET /participants/{id}/thumbnail.bmp — small BMP of the latest + // GET /participants/{id}/thumbnail.bmp — small BMP of the latest // processed frame. Returns 404 when no pipeline is running for // this participant. The HTML control panel uses this URL with // a cache-busting query param every ~1s to drive live preview @@ -293,7 +293,7 @@ public sealed partial class ControlSurfaceServer : IAsyncDisposable ("POST", "/teams/share") => InvokeTeams(TeamsControlBridge.OpenShareTray, "share"), ("POST", "/teams/raise-hand") => InvokeTeams(TeamsControlBridge.ToggleRaiseHand, "raise-hand"), // /recording routes removed alongside the rest of the recording surface. - // Topology — read the machine NDI config to report whether raw + // Topology — read the machine NDI config to report whether raw // Teams NDI sources are hidden from the LAN, and let the // operator apply / restore without leaving the web UI. ("GET", "/topology") => GetTopology(), @@ -342,7 +342,7 @@ public sealed partial class ControlSurfaceServer : IAsyncDisposable } } - // ─── handlers ─────────────────────────────────────────────────────── + // ─── handlers ─────────────────────────────────────────────────────── // // Endpoint bodies live in Services/ControlSurface/Endpoints/*.cs as // partials of this class. See HomeEndpoints, ParticipantsEndpoints, @@ -353,7 +353,7 @@ public sealed partial class ControlSurfaceServer : IAsyncDisposable [SuppressMessage("Performance", "CA1822", Justification = "Method group used in switch arm.")] private object NotFound() => new { error = "not found" }; - // ─── helpers ──────────────────────────────────────────────────────── + // ─── helpers ──────────────────────────────────────────────────────── private static async Task ReadBodyAsync(HttpListenerRequest req) { diff --git a/src/TeamsISO.App/Services/DiagnosticsBundle.cs b/src/Dragon-ISO.App/Services/DiagnosticsBundle.cs similarity index 92% rename from src/TeamsISO.App/Services/DiagnosticsBundle.cs rename to src/Dragon-ISO.App/Services/DiagnosticsBundle.cs index c0297d6..c5b8b69 100644 --- a/src/TeamsISO.App/Services/DiagnosticsBundle.cs +++ b/src/Dragon-ISO.App/Services/DiagnosticsBundle.cs @@ -1,30 +1,30 @@ -using System.IO; +using System.IO; using System.IO.Compression; using System.Reflection; using System.Text; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Gathers logs + config + presets + version metadata into a single .zip the /// operator can attach to a bug report. Surfaced via the "Export diagnostics" /// button in About. /// -/// We deliberately do NOT include screenshots or any process/memory dumps — +/// We deliberately do NOT include screenshots or any process/memory dumps — /// that's outside the scope of a v1 support bundle and would raise privacy -/// flags. The bundle has only files the user already wrote with their TeamsISO +/// flags. The bundle has only files the user already wrote with their Dragon-ISO /// usage; nothing here is hidden state. /// public static class DiagnosticsBundle { /// /// Build the bundle and return the path it was written to. - /// Throws on disk failure — the caller toasts/dialogs. + /// Throws on disk failure — the caller toasts/dialogs. /// public static string Export() { var ts = DateTimeOffset.Now.ToString("yyyyMMdd-HHmmss"); - var fileName = $"teamsiso-diagnostics-{ts}.zip"; + var fileName = $"Dragon-ISO-diagnostics-{ts}.zip"; var outDir = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); var downloads = Path.Combine(outDir, "Downloads"); if (!Directory.Exists(downloads)) downloads = outDir; // fall back to ~/ @@ -51,9 +51,9 @@ public static class DiagnosticsBundle ?? asm.GetName().Version?.ToString() ?? "unknown"; var sb = new StringBuilder(); - sb.AppendLine("TeamsISO diagnostic bundle"); + sb.AppendLine("Dragon-ISO diagnostic bundle"); sb.AppendLine($"Generated: {DateTimeOffset.Now:o}"); - sb.AppendLine($"TeamsISO version: {version}"); + sb.AppendLine($"Dragon-ISO version: {version}"); sb.AppendLine($".NET runtime: {Environment.Version}"); sb.AppendLine($"OS: {Environment.OSVersion}"); sb.AppendLine($"Machine: {Environment.MachineName}"); @@ -115,17 +115,17 @@ public static class DiagnosticsBundle private static string LogsDirectory => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "TeamsISO", "Logs"); + "Dragon-ISO", "Logs"); private static string LocalAppDataPath(string fileName) => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "TeamsISO", fileName); + "Dragon-ISO", fileName); private static string AppDataPath(string fileName) => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "TeamsISO", fileName); + "Dragon-ISO", fileName); private static string NdiConfigPath() => Path.Combine( diff --git a/src/TeamsISO.App/Services/NdiAccessManagerConfig.cs b/src/Dragon-ISO.App/Services/NdiAccessManagerConfig.cs similarity index 93% rename from src/TeamsISO.App/Services/NdiAccessManagerConfig.cs rename to src/Dragon-ISO.App/Services/NdiAccessManagerConfig.cs index 62a3b65..098f80c 100644 --- a/src/TeamsISO.App/Services/NdiAccessManagerConfig.cs +++ b/src/Dragon-ISO.App/Services/NdiAccessManagerConfig.cs @@ -1,19 +1,19 @@ -using System.IO; +using System.IO; using System.Text.Json; using System.Text.Json.Nodes; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Reads and writes NDI Access Manager's per-user config at /// %APPDATA%\NDI\ndi-config.v1.json. This file controls global NDI behavior for -/// every NDI application on the machine — sender groups, receiver groups, RUDP/TCP +/// every NDI application on the machine — sender groups, receiver groups, RUDP/TCP /// transport toggles, allowed adapters, etc. NDI applications read it on startup, so /// changes here only take effect after restarting the affected app (Teams, OBS, etc.). /// /// We use it to implement the "transcoder topology" requested by the user: pin Teams' -/// raw at-source-resolution NDI broadcasts to a private group (teamsiso-input) so -/// they don't pollute the production network, while TeamsISO's own clean normalized ISO +/// raw at-source-resolution NDI broadcasts to a private group (Dragon-ISO-input) so +/// they don't pollute the production network, while Dragon-ISO's own clean normalized ISO /// outputs continue to broadcast on the standard Public group that downstream /// switchers and recorders default to. /// @@ -36,7 +36,7 @@ public static class NdiAccessManagerConfig /// Default name of the private group used for the transcoder topology. /// Matches the convention referenced in the NDI Network settings UI. /// - public const string TranscoderInputGroup = "teamsiso-input"; + public const string TranscoderInputGroup = "Dragon-ISO-input"; /// /// Result of an apply attempt. indicates the file was @@ -54,12 +54,12 @@ public static class NdiAccessManagerConfig /// Configures the machine-wide NDI groups so: /// /// All local senders (Teams, anything else) broadcast on - /// only — i.e. the private input group. + /// only — i.e. the private input group. /// All local receivers see both and - /// public so TeamsISO can discover Teams' sources AND any + /// public so Dragon-ISO can discover Teams' sources AND any /// standard public sources from elsewhere on the network. /// - /// TeamsISO's own per-pipeline OutputGroups still overrides the per-machine + /// Dragon-ISO's own per-pipeline OutputGroups still overrides the per-machine /// default at the sender level, so its normalized ISO outputs go on Public. /// /// Private group name for Teams' raw broadcasts. diff --git a/src/TeamsISO.App/Services/NotesService.cs b/src/Dragon-ISO.App/Services/NotesService.cs similarity index 76% rename from src/TeamsISO.App/Services/NotesService.cs rename to src/Dragon-ISO.App/Services/NotesService.cs index e53380f..844840e 100644 --- a/src/TeamsISO.App/Services/NotesService.cs +++ b/src/Dragon-ISO.App/Services/NotesService.cs @@ -1,13 +1,13 @@ -using System.IO; +using System.IO; using System.Text; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Append-only show-notes log. Each call writes a timestamped line to a daily -/// markdown file at %LOCALAPPDATA%\TeamsISO\Notes\<YYYY-MM-DD>.md. +/// markdown file at %LOCALAPPDATA%\Dragon-ISO\Notes\<YYYY-MM-DD>.md. /// Operators stamp notes via the REST POST /notes endpoint or the OSC -/// /teamsiso/notes "..." address — typically wired to a Stream Deck +/// /Dragon-ISO/notes "..." address — typically wired to a Stream Deck /// button so a note can be left without leaving the show. /// /// We deliberately don't surface the notes inside the WPF UI: the file is @@ -20,17 +20,17 @@ public static class NotesService private static readonly object _gate = new(); /// - /// Test-only seam — when set, overrides the default - /// %LOCALAPPDATA%\TeamsISO\Notes path. Lets tests write to a + /// Test-only seam — when set, overrides the default + /// %LOCALAPPDATA%\Dragon-ISO\Notes path. Lets tests write to a /// tempdir without polluting the dev's real notes folder. - /// InternalsVisibleTo grants TeamsISO.App.Tests access. + /// InternalsVisibleTo grants DragonISO.App.Tests access. /// internal static string? DirectoryOverride { get; set; } private static string NotesDirectory => DirectoryOverride ?? Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "TeamsISO", "Notes"); + "Dragon-ISO", "Notes"); /// Today's notes file path (created lazily on first append). public static string TodayPath => @@ -50,10 +50,10 @@ public static class NotesService { Directory.CreateDirectory(NotesDirectory); var path = TodayPath; - var line = $"- **{DateTimeOffset.Now:HH:mm:ss}** — {text.Trim()}{Environment.NewLine}"; + var line = $"- **{DateTimeOffset.Now:HH:mm:ss}** — {text.Trim()}{Environment.NewLine}"; if (!File.Exists(path)) { - var header = $"# TeamsISO show notes — {DateTimeOffset.Now:yyyy-MM-dd}{Environment.NewLine}{Environment.NewLine}"; + var header = $"# Dragon-ISO show notes — {DateTimeOffset.Now:yyyy-MM-dd}{Environment.NewLine}{Environment.NewLine}"; File.WriteAllText(path, header, Encoding.UTF8); } File.AppendAllText(path, line, Encoding.UTF8); diff --git a/src/TeamsISO.App/Services/OperatorPresetStore.cs b/src/Dragon-ISO.App/Services/OperatorPresetStore.cs similarity index 95% rename from src/TeamsISO.App/Services/OperatorPresetStore.cs rename to src/Dragon-ISO.App/Services/OperatorPresetStore.cs index 73050c0..cdf49ef 100644 --- a/src/TeamsISO.App/Services/OperatorPresetStore.cs +++ b/src/Dragon-ISO.App/Services/OperatorPresetStore.cs @@ -1,7 +1,7 @@ -using System.IO; +using System.IO; using System.Text.Json; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Persistent named snapshots of which participants should have ISOs enabled and @@ -10,7 +10,7 @@ namespace TeamsISO.App.Services; /// meeting load the same preset and auto-enable everyone whose display name /// matches. /// -/// Persisted as JSON at %LOCALAPPDATA%\TeamsISO\presets.json. We key by +/// Persisted as JSON at %LOCALAPPDATA%\Dragon-ISO\presets.json. We key by /// participant rather than Id /// because the Id is freshly generated for every meeting (Teams' NDI source /// identity isn't stable across sessions); display name is the operator's @@ -29,7 +29,7 @@ public static class OperatorPresetStore private static string PresetsPath => PathOverride ?? Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "TeamsISO", + "Dragon-ISO", "presets.json"); /// @@ -77,7 +77,7 @@ public static class OperatorPresetStore /// /// Returns the operator's startup preference (which preset, if any, should be /// auto-applied on launch). Defaults to (null, false) when no file exists - /// or the file predates the field — older preset.json files deserialize cleanly + /// or the file predates the field — older preset.json files deserialize cleanly /// because both fields are optional with default values. /// public static StartupPreference GetStartupPreference() @@ -153,8 +153,8 @@ public static class OperatorPresetStore /// /// Bundle format for the export/import surface. Wraps the preset list with /// a version stamp + an export timestamp so a future-format-aware importer - /// can migrate the data. We deliberately export a flat preset list — not - /// the full envelope — because StartupPreference is + /// can migrate the data. We deliberately export a flat preset list — not + /// the full envelope — because StartupPreference is /// machine-local (operator A's "auto-apply Friday Show" shouldn't follow /// the bundle to operator B's machine). /// @@ -163,7 +163,7 @@ public static class OperatorPresetStore DateTimeOffset ExportedAt, IReadOnlyList Presets) { - public const string CurrentSchema = "teamsiso-presets-bundle/v1"; + public const string CurrentSchema = "Dragon-ISO-presets-bundle/v1"; } /// @@ -181,7 +181,7 @@ public static class OperatorPresetStore } /// - /// Result of an import attempt — counts so the UI can toast a clear summary. + /// Result of an import attempt — counts so the UI can toast a clear summary. /// public sealed record ImportResult(int Added, int Overwritten, int Skipped, string? Error) { diff --git a/src/TeamsISO.App/Services/OscBridge.cs b/src/Dragon-ISO.App/Services/OscBridge.cs similarity index 76% rename from src/TeamsISO.App/Services/OscBridge.cs rename to src/Dragon-ISO.App/Services/OscBridge.cs index fc028c8..118ff12 100644 --- a/src/TeamsISO.App/Services/OscBridge.cs +++ b/src/Dragon-ISO.App/Services/OscBridge.cs @@ -1,39 +1,39 @@ -using System.Net; +using System.Net; using System.Net.Sockets; using System.Text; using Microsoft.Extensions.Logging; -using TeamsISO.App.ViewModels; -using TeamsISO.Engine.Controller; +using DragonISO.App.ViewModels; +using DragonISO.Engine.Controller; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// OSC over UDP. Companion, TouchOSC, Bome, and most lighting consoles speak /// OSC natively, so wrapping the same command surface in OSC opens the /// product to the broader live-show ecosystem without a Companion bridge. /// -/// Protocol — minimal OSC 1.0: +/// Protocol — minimal OSC 1.0: /// - Address pattern (null-terminated string, padded to 4-byte boundary) /// - Type tag (",iiisf" etc., null-terminated, padded to 4) /// - Args in order /// /// We don't implement bundles, time tags, blob args, or pattern matching -/// — none are needed for the verbs we support. If a sender uses bundles -/// we ignore them; if a sender uses a wildcard address ("/teamsiso/*") we +/// — none are needed for the verbs we support. If a sender uses bundles +/// we ignore them; if a sender uses a wildcard address ("/Dragon-ISO/*") we /// ignore it. Operators get a clear log line in either case. /// /// Routes: -/// /teamsiso/iso "DisplayName" {0|1} — toggle/set ISO by display name -/// /teamsiso/iso/by-id "guid" {0|1} — toggle/set by Id -/// /teamsiso/preset "Name" — apply preset -/// /teamsiso/teams/mute — UIA toggle mute -/// /teamsiso/teams/camera — UIA toggle camera -/// /teamsiso/teams/leave — UIA leave -/// /teamsiso/teams/share — UIA share tray -/// /teamsiso/teams/raise-hand — UIA raise hand -/// /teamsiso/refresh-discovery — rebuild NDI finder -/// /teamsiso/stop-all — disable every ISO -/// /teamsiso/recording {0|1} — recording on/off (default dir) +/// /Dragon-ISO/iso "DisplayName" {0|1} — toggle/set ISO by display name +/// /Dragon-ISO/iso/by-id "guid" {0|1} — toggle/set by Id +/// /Dragon-ISO/preset "Name" — apply preset +/// /Dragon-ISO/teams/mute — UIA toggle mute +/// /Dragon-ISO/teams/camera — UIA toggle camera +/// /Dragon-ISO/teams/leave — UIA leave +/// /Dragon-ISO/teams/share — UIA share tray +/// /Dragon-ISO/teams/raise-hand — UIA raise hand +/// /Dragon-ISO/refresh-discovery — rebuild NDI finder +/// /Dragon-ISO/stop-all — disable every ISO +/// /Dragon-ISO/recording {0|1} — recording on/off (default dir) /// public sealed class OscBridge : IAsyncDisposable { @@ -63,9 +63,9 @@ public sealed class OscBridge : IAsyncDisposable /// /// Start the OSC listener on the given UDP port. - /// flag selects between loopback (default — only this machine) and any- + /// flag selects between loopback (default — only this machine) and any- /// interface binding (LAN-reachable, for thin-client controllers). - /// Unlike the REST surface, UDP doesn't need a URL ACL — binding 0.0.0.0 + /// Unlike the REST surface, UDP doesn't need a URL ACL — binding 0.0.0.0 /// is just an unprivileged port reservation. /// public void Start(int port, bool bindToLan = false) @@ -147,25 +147,25 @@ public sealed class OscBridge : IAsyncDisposable var addr = msg.Address; switch (addr) { - case "/teamsiso/teams/mute": InvokeTeams(TeamsControlBridge.ToggleMute); return; - case "/teamsiso/teams/camera": InvokeTeams(TeamsControlBridge.ToggleCamera); return; - case "/teamsiso/teams/leave": InvokeTeams(TeamsControlBridge.LeaveCall); return; - case "/teamsiso/teams/share": InvokeTeams(TeamsControlBridge.OpenShareTray); return; - case "/teamsiso/teams/raise-hand":InvokeTeams(TeamsControlBridge.ToggleRaiseHand); return; - case "/teamsiso/refresh-discovery":_controller.RefreshDiscovery(); return; - case "/teamsiso/stop-all": await StopAllAsync(); return; - // /teamsiso/recording routes removed alongside the rest of the recording surface. - case "/teamsiso/notes": AppendNote(msg); return; - case "/teamsiso/iso": await ToggleByNameAsync(msg); return; - case "/teamsiso/iso/by-id": await ToggleByIdAsync(msg); return; - case "/teamsiso/preset": await ApplyPresetAsync(msg); return; + case "/Dragon-ISO/teams/mute": InvokeTeams(TeamsControlBridge.ToggleMute); return; + case "/Dragon-ISO/teams/camera": InvokeTeams(TeamsControlBridge.ToggleCamera); return; + case "/Dragon-ISO/teams/leave": InvokeTeams(TeamsControlBridge.LeaveCall); return; + case "/Dragon-ISO/teams/share": InvokeTeams(TeamsControlBridge.OpenShareTray); return; + case "/Dragon-ISO/teams/raise-hand":InvokeTeams(TeamsControlBridge.ToggleRaiseHand); return; + case "/Dragon-ISO/refresh-discovery":_controller.RefreshDiscovery(); return; + case "/Dragon-ISO/stop-all": await StopAllAsync(); return; + // /Dragon-ISO/recording routes removed alongside the rest of the recording surface. + case "/Dragon-ISO/notes": AppendNote(msg); return; + case "/Dragon-ISO/iso": await ToggleByNameAsync(msg); return; + case "/Dragon-ISO/iso/by-id": await ToggleByIdAsync(msg); return; + case "/Dragon-ISO/preset": await ApplyPresetAsync(msg); return; default: _logger?.LogDebug("Ignoring unknown OSC address {Addr}", addr); return; } } - // ─── handler helpers ──────────────────────────────────────────────── + // ─── handler helpers ──────────────────────────────────────────────── private static void InvokeTeams(Func action) => _ = action(); @@ -265,12 +265,12 @@ public sealed class OscBridge : IAsyncDisposable } } -// ─── OSC message parser ───────────────────────────────────────────────── +// ─── OSC message parser ───────────────────────────────────────────────── /// /// Minimal OSC 1.0 message parser. Supports the subset we care about: /// integer (i), float (f), string (s) args. Bundles / time tags / blobs are -/// not implemented — incoming packets that look like bundles return null +/// not implemented — incoming packets that look like bundles return null /// and the caller logs + skips them. /// internal sealed class OscMessage @@ -283,7 +283,7 @@ internal sealed class OscMessage public static OscMessage? TryParse(byte[] bytes) { if (bytes.Length < 8) return null; - // Bundle marker — we don't support bundles. Skip. + // Bundle marker — we don't support bundles. Skip. if (bytes[0] == '#') return null; var idx = 0; @@ -317,7 +317,7 @@ internal sealed class OscMessage case 'T': args.Add(true); break; case 'F': args.Add(false); break; default: - // Unknown type — bail rather than mis-aligning subsequent args. + // Unknown type — bail rather than mis-aligning subsequent args. return null; } } diff --git a/src/TeamsISO.App/Services/OutputNameTemplate.cs b/src/Dragon-ISO.App/Services/OutputNameTemplate.cs similarity index 81% rename from src/TeamsISO.App/Services/OutputNameTemplate.cs rename to src/Dragon-ISO.App/Services/OutputNameTemplate.cs index e2a9b1b..cb4163e 100644 --- a/src/TeamsISO.App/Services/OutputNameTemplate.cs +++ b/src/Dragon-ISO.App/Services/OutputNameTemplate.cs @@ -1,16 +1,16 @@ -using System.IO; +using System.IO; using System.Linq; using System.Text; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// User-editable template for the NDI source name a participant's ISO is /// published as. Default "{name}" renders the speaker's display name /// directly, which is what downstream switchers want when they key on /// readable identifiers. Operators can override globally to -/// "TEAMSISO_{guid}" for the legacy stable-id behavior, or -/// "TEAMSISO_{machine}_{name}" when multiple TeamsISO machines feed +/// "Dragon-ISO_{guid}" for the legacy stable-id behavior, or +/// "Dragon-ISO_{machine}_{name}" when multiple Dragon-ISO machines feed /// the same NDI network and you want the source name to carry both. /// Per-participant overrides take priority over whatever template is set. /// @@ -22,16 +22,16 @@ namespace TeamsISO.App.Services; /// /// Empty-name fallback: if the rendered result is empty/whitespace (e.g. /// template was "{name}" and the participant joined with no display -/// name yet), falls back to TEAMSISO_{guid} so +/// name yet), falls back to Dragon-ISO_{guid} so /// the NDI sender always has a usable, unique identifier. /// -/// Persisted to %LOCALAPPDATA%\TeamsISO\output-name-template.txt. +/// Persisted to %LOCALAPPDATA%\Dragon-ISO\output-name-template.txt. /// public static class OutputNameTemplate { /// - /// Default template — renders just the speaker's display name. Was - /// "TEAMSISO_{guid}" in pre-v1 builds; switched 2026-05-16 so + /// Default template — renders just the speaker's display name. Was + /// "Dragon-ISO_{guid}" in pre-v1 builds; switched 2026-05-16 so /// new installs get human-readable source names out of the box. /// public const string DefaultTemplate = "{name}"; @@ -42,12 +42,12 @@ public static class OutputNameTemplate /// Mirrors the engine's legacy DefaultOutputName so the NDI sender is /// always uniquely identifiable. /// - private const string EmptyNameFallback = "TEAMSISO_{guid}"; + private const string EmptyNameFallback = "Dragon-ISO_{guid}"; private static string TemplatePath => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "TeamsISO", "output-name-template.txt"); + "Dragon-ISO", "output-name-template.txt"); /// /// Get the operator's current template, or the shipped default when no @@ -65,7 +65,7 @@ public static class OutputNameTemplate } catch { - // Disk read failure → fall through to default. The next Set() call + // Disk read failure → fall through to default. The next Set() call // will overwrite cleanly. } return DefaultTemplate; @@ -105,7 +105,7 @@ public static class OutputNameTemplate .Replace("{machine}", machine) .Replace("{timestamp}", timestamp); - // Final sanitize on the rendered result — protects against a template + // Final sanitize on the rendered result — protects against a template // that includes literal characters NDI doesn't accept. var sanitized = SanitizeForNdi(result); @@ -114,13 +114,13 @@ public static class OutputNameTemplate // populated yet (Teams sometimes delivers the displayName a tick // after the participant join event). Two failure modes to catch: // - // • DisplayName == "" → "{name}" expands to "" → sanitized "". - // • DisplayName == " " → "{name}" expands to "___" because the + // • DisplayName == "" → "{name}" expands to "" → sanitized "". + // • DisplayName == " " → "{name}" expands to "___" because the // sanitizer converts whitespace to underscores. // // Neither is a meaningful NDI source identifier, so we substitute - // TEAMSISO_{guid}. The Any(char.IsLetterOrDigit) check covers both - // cases — anything without at least one alphanumeric is unusable. + // Dragon-ISO_{guid}. The Any(char.IsLetterOrDigit) check covers both + // cases — anything without at least one alphanumeric is unusable. // We apply this AFTER token expansion (not on the raw input) so a // template like "PFX_{name}" with empty displayName still works: // it renders to "PFX_" which contains alphanumerics and is left diff --git a/src/TeamsISO.App/Services/PresetApplier.cs b/src/Dragon-ISO.App/Services/PresetApplier.cs similarity index 94% rename from src/TeamsISO.App/Services/PresetApplier.cs rename to src/Dragon-ISO.App/Services/PresetApplier.cs index d68855c..cb83246 100644 --- a/src/TeamsISO.App/Services/PresetApplier.cs +++ b/src/Dragon-ISO.App/Services/PresetApplier.cs @@ -1,8 +1,8 @@ -using System.Windows.Threading; -using TeamsISO.App.ViewModels; -using TeamsISO.Engine.Controller; +using System.Windows.Threading; +using DragonISO.App.ViewModels; +using DragonISO.Engine.Controller; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Shared preset-application logic. Originally lived inline in @@ -36,7 +36,7 @@ public static class PresetApplier Dispatcher? dispatcher = null, CancellationToken cancellationToken = default) { - // Build the lookup once, case-insensitive — Teams display names are + // Build the lookup once, case-insensitive — Teams display names are // human-typed, so "Jane" and "jane" should match the same row. var byName = preset.Assignments.ToDictionary( a => a.DisplayName, diff --git a/src/TeamsISO.App/Services/TeamsControlBridge.cs b/src/Dragon-ISO.App/Services/TeamsControlBridge.cs similarity index 78% rename from src/TeamsISO.App/Services/TeamsControlBridge.cs rename to src/Dragon-ISO.App/Services/TeamsControlBridge.cs index fb085e1..e1fde42 100644 --- a/src/TeamsISO.App/Services/TeamsControlBridge.cs +++ b/src/Dragon-ISO.App/Services/TeamsControlBridge.cs @@ -1,10 +1,10 @@ -using System.Diagnostics; +using System.Diagnostics; using System.Windows.Automation; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// -/// Phase E.3 — UIAutomation bridge for the in-call controls (mute, camera, +/// Phase E.3 — UIAutomation bridge for the in-call controls (mute, camera, /// leave, share screen). Walks Teams' automation tree to locate the relevant /// buttons and invokes their or . /// @@ -21,20 +21,20 @@ namespace TeamsISO.App.Services; /// window; we enumerate every top-level window owned by every Teams process, /// so we'll find it wherever it lives. /// - Hidden windows (after ) are still -/// traversable by UIAutomation — the buttons exist in the automation tree +/// traversable by UIAutomation — the buttons exist in the automation tree /// even when their HWND is SW_HIDDEN. This is what makes the "hide Teams, -/// drive it from TeamsISO" workflow viable. +/// drive it from Dragon-ISO" workflow viable. /// public static class TeamsControlBridge { - // ──────────────────────────────────────────────────────────────────── + // ──────────────────────────────────────────────────────────────────── // Localized candidate-name lists. // // Teams localizes the AutomationElement.Name we match against. The lookup // strategy is: ALL candidate strings across all locales are tried for each // command, and the first match wins. This gives us a single binary that // works regardless of the Teams UI language without needing to detect it - // — at the cost of a slightly broader match surface (a non-mute button + // — at the cost of a slightly broader match surface (a non-mute button // with the German word "Stumm" in its name would false-positive). In // practice Teams' button Names are highly distinctive and we haven't seen // false positives during development. @@ -42,7 +42,7 @@ public static class TeamsControlBridge // Adding a locale: append the localized strings to each command's array. // Order doesn't matter for correctness; for performance we put the most // common locales first since the array is iterated in order. - // ──────────────────────────────────────────────────────────────────── + // ──────────────────────────────────────────────────────────────────── private static readonly string[] MuteCandidates = { @@ -51,13 +51,13 @@ public static class TeamsControlBridge // German "Stumm", "Stummschaltung", "Stummschalten", "Stummschaltung aufheben", "Mikrofon", // Spanish - "Silenciar", "Activar audio", "Micrófono", + "Silenciar", "Activar audio", "Micrófono", // French - "Désactiver le micro", "Activer le micro", "Micro", "Microphone", + "Désactiver le micro", "Activer le micro", "Micro", "Microphone", // Portuguese - "Desativar áudio", "Ativar áudio", "Microfone", + "Desativar áudio", "Ativar áudio", "Microfone", // Japanese - "ミュート", "ミュート解除", "マイク", + "ミュート", "ミュート解除", "マイク", }; private static readonly string[] CameraCandidates = @@ -66,13 +66,13 @@ public static class TeamsControlBridge // German "Kamera", "Kamera einschalten", "Kamera ausschalten", "Video", // Spanish - "Cámara", "Activar cámara", "Desactivar cámara", "Vídeo", + "Cámara", "Activar cámara", "Desactivar cámara", "Vídeo", // French - "Caméra", "Activer la caméra", "Désactiver la caméra", "Vidéo", + "Caméra", "Activer la caméra", "Désactiver la caméra", "Vidéo", // Portuguese - "Câmera", "Ativar câmera", "Desativar câmera", + "Câmera", "Ativar câmera", "Desativar câmera", // Japanese - "カメラ", "ビデオ", + "カメラ", "ビデオ", }; private static readonly string[] LeaveCandidates = @@ -87,7 +87,7 @@ public static class TeamsControlBridge // Portuguese "Sair", "Desligar", "Encerrar chamada", // Japanese - "退出", "通話を終了", + "退出", "通話を終了", }; private static readonly string[] ShareCandidates = @@ -98,11 +98,11 @@ public static class TeamsControlBridge // Spanish "Compartir", "Compartir contenido", "Compartir pantalla", // French - "Partager", "Partager du contenu", "Partager l'écran", + "Partager", "Partager du contenu", "Partager l'écran", // Portuguese - "Compartilhar", "Compartilhar conteúdo", "Compartilhar tela", + "Compartilhar", "Compartilhar conteúdo", "Compartilhar tela", // Japanese - "共有", "コンテンツの共有", "画面を共有", + "共有", "コンテンツの共有", "画面を共有", }; private static readonly string[] RaiseHandCandidates = @@ -115,9 +115,9 @@ public static class TeamsControlBridge // French "Lever la main", "Baisser la main", // Portuguese - "Levantar a mão", "Abaixar a mão", + "Levantar a mão", "Abaixar a mão", // Japanese - "手を挙げる", "手を下ろす", + "手を挙げる", "手を下ろす", }; private static readonly string[] ToggleChatCandidates = @@ -126,13 +126,13 @@ public static class TeamsControlBridge // German "Unterhaltung anzeigen", "Unterhaltung ausblenden", "Chat", // Spanish - "Mostrar conversación", "Ocultar conversación", "Chat", + "Mostrar conversación", "Ocultar conversación", "Chat", // French "Afficher la conversation", "Masquer la conversation", "Conversation", // Portuguese "Mostrar conversa", "Ocultar conversa", "Chat", // Japanese - "会話を表示", "会話を非表示", "チャット", + "会話を表示", "会話を非表示", "チャット", }; private static readonly string[] BackgroundBlurCandidates = @@ -143,11 +143,11 @@ public static class TeamsControlBridge // Spanish "Efectos de fondo", "Filtros de fondo", // French - "Effets d'arrière-plan", "Filtres d'arrière-plan", + "Effets d'arrière-plan", "Filtres d'arrière-plan", // Portuguese "Efeitos de plano de fundo", "Filtros de plano de fundo", // Japanese - "背景効果", "背景フィルター", + "背景効果", "背景フィルター", }; /// Result of attempting one of the in-call commands. @@ -186,7 +186,7 @@ public static class TeamsControlBridge /// /// Returns IsInCall=false if Teams isn't running or no Leave button /// exists. Returns IsMuted/IsCameraOff as null if those buttons aren't - /// found in this build (defensive — Teams sometimes uses different + /// found in this build (defensive — Teams sometimes uses different /// candidate names across locales). /// public static CallStateSnapshot DetectCallState() @@ -225,11 +225,11 @@ public static class TeamsControlBridge { if (lower.Contains("unmute") || lower.Contains("stummschaltung aufheben") || lower.Contains("activar audio") || lower.Contains("activer le micro") || - lower.Contains("ativar áudio") || lower.Contains("ミュート解除")) + lower.Contains("ativar áudio") || lower.Contains("ミュート解除")) muted = true; else if (lower.Contains("mute") || lower.Contains("stummschalten") || - lower.Contains("silenciar") || lower.Contains("désactiver le micro") || - lower.Contains("desativar áudio") || lower.Contains("ミュート")) + lower.Contains("silenciar") || lower.Contains("désactiver le micro") || + lower.Contains("desativar áudio") || lower.Contains("ミュート")) muted = false; } @@ -238,12 +238,12 @@ public static class TeamsControlBridge if (camOff is null) { if (lower.Contains("turn camera on") || lower.Contains("kamera einschalten") || - lower.Contains("activar cámara") || lower.Contains("activer la caméra") || - lower.Contains("ativar câmera")) + lower.Contains("activar cámara") || lower.Contains("activer la caméra") || + lower.Contains("ativar câmera")) camOff = true; else if (lower.Contains("turn camera off") || lower.Contains("kamera ausschalten") || - lower.Contains("desactivar cámara") || lower.Contains("désactiver la caméra") || - lower.Contains("desativar câmera")) + lower.Contains("desactivar cámara") || lower.Contains("désactiver la caméra") || + lower.Contains("desativar câmera")) camOff = false; } } @@ -259,8 +259,8 @@ public static class TeamsControlBridge /// UI is in a state we don't recognize. /// /// This is the "tell me what Teams is doing without me having to look - /// at it" probe — operators using auto-hide Teams want a status pill - /// that says "In call · ready" without having to restore the Teams + /// at it" probe — operators using auto-hide Teams want a status pill + /// that says "In call · ready" without having to restore the Teams /// window. Safe to call from any thread (UIA traversal is thread-safe); /// not free (walks the descendant tree) so callers should poll at most /// a few times per second. @@ -305,7 +305,7 @@ public static class TeamsControlBridge { // Search by Name first (most common case for Teams). Use a NameProperty // contains-style match by collecting all Buttons in the subtree and then - // filtering manually — Condition only supports equality, and Teams' + // filtering manually — Condition only supports equality, and Teams' // labels can include trailing state ("(unmuted)") that breaks equality. var allButtons = root.FindAll( TreeScope.Descendants, @@ -388,7 +388,7 @@ public static class TeamsControlBridge } catch { - // ElementNotEnabledException, ElementNotAvailableException — Teams + // ElementNotEnabledException, ElementNotAvailableException — Teams // disabled the button mid-traversal (e.g. mute is disabled before // joining a call). Treat as "found but couldn't invoke" so the // caller can surface a useful message. diff --git a/src/TeamsISO.App/Services/TeamsEmbedHost.cs b/src/Dragon-ISO.App/Services/TeamsEmbedHost.cs similarity index 90% rename from src/TeamsISO.App/Services/TeamsEmbedHost.cs rename to src/Dragon-ISO.App/Services/TeamsEmbedHost.cs index b2af0bd..5fde9cb 100644 --- a/src/TeamsISO.App/Services/TeamsEmbedHost.cs +++ b/src/Dragon-ISO.App/Services/TeamsEmbedHost.cs @@ -1,11 +1,11 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// -/// Phase E.4 — Embedded Teams via SetParent. +/// Phase E.4 — Embedded Teams via SetParent. /// -/// Reparents Teams' main top-level window into a TeamsISO-owned host +/// Reparents Teams' main top-level window into a Dragon-ISO-owned host /// (typically a Border element's HWND). Strips the captured window's /// caption + thick frame so it integrates flush with the host, and /// remembers enough about the original to restore it cleanly later. @@ -18,8 +18,8 @@ namespace TeamsISO.App.Services; /// caller wraps Embed in a finally block) so operators can fall back to /// auto-hide mode if embedding misbehaves on their specific Teams build. /// -/// Lives in its own static class — separated from -/// because the embedding lifecycle (reparent → resize → restore) is its +/// Lives in its own static class — separated from +/// because the embedding lifecycle (reparent → resize → restore) is its /// own thing, and the Win32 surface it requires (SetParent / window-style /// muck / SetWindowPos / MoveWindow) isn't reused by the launch / hide / /// in-call control paths. @@ -67,7 +67,7 @@ public static class TeamsEmbedHost private static (IntPtr OriginalParent, int OriginalStyle)? _embedSavedState; private static IntPtr _embeddedHwnd = IntPtr.Zero; - /// True when a Teams window is currently parented inside a TeamsISO host. + /// True when a Teams window is currently parented inside a Dragon-ISO host. public static bool IsEmbedded => _embeddedHwnd != IntPtr.Zero; /// @@ -79,7 +79,7 @@ public static class TeamsEmbedHost /// The host HWND is typically obtained via: /// var src = (System.Windows.Interop.HwndSource) /// PresentationSource.FromVisual(MyHostBorder); - /// src.Handle // → IntPtr suitable for hostHwnd + /// src.Handle // → IntPtr suitable for hostHwnd /// public static bool EmbedTeamsInto(IntPtr hostHwnd, int width, int height) { @@ -87,7 +87,7 @@ public static class TeamsEmbedHost var teamsWindows = TeamsLauncher.EnumerateTopLevelTeamsWindows(); if (teamsWindows.Count == 0) return false; - // Pick the longest-title window as the "main" one — same + // Pick the longest-title window as the "main" one — same // heuristic GetActiveWindowTitle uses; matches the call / // meeting window. IntPtr best = IntPtr.Zero; @@ -135,7 +135,7 @@ public static class TeamsEmbedHost /// /// Resize the currently-embedded Teams window to - /// × . Called when the host element resizes + /// × . Called when the host element resizes /// (window resize, layout change, etc.). No-op if nothing is embedded. /// public static void ResizeEmbedded(int width, int height) @@ -147,7 +147,7 @@ public static class TeamsEmbedHost /// /// Reverse an active embed: SetParent back to desktop + restore the /// original window style so Teams looks/behaves like a normal - /// top-level window again. Safe to call when nothing is embedded — + /// top-level window again. Safe to call when nothing is embedded — /// no-op. /// public static void RestoreEmbed() @@ -167,7 +167,7 @@ public static class TeamsEmbedHost SetWindowPos(_embeddedHwnd, IntPtr.Zero, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); } - catch { /* defensive — restore must never throw */ } + catch { /* defensive — restore must never throw */ } finally { _embedSavedState = null; diff --git a/src/TeamsISO.App/Services/TeamsLauncher.cs b/src/Dragon-ISO.App/Services/TeamsLauncher.cs similarity index 82% rename from src/TeamsISO.App/Services/TeamsLauncher.cs rename to src/Dragon-ISO.App/Services/TeamsLauncher.cs index b4ba4cd..d0fbaf3 100644 --- a/src/TeamsISO.App/Services/TeamsLauncher.cs +++ b/src/Dragon-ISO.App/Services/TeamsLauncher.cs @@ -1,14 +1,14 @@ -using System.Diagnostics; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Small Win32 wrapper that launches the Microsoft Teams desktop client as a -/// subprocess of TeamsISO. First step toward Phase E.1 of the embedded-Teams +/// subprocess of DragonISO. First step toward Phase E.1 of the embedded-Teams /// roadmap (see docs/superpowers/specs/2026-05-08-embedded-teams-orchestration.md): -/// the operator can launch Teams from within TeamsISO so they don't have to +/// the operator can launch Teams from within Dragon-ISO so they don't have to /// switch apps to start a meeting. /// /// The launcher tries (in order): @@ -17,7 +17,7 @@ namespace TeamsISO.App.Services; /// 3. Teams.exe in %LOCALAPPDATA%\Microsoft\Teams\Update.exe (legacy) /// /// Group-routing automation (writing NDI Access Manager config so Teams -/// broadcasts on a private group) is deferred to a follow-up — for v1.0 we +/// broadcasts on a private group) is deferred to a follow-up — for v1.0 we /// document the manual steps in RELEASING.md and trust the operator to set /// them once per machine. /// @@ -48,12 +48,12 @@ public static class TeamsLauncher /// reasons each attempt was rejected so the operator can see why. /// /// Path order matters: - /// 1. ms-teams: URI — new Teams (MSTeams AppX) registers this + /// 1. ms-teams: URI — new Teams (MSTeams AppX) registers this /// handler at install. Activates through the AppX shell so the /// stub ms-teams.exe in WindowsApps gets the right context. - /// 2. AppsFolder shell verb — direct AppX activation. Belt-and-braces + /// 2. AppsFolder shell verb — direct AppX activation. Belt-and-braces /// fallback if a misconfigured registry breaks the URI handler. - /// 3. Classic Teams Update.exe — pre-2024 Teams installations. + /// 3. Classic Teams Update.exe — pre-2024 Teams installations. /// We deliberately DON'T try the bare ms-teams.exe WindowsApps /// path: it's a 0-byte AppX placeholder that fails silently when invoked /// without AppX activation context. Looked plausible, never worked. @@ -65,9 +65,9 @@ public static class TeamsLauncher // Path 1: URI scheme. The shell handler picks the registered Teams // (new MSTeams takes priority on modern Windows). UseShellExecute=true - // is required — Win32 Process creation can't open URIs directly. + // is required — Win32 Process creation can't open URIs directly. if (TryStart("ms-teams:", useShell: true, out var err1)) return true; - attempts.Add($"ms-teams: URI → {err1}"); + attempts.Add($"ms-teams: URI → {err1}"); // Path 2: AppX activation via the explorer.exe shell. Modern Teams // ships as MSTeams_8wekyb3d8bbwe; if other code on the box has @@ -76,7 +76,7 @@ public static class TeamsLauncher if (TryStart("explorer.exe", false, out var err2, arguments: "shell:appsFolder\\MSTeams_8wekyb3d8bbwe!MSTeams")) return true; - attempts.Add($"AppsFolder shell → {err2}"); + attempts.Add($"AppsFolder shell → {err2}"); // Path 3: classic Teams Update.exe with --processStart hands off to // the actual Teams.exe via Squirrel. @@ -98,24 +98,24 @@ public static class TeamsLauncher } catch (Exception ex) { - attempts.Add($"classic Update.exe → {ex.Message}"); + attempts.Add($"classic Update.exe → {ex.Message}"); } } else { - attempts.Add($"classic Update.exe → not found at {classicUpdater}"); + attempts.Add($"classic Update.exe → not found at {classicUpdater}"); } errorMessage = "No Microsoft Teams installation could be launched. " + "Install Teams from https://www.microsoft.com/microsoft-teams and try again.\n\n" + - "Attempts:\n • " + string.Join("\n • ", attempts); + "Attempts:\n • " + string.Join("\n • ", attempts); return false; } /// /// Asks every running Teams process to close gracefully via WM_CLOSE /// (CloseMainWindow). Returns the count of processes that exited cleanly within - /// . Stragglers are NOT force-killed — Teams' own + /// . Stragglers are NOT force-killed — Teams' own /// "are you sure" prompt may legitimately keep a process alive briefly, and we /// don't want to nuke the user's call mid-transition. /// @@ -151,14 +151,14 @@ public static class TeamsLauncher /// Hand a meeting URL off to the Teams shell handler. Accepts both the /// https://teams.microsoft.com/l/meetup-join/... web format and /// the msteams:/l/meetup-join/... deep-link form (either causes - /// Teams to launch + join the meeting in one shot — the OS shell maps + /// Teams to launch + join the meeting in one shot — the OS shell maps /// teams.microsoft.com URLs to the registered ms-teams: handler). /// /// Use case: operator pastes a meeting link they got over email / chat - /// into TeamsISO's quick-join field instead of opening Teams, + /// into Dragon-ISO's quick-join field instead of opening Teams, /// hunting down the calendar entry, and clicking Join. With auto-hide /// on, the Teams window flashes briefly then disappears; the operator - /// is now in the meeting, driving routing from TeamsISO. + /// is now in the meeting, driving routing from DragonISO. /// /// Returns true if the shell accepted the URL; false if URL is malformed /// or rejected. errorMessage populated on failure. @@ -176,7 +176,7 @@ public static class TeamsLauncher // Defensive sanity-check: only accept URLs that obviously target // Teams. We don't want to invoke arbitrary shell handlers from a - // clipboard paste — if someone pastes "calc.exe" into the input we + // clipboard paste — if someone pastes "calc.exe" into the input we // shouldn't launch it. Specifically: http(s) URLs must contain // "teams.microsoft.com" or "teams.live.com"; otherwise must start // with "msteams:". @@ -221,11 +221,11 @@ public static class TeamsLauncher } } - // ════════════════════════════════════════════════════════════════════════ - // Phase E.2 — window orchestration + // ════════════════════════════════════════════════════════════════════════ + // Phase E.2 — window orchestration // // Once Teams is running, we want to be able to hide its main window so the - // operator only sees TeamsISO. We do this by enumerating top-level windows, + // operator only sees DragonISO. We do this by enumerating top-level windows, // matching against each Teams process Id, and ShowWindow(SW_HIDE)-ing each // match. To restore we ShowWindow(SW_SHOW) and SetForegroundWindow. // @@ -234,7 +234,7 @@ public static class TeamsLauncher // process and Process picks an inconsistent one across launches; iterating // via EnumWindows + GetWindowThreadProcessId catches every visible window // owned by the process. - // ════════════════════════════════════════════════════════════════════════ + // ════════════════════════════════════════════════════════════════════════ private const int SW_HIDE = 0; private const int SW_SHOWNORMAL = 1; @@ -276,9 +276,9 @@ public static class TeamsLauncher /// window, or empty string if Teams isn't running. Modern Teams puts /// the meeting title in the window title while in a call ("Meeting with /// Alice | Microsoft Teams"), so this is the cheapest way to surface - /// meeting context to TeamsISO's UI without burning a UIA traversal. + /// meeting context to Dragon-ISO's UI without burning a UIA traversal. /// - /// Includes hidden windows — operators using auto-hide still get the + /// Includes hidden windows — operators using auto-hide still get the /// title surfaced, which is the whole point. /// public static string GetActiveWindowTitle() @@ -363,12 +363,12 @@ public static class TeamsLauncher /// Fire-and-forget background watcher that polls every 250ms for up to /// and hides any visible top-level Teams /// windows it finds. Used after launch so the operator never sees the - /// Teams UI flash on screen — Teams takes 2-5s to splash + render its + /// Teams UI flash on screen — Teams takes 2-5s to splash + render its /// main window, and the splash arrives separately from the main window /// (so we keep polling past the first hide to catch follow-up windows). /// /// Returns the Task so callers can await completion if they want, but - /// production code should fire-and-forget. Exceptions are swallowed — + /// production code should fire-and-forget. Exceptions are swallowed — /// failure to hide is harmless (user just sees Teams briefly). /// public static Task AutoHideAfterLaunchAsync(TimeSpan? timeout = null, CancellationToken ct = default) @@ -382,7 +382,7 @@ public static class TeamsLauncher while (!ct.IsCancellationRequested && DateTime.UtcNow < deadline) { // Poll for visible windows. Each iteration may catch new - // ones — Teams sometimes opens a small splash, then a + // ones — Teams sometimes opens a small splash, then a // larger main window 1-2s later, then a "What's new" // banner. Keep hiding until we've gone a full second // with nothing new appearing. @@ -409,21 +409,21 @@ public static class TeamsLauncher } } catch (OperationCanceledException) { /* expected on cancel */ } - catch { /* defensive — auto-hide is best-effort, never breaks the app */ } + catch { /* defensive — auto-hide is best-effort, never breaks the app */ } }, ct); } - // ──────────────────────────────────────────────────────────────────── + // ──────────────────────────────────────────────────────────────────── // Keyboard-shortcut forwarding (PostMessage path). // // UIAutomation (TeamsControlBridge) is our preferred way to drive Teams // because it works regardless of foreground/visibility state. PostMessage // is a fallback for shortcuts that don't have a stable UIA-discoverable - // button — chat scroll, custom keymap actions, etc. Note: WebView2-hosted + // button — chat scroll, custom keymap actions, etc. Note: WebView2-hosted // Teams (the modern client) frequently ignores PostMessage(WM_KEYDOWN) at // its app-shortcut layer because shortcut routing happens after focus // changes, not on raw key messages. Treat this as best-effort. - // ──────────────────────────────────────────────────────────────────── + // ──────────────────────────────────────────────────────────────────── private const uint WM_KEYDOWN = 0x0100; private const uint WM_KEYUP = 0x0101; @@ -448,7 +448,7 @@ public static class TeamsLauncher /// Sends a synthesized key press (modifier-down, key-down, key-up, /// modifier-up) to the most recently used top-level Teams window via /// PostMessage. Returns true if a window was found to send to. Note that - /// returning true doesn't guarantee Teams reacted — modern WebView2-based + /// returning true doesn't guarantee Teams reacted — modern WebView2-based /// Teams sometimes ignores synthesized key messages at the app-shortcut /// layer. Prefer UIA () when an equivalent /// button exists. diff --git a/src/TeamsISO.App/Services/ThemeManager.cs b/src/Dragon-ISO.App/Services/ThemeManager.cs similarity index 86% rename from src/TeamsISO.App/Services/ThemeManager.cs rename to src/Dragon-ISO.App/Services/ThemeManager.cs index a7bc82e..cb5c205 100644 --- a/src/TeamsISO.App/Services/ThemeManager.cs +++ b/src/Dragon-ISO.App/Services/ThemeManager.cs @@ -1,17 +1,17 @@ -using System; +using System; using System.Linq; using System.Windows; using Microsoft.Win32; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Owns the active theme for the WPF host. Three preferences: /// -/// System — follows the Windows app-mode setting (default for new +/// System — follows the Windows app-mode setting (default for new /// users; re-reads on ). -/// Dark — pin dark regardless of OS. -/// Light — pin light regardless of OS. +/// Dark — pin dark regardless of OS. +/// Light — pin light regardless of OS. /// /// The two color files (Theme.Dark.xaml + Theme.Light.xaml) are /// kept in lockstep on the same set of brush keys; this manager swaps the @@ -20,7 +20,7 @@ namespace TeamsISO.App.Services; /// so the visual tree re-resolves without an app restart. /// /// Preference is persisted via 's Theme field, -/// reserved on disk during the v1 → v2 rollout so the rebuild doesn't lose the +/// reserved on disk during the v1 → v2 rollout so the rebuild doesn't lose the /// operator's choice. /// public sealed class ThemeManager @@ -31,12 +31,12 @@ public sealed class ThemeManager savePreference: TrySavePreferenceToDisk, subscribeToSystemPreference: true); - // Pack URIs (rather than relative "/Themes/…") so the resolution + // Pack URIs (rather than relative "/Themes/…") so the resolution // works equally well from production (where Application.Current's - // base URI is the TeamsISO entry assembly) and from xUnit tests - // (where it's the test assembly — relative URIs would miss). - private const string DarkUri = "pack://application:,,,/TeamsISO;component/Themes/Theme.Dark.xaml"; - private const string LightUri = "pack://application:,,,/TeamsISO;component/Themes/Theme.Light.xaml"; + // base URI is the Dragon-ISO entry assembly) and from xUnit tests + // (where it's the test assembly — relative URIs would miss). + private const string DarkUri = "pack://application:,,,/DragonISO;component/Themes/Theme.Dark.xaml"; + private const string LightUri = "pack://application:,,,/DragonISO;component/Themes/Theme.Light.xaml"; private const string PreferenceKeySystem = "System"; private const string PreferenceKeyDark = "Dark"; private const string PreferenceKeyLight = "Light"; @@ -69,7 +69,7 @@ public sealed class ThemeManager } catch { - // Defensive — ctor must not throw or the app loses theming. + // Defensive — ctor must not throw or the app loses theming. } // Re-evaluate when Windows app-mode flips, but only when the @@ -134,7 +134,7 @@ public sealed class ThemeManager /// /// Apply the current resolved theme. Should be called once during app /// startup (after Application.Current.Resources is initialized) and - /// whenever changes — already + /// whenever changes — already /// does the latter for you. /// public void Apply() @@ -152,7 +152,7 @@ public sealed class ThemeManager var dicts = app.Resources.MergedDictionaries; // Find the existing theme color dictionary by source URI. We - // distinguish "color" dictionaries from "WildDragonTheme" by name — + // distinguish "color" dictionaries from "WildDragonTheme" by name — // the color files are at Theme.Dark.xaml / Theme.Light.xaml; the // styles file is at WildDragonTheme.xaml. Replace in place to // preserve merge order so DynamicResource refs resolve to the new @@ -184,7 +184,7 @@ public sealed class ThemeManager /// /// Read Windows' AppsUseLightTheme registry value. 1 = light, 0 = dark. - /// Returns true (dark) on any read failure — the dark scene is the + /// Returns true (dark) on any read failure — the dark scene is the /// default per DESIGN.md so a missing value still lands somewhere /// sensible. Backs the singleton's _isSystemDark seam. /// @@ -208,7 +208,7 @@ public sealed class ThemeManager /// /// Load the operator's persisted theme preference from - /// %LOCALAPPDATA%\TeamsISO\ui-prefs.json. Returns null on any read + /// %LOCALAPPDATA%\Dragon-ISO\ui-prefs.json. Returns null on any read /// failure (missing file, corrupt JSON, schema mismatch) so the /// caller falls back to the in-memory default of "System". Backs /// the singleton's loadPreference seam. @@ -221,7 +221,7 @@ public sealed class ThemeManager /// /// Persist the operator's theme preference to ui-prefs.json. Errors - /// are swallowed — persistence is best-effort and a single failed + /// are swallowed — persistence is best-effort and a single failed /// save shouldn't break the in-session UI experience. Backs the /// singleton's savePreference seam. /// @@ -235,7 +235,7 @@ public sealed class ThemeManager { if (e.Category != UserPreferenceCategory.General) return; if (_preference != PreferenceKeySystem) return; - // Marshal to the UI thread — registry events fire on a system pool + // Marshal to the UI thread — registry events fire on a system pool // thread and resource dictionary mutations require dispatcher access. Application.Current?.Dispatcher.BeginInvoke(new Action(Apply)); } diff --git a/src/TeamsISO.App/Services/TrayIconHost.cs b/src/Dragon-ISO.App/Services/TrayIconHost.cs similarity index 88% rename from src/TeamsISO.App/Services/TrayIconHost.cs rename to src/Dragon-ISO.App/Services/TrayIconHost.cs index 2605ac1..28ab62a 100644 --- a/src/TeamsISO.App/Services/TrayIconHost.cs +++ b/src/Dragon-ISO.App/Services/TrayIconHost.cs @@ -1,16 +1,16 @@ -using System.Drawing; +using System.Drawing; using System.IO; using System.Reflection; using System.Runtime.Versioning; using System.Windows; using WinForms = System.Windows.Forms; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Wraps a WinForms so the WPF host can /// minimize-to-tray during long shows. Operators with a Stream Deck setup -/// often want TeamsISO running but invisible — the tray icon keeps the +/// often want Dragon-ISO running but invisible — the tray icon keeps the /// process alive (and the engine routing live) while the window stays /// hidden. /// @@ -31,7 +31,7 @@ public sealed class TrayIconHost : IDisposable _mainWindow = mainWindow; _notifyIcon = new WinForms.NotifyIcon { - Text = "TeamsISO", + Text = "Dragon-ISO", Icon = LoadEmbeddedIcon(), Visible = false, }; @@ -76,7 +76,7 @@ public sealed class TrayIconHost : IDisposable _notifyIcon.Visible = true; _notifyIcon.ShowBalloonTip( timeout: 1500, - tipTitle: "TeamsISO is still running", + tipTitle: "Dragon-ISO is still running", tipText: "Engine + control surface are live. Double-click the tray icon to restore the window.", tipIcon: WinForms.ToolTipIcon.Info); } @@ -93,7 +93,7 @@ public sealed class TrayIconHost : IDisposable private WinForms.ContextMenuStrip BuildMenu() { var menu = new WinForms.ContextMenuStrip(); - menu.Items.Add("Show TeamsISO", null, (_, _) => RestoreFromTray()); + menu.Items.Add("Show Dragon-ISO", null, (_, _) => RestoreFromTray()); menu.Items.Add("-"); menu.Items.Add("Stop all ISOs", null, (_, _) => { @@ -106,12 +106,12 @@ public sealed class TrayIconHost : IDisposable } }); menu.Items.Add("-"); - menu.Items.Add("Exit TeamsISO", null, (_, _) => System.Windows.Application.Current.Shutdown()); + menu.Items.Add("Exit Dragon-ISO", null, (_, _) => System.Windows.Application.Current.Shutdown()); return menu; } /// - /// Load the bundled teamsiso.ico from this assembly's resources. We use + /// Load the bundled DragonISO.ico from this assembly's resources. We use /// the embedded resource rather than the file-system path because the /// app may be run from any CWD (via the MSI install or a developer dotnet run). /// @@ -120,7 +120,7 @@ public sealed class TrayIconHost : IDisposable try { var asm = Assembly.GetExecutingAssembly(); - var uri = new Uri("pack://application:,,,/Assets/teamsiso.ico"); + var uri = new Uri("pack://application:,,,/Assets/DragonISO.ico"); using var stream = System.Windows.Application.GetResourceStream(uri)?.Stream; if (stream is not null) return new Icon(stream); } diff --git a/src/TeamsISO.App/Services/UIPreferences.cs b/src/Dragon-ISO.App/Services/UIPreferences.cs similarity index 82% rename from src/TeamsISO.App/Services/UIPreferences.cs rename to src/Dragon-ISO.App/Services/UIPreferences.cs index 1571903..bc26f24 100644 --- a/src/TeamsISO.App/Services/UIPreferences.cs +++ b/src/Dragon-ISO.App/Services/UIPreferences.cs @@ -1,17 +1,17 @@ -using System.IO; +using System.IO; using System.Text.Json; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// -/// Persistent UI-side toggles that don't belong in -/// (which is the engine's domain model — framerate, NDI groups, ISO assignments). +/// Persistent UI-side toggles that don't belong in +/// (which is the engine's domain model — framerate, NDI groups, ISO assignments). /// /// Each toggle is a property on a single record persisted as JSON at -/// %LOCALAPPDATA%\TeamsISO\ui-prefs.json. Defaults match the original +/// %LOCALAPPDATA%\Dragon-ISO\ui-prefs.json. Defaults match the original /// in-memory behavior: HideLocalSelf=true (filter the operator's own preview /// out of the participants list) and AutoDisableOnDeparture=false (a participant -/// going offline doesn't tear down their pipeline by default — operators +/// going offline doesn't tear down their pipeline by default — operators /// usually want to keep the routing in case they reconnect). /// /// Centralizing these here means the settings VM doesn't have to plumb @@ -24,14 +24,14 @@ public static class UIPreferences private static string PrefsPath => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "TeamsISO", "ui-prefs.json"); + "Dragon-ISO", "ui-prefs.json"); /// /// Sort modes for the participants DataGrid. is the default /// and matches the engine's discovery order (operators with custom Stream Deck /// layouts sometimes prefer Alphabetical for stability across meetings). /// resorts at the 1Hz stats tick so the active - /// speaker bubbles to the top — useful for operators reacting to who's talking. + /// speaker bubbles to the top — useful for operators reacting to who's talking. /// public enum SortMode { JoinOrder, Alphabetical, OnlineFirst, LoudestFirst } @@ -43,20 +43,20 @@ public static class UIPreferences bool MinimizeToTray = false, bool ControlSurfaceLanReachable = false, // Phase E.1 / E.2 quality-of-life. With both true, the operator launches - // TeamsISO and never sees the Teams UI — Teams auto-starts in the + // Dragon-ISO and never sees the Teams UI — Teams auto-starts in the // background and its windows are auto-hidden as soon as they materialize. // All control happens via the IN-CALL bar + participants DataGrid. bool LaunchTeamsOnStartup = false, bool AutoHideTeamsWindows = false, // Experimental Phase E.4. SetParent-reparents Teams' main window - // into a TeamsISO-owned host. WebView2 in modern Teams can render + // into a Dragon-ISO-owned host. WebView2 in modern Teams can render // weirdly after reparent; if so the operator unticks and falls // back to auto-hide mode. Off by default. bool EmbedTeamsWindow = false, // Theme preference for the v2 redesign. One of "System" (follow // Windows app-mode), "Dark", or "Light". ThemeManager hydrates // from this on startup and persists back here on toggle. Default - // "System" matches DESIGN.md's "Follow Windows" choice — the + // "System" matches DESIGN.md's "Follow Windows" choice — the // operator who doesn't care gets whatever Windows is set to. string Theme = "System", // REST + WebSocket control surface auto-start. When true, the @@ -100,7 +100,7 @@ public static class UIPreferences } catch { - // Disk full / permission denied — in-memory state still holds for this session. + // Disk full / permission denied — in-memory state still holds for this session. } } } diff --git a/src/TeamsISO.App/Services/UpdateChecker.cs b/src/Dragon-ISO.App/Services/UpdateChecker.cs similarity index 90% rename from src/TeamsISO.App/Services/UpdateChecker.cs rename to src/Dragon-ISO.App/Services/UpdateChecker.cs index 62b3661..01109b6 100644 --- a/src/TeamsISO.App/Services/UpdateChecker.cs +++ b/src/Dragon-ISO.App/Services/UpdateChecker.cs @@ -1,21 +1,21 @@ -using System.Diagnostics; +using System.Diagnostics; using System.IO; using System.Net.Http; using System.Reflection; using System.Text.Json; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Asks Forgejo's REST API whether a newer release tag exists than the one -/// we're running. Manual-only for v1 — there's no background polling. The +/// we're running. Manual-only for v1 — there's no background polling. The /// operator can click "Check for updates" in the About dialog whenever they /// want, and a positive result opens the release page in their browser /// (rather than auto-downloading; we don't want a long-running show /// interrupted by a surprise installer). /// /// We use the public release endpoint so no auth is needed: -/// GET /api/v1/repos/zgaetano/teamsiso/releases?limit=1 +/// GET /api/v1/repos/zgaetano/Dragon-ISO/releases?limit=1 /// /// On any error (offline, DNS failure, repo private, malformed response), /// the caller gets with a short @@ -24,10 +24,10 @@ namespace TeamsISO.App.Services; public static class UpdateChecker { private const string ReleasesApi = - "https://forge.wilddragon.net/api/v1/repos/zgaetano/teamsiso/releases?limit=1"; + "https://forge.wilddragon.net/api/v1/repos/zgaetano/Dragon-ISO/releases?limit=1"; private const string ReleasesPage = - "https://forge.wilddragon.net/zgaetano/teamsiso/releases"; + "https://forge.wilddragon.net/zgaetano/Dragon-ISO/releases"; /// Outcome of a single check. public sealed record UpdateCheckResult( @@ -53,7 +53,7 @@ public static class UpdateChecker try { using var client = new HttpClient { Timeout = TimeSpan.FromSeconds(8) }; - client.DefaultRequestHeaders.Add("User-Agent", "TeamsISO/" + current); + client.DefaultRequestHeaders.Add("User-Agent", "Dragon-ISO/" + current); using var res = await client.GetAsync(ReleasesApi, ct); if (!res.IsSuccessStatusCode) @@ -102,7 +102,7 @@ public static class UpdateChecker /// /// Open the releases page in the user's default browser. Used by the - /// "Update available" dialog button — we deliberately don't download/run + /// "Update available" dialog button — we deliberately don't download/run /// the MSI ourselves, so the operator decides when to install. /// public static void OpenReleasesPage() @@ -124,7 +124,7 @@ public static class UpdateChecker /// /// Silent throttled launch-time check. Returns the result if a check actually /// happened, or null if the cooldown window suppressed it. The cooldown lives - /// in %LOCALAPPDATA%\TeamsISO\last-update-check.txt as an ISO 8601 + /// in %LOCALAPPDATA%\Dragon-ISO\last-update-check.txt as an ISO 8601 /// timestamp; a missing file means "never checked, do it now." /// public static async Task CheckIfDueAsync(TimeSpan cooldown, CancellationToken ct = default) @@ -165,8 +165,8 @@ public static class UpdateChecker } /// - /// Test-only seam — when set, overrides the default - /// %LOCALAPPDATA%\TeamsISO path that holds the cooldown stamp + + /// Test-only seam — when set, overrides the default + /// %LOCALAPPDATA%\Dragon-ISO path that holds the cooldown stamp + /// the opt-out flag. Tests use this to write to a tempdir so /// CheckIfDueAsync's throttle path can be exercised without /// hitting real disk paths or the real network (the throttle @@ -177,7 +177,7 @@ public static class UpdateChecker private static string StateDirectory => StateDirectoryOverride ?? Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "TeamsISO"); + "Dragon-ISO"); private static string CooldownPath => Path.Combine(StateDirectory, "last-update-check.txt"); @@ -229,7 +229,7 @@ public static class UpdateChecker /// /// Parse a "vX.Y.Z" or "X.Y.Z(.N)" string into a Version. Strips any /// pre-release suffix ("-alpha", "-beta") so the comparison is on - /// numeric components only — pre-release vs. release ordering is a + /// numeric components only — pre-release vs. release ordering is a /// follow-up if we need it. Internal so tests can pin parsing /// behaviour without HTTP. /// diff --git a/src/TeamsISO.App/Services/WindowStateStore.cs b/src/Dragon-ISO.App/Services/WindowStateStore.cs similarity index 91% rename from src/TeamsISO.App/Services/WindowStateStore.cs rename to src/Dragon-ISO.App/Services/WindowStateStore.cs index bacdaa5..ebbaa2b 100644 --- a/src/TeamsISO.App/Services/WindowStateStore.cs +++ b/src/Dragon-ISO.App/Services/WindowStateStore.cs @@ -1,12 +1,12 @@ -using System.IO; +using System.IO; using System.Text.Json; using System.Windows; -namespace TeamsISO.App.Services; +namespace DragonISO.App.Services; /// /// Saves / restores the main window's size, position, and state across launches. -/// Stored as JSON at %LOCALAPPDATA%\TeamsISO\window.json. Multi-monitor +/// Stored as JSON at %LOCALAPPDATA%\Dragon-ISO\window.json. Multi-monitor /// friendly: a saved position that no longer falls inside any working area is /// rejected on restore so the window doesn't disappear off-screen when a monitor /// has been disconnected. @@ -14,8 +14,8 @@ namespace TeamsISO.App.Services; public static class WindowStateStore { /// - /// Test-only seam — when set, overrides the default - /// %LOCALAPPDATA%\TeamsISO\window.json path. Lets tests verify + /// Test-only seam — when set, overrides the default + /// %LOCALAPPDATA%\Dragon-ISO\window.json path. Lets tests verify /// the serialization round-trip without polluting the dev's /// real placement state. /// @@ -24,7 +24,7 @@ public static class WindowStateStore private static string Path => PathOverride ?? System.IO.Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "TeamsISO", + "Dragon-ISO", "window.json"); public sealed record Snapshot( @@ -70,7 +70,7 @@ public static class WindowStateStore var snap = JsonSerializer.Deserialize(json); if (snap is null) return false; - // Sanity-check sizes (don't restore a 0×0 or absurdly large window). + // Sanity-check sizes (don't restore a 0×0 or absurdly large window). if (snap.Width < 320 || snap.Height < 240) return false; if (snap.Width > 16000 || snap.Height > 12000) return false; diff --git a/src/TeamsISO.App/StartupTrace.cs b/src/Dragon-ISO.App/StartupTrace.cs similarity index 83% rename from src/TeamsISO.App/StartupTrace.cs rename to src/Dragon-ISO.App/StartupTrace.cs index a54325d..ca8b8c5 100644 --- a/src/TeamsISO.App/StartupTrace.cs +++ b/src/Dragon-ISO.App/StartupTrace.cs @@ -1,15 +1,15 @@ -using System.IO; +using System.IO; -namespace TeamsISO.App; +namespace DragonISO.App; /// /// Bare-metal startup tracer that opens, appends, and closes a file on /// every call. Used to capture what's happening BEFORE Serilog comes up /// (and to capture failures that would prevent Serilog from coming up at -/// all). Failures here are swallowed — we never want diagnostics to crash +/// all). Failures here are swallowed — we never want diagnostics to crash /// the very thing we're trying to diagnose. /// -/// File lives at %LOCALAPPDATA%\TeamsISO\startup-trace.log. Grows +/// File lives at %LOCALAPPDATA%\Dragon-ISO\startup-trace.log. Grows /// without rotation; expected to be tiny since each launch writes ~20 /// lines. Acceptable cost for catching launch-time regressions. /// @@ -23,7 +23,7 @@ internal static class StartupTrace { var dir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "TeamsISO"); + "Dragon-ISO"); Directory.CreateDirectory(dir); var path = Path.Combine(dir, "startup-trace.log"); var line = $"[{DateTimeOffset.Now:yyyy-MM-dd HH:mm:ss.fff}] [PID {Environment.ProcessId}] {message}{Environment.NewLine}"; diff --git a/src/TeamsISO.App/TeamsEmbedWindow.xaml b/src/Dragon-ISO.App/TeamsEmbedWindow.xaml similarity index 97% rename from src/TeamsISO.App/TeamsEmbedWindow.xaml rename to src/Dragon-ISO.App/TeamsEmbedWindow.xaml index ca7c9e9..f648ebd 100644 --- a/src/TeamsISO.App/TeamsEmbedWindow.xaml +++ b/src/Dragon-ISO.App/TeamsEmbedWindow.xaml @@ -1,9 +1,9 @@ - -/// Phase E.4 experimental — hosts an embedded copy of the Teams main -/// window via SetParent. Operator opens this from Settings → DISPLAY → +/// Phase E.4 experimental — hosts an embedded copy of the Teams main +/// window via SetParent. Operator opens this from Settings → DISPLAY → /// 'Embed Teams window'. The host Border's HWND becomes Teams' parent on /// Loaded; SizeChanged keeps Teams fitted; Closing always restores Teams /// to a normal top-level window before we exit. /// /// Failsafes: -/// • If no Teams window is found at Loaded, show a friendly message +/// • If no Teams window is found at Loaded, show a friendly message /// instead of leaving the host blank. -/// • Restore-on-close runs in a finally block so a crash mid-host +/// • Restore-on-close runs in a finally block so a crash mid-host /// can't leave Teams orphaned with stripped window styles. -/// • TeamsEmbedHost.RestoreEmbed is idempotent — safe to call even if +/// • TeamsEmbedHost.RestoreEmbed is idempotent — safe to call even if /// embedding never succeeded. /// public partial class TeamsEmbedWindow : Window @@ -36,7 +36,7 @@ public partial class TeamsEmbedWindow : Window MessageBox.Show( "Couldn't obtain a host HWND for the embed window. " + "Try closing and re-opening the embed window.", - "TeamsISO — embed", + "Dragon-ISO — embed", MessageBoxButton.OK, MessageBoxImage.Warning); return; } @@ -48,7 +48,7 @@ public partial class TeamsEmbedWindow : Window MessageBox.Show( "Couldn't find a Microsoft Teams window to embed. " + "Launch Teams first (rail camera icon), then re-open this window.", - "TeamsISO — embed", + "Dragon-ISO — embed", MessageBoxButton.OK, MessageBoxImage.Information); } } @@ -65,7 +65,7 @@ public partial class TeamsEmbedWindow : Window // ALWAYS restore Teams to top-level state when this window closes, // even if the embed never succeeded. Idempotent. try { TeamsEmbedHost.RestoreEmbed(); } - catch { /* defensive — restore is best-effort */ } + catch { /* defensive — restore is best-effort */ } } private void OnClose(object sender, RoutedEventArgs e) => Close(); diff --git a/src/TeamsISO.App/Themes/Theme.Dark.xaml b/src/Dragon-ISO.App/Themes/Theme.Dark.xaml similarity index 96% rename from src/TeamsISO.App/Themes/Theme.Dark.xaml rename to src/Dragon-ISO.App/Themes/Theme.Dark.xaml index 2a748b2..a096541 100644 --- a/src/TeamsISO.App/Themes/Theme.Dark.xaml +++ b/src/Dragon-ISO.App/Themes/Theme.Dark.xaml @@ -58,6 +58,6 @@ of the resource dictionary. --> diff --git a/src/TeamsISO.App/Themes/Theme.Light.xaml b/src/Dragon-ISO.App/Themes/Theme.Light.xaml similarity index 96% rename from src/TeamsISO.App/Themes/Theme.Light.xaml rename to src/Dragon-ISO.App/Themes/Theme.Light.xaml index 90c774b..e9f3c70 100644 --- a/src/TeamsISO.App/Themes/Theme.Light.xaml +++ b/src/Dragon-ISO.App/Themes/Theme.Light.xaml @@ -55,6 +55,6 @@ See Theme.Dark.xaml's comment for the CacheOption=OnLoad rationale. --> diff --git a/src/TeamsISO.App/Themes/WildDragonTheme.xaml b/src/Dragon-ISO.App/Themes/WildDragonTheme.xaml similarity index 99% rename from src/TeamsISO.App/Themes/WildDragonTheme.xaml rename to src/Dragon-ISO.App/Themes/WildDragonTheme.xaml index ecebae7..2c47db3 100644 --- a/src/TeamsISO.App/Themes/WildDragonTheme.xaml +++ b/src/Dragon-ISO.App/Themes/WildDragonTheme.xaml @@ -4,7 +4,7 @@ xmlns:sys="clr-namespace:System;assembly=mscorlib"> - - - <_Parameter1>TeamsISO.Engine.Tests - - - - - net8.0 - enable - enable - - - + + + + + + + + + + <_Parameter1>Dragon-ISO.Engine.Tests + + + + + net8.0 + enable + enable + + + diff --git a/src/TeamsISO.Engine.NdiInterop/NdiInteropPInvoke.cs b/src/Dragon-ISO.Engine.NdiInterop/NdiInteropPInvoke.cs similarity index 94% rename from src/TeamsISO.Engine.NdiInterop/NdiInteropPInvoke.cs rename to src/Dragon-ISO.Engine.NdiInterop/NdiInteropPInvoke.cs index 9eb4298..811136b 100644 --- a/src/TeamsISO.Engine.NdiInterop/NdiInteropPInvoke.cs +++ b/src/Dragon-ISO.Engine.NdiInterop/NdiInteropPInvoke.cs @@ -1,10 +1,10 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; using System.Runtime.Versioning; using Microsoft.Extensions.Logging; -using TeamsISO.Engine.Interop; -using TeamsISO.Engine.Pipeline; +using DragonISO.Engine.Interop; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.NdiInterop; +namespace DragonISO.Engine.NdiInterop; /// /// Production implementation backed by the NDI 6 SDK. @@ -42,10 +42,10 @@ public sealed class NdiInteropPInvoke : INdiInterop, IDisposable /// **NDI group names are case-sensitive in the runtime.** "Public" matches; "public" /// does NOT. The default group an unconfigured NDI Sender broadcasts to is "Public" /// (capital P). Operators who type "public" into the discovery groups field then see - /// zero sources and report the app as broken — that's how this normalizer came to - /// exist (2026-05-16 dev session, ~6h of misdiagnosis). We special-case "public" → + /// zero sources and report the app as broken — that's how this normalizer came to + /// exist (2026-05-16 dev session, ~6h of misdiagnosis). We special-case "public" → /// "Public" to match the most common operator footgun. Other group names are - /// passed through verbatim — custom groups like "teamsiso-input" are + /// passed through verbatim — custom groups like "Dragon-ISO-input" are /// intentionally lowercase and must round-trip unchanged. /// /// Marked internal so the test project can cover the lookup table directly. @@ -74,7 +74,7 @@ public sealed class NdiInteropPInvoke : INdiInterop, IDisposable // // Memory ownership: the NDI SDK's NDIlib_find_create_v2 (and _send_create, // _recv_create_v3) copy the strings out of the settings struct synchronously - // before returning — they don't retain pointers into our buffers. This is the + // before returning — they don't retain pointers into our buffers. This is the // same lifetime contract CreateReceiver / CreateSender below have relied on // since Phase B-2; if it ever turns out to be wrong, those will fail too. The // loopback discovery integration test would catch a regression here. @@ -142,7 +142,7 @@ public sealed class NdiInteropPInvoke : INdiInterop, IDisposable public NdiReceiverHandle CreateReceiver(string sourceFullName) { var nameUtf8 = Marshal.StringToHGlobalAnsi(sourceFullName); - var recvNameUtf8 = Marshal.StringToHGlobalAnsi("TeamsISO"); + var recvNameUtf8 = Marshal.StringToHGlobalAnsi("Dragon-ISO"); try { var settings = new NdiNative.RecvCreateV3Settings @@ -173,7 +173,7 @@ public sealed class NdiInteropPInvoke : INdiInterop, IDisposable /// Pulls one audio frame and returns its peak amplitude in [0,1], or null /// if the timeout elapsed without an audio frame arriving. Uses the same /// underlying NDIlib_recv_capture_v3 the video path does, but binds the - /// audio output slot only — the receiver's internal queue serves video + /// audio output slot only — the receiver's internal queue serves video /// and audio independently, so this can be polled from a separate thread /// without contending with the video capture loop. /// @@ -190,7 +190,7 @@ public sealed class NdiInteropPInvoke : INdiInterop, IDisposable if (frameType != NdiNative.FrameType.Audio || nativeAudio.p_data == IntPtr.Zero) { // Free defensively on the off-chance an audio struct was partially - // populated despite the wrong frame-type return — the SDK's free + // populated despite the wrong frame-type return — the SDK's free // is a no-op on a zero pointer. if (nativeAudio.p_data != IntPtr.Zero) NdiNative.RecvFreeAudioV3(pInvokeReceiver.Native, ref nativeAudio); @@ -212,7 +212,7 @@ public sealed class NdiInteropPInvoke : INdiInterop, IDisposable Marshal.Copy(nativeAudio.p_data, managed, 0, totalBytes); var totalSamples = nativeAudio.no_channels * nativeAudio.no_samples; - return TeamsISO.Engine.Pipeline.AudioPeakComputer.ComputePeak( + return DragonISO.Engine.Pipeline.AudioPeakComputer.ComputePeak( managed, nativeAudio.FourCC, totalSamples); } finally diff --git a/src/TeamsISO.Engine.NdiInterop/NdiNative.cs b/src/Dragon-ISO.Engine.NdiInterop/NdiNative.cs similarity index 97% rename from src/TeamsISO.Engine.NdiInterop/NdiNative.cs rename to src/Dragon-ISO.Engine.NdiInterop/NdiNative.cs index 609d4a0..4780389 100644 --- a/src/TeamsISO.Engine.NdiInterop/NdiNative.cs +++ b/src/Dragon-ISO.Engine.NdiInterop/NdiNative.cs @@ -1,13 +1,13 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; -namespace TeamsISO.Engine.NdiInterop; +namespace DragonISO.Engine.NdiInterop; /// /// P/Invoke declarations for the NewTek/Vizrt NDI SDK 6 native library. /// On Windows the import target is Processing.NDI.Lib.x64.dll. Resolution of /// the DLL is handled by , which loads it from /// the NDI Runtime installation directory exposed by the -/// NDI_RUNTIME_DIR_V<n> environment variables — the NDI installer sets +/// NDI_RUNTIME_DIR_V<n> environment variables — the NDI installer sets /// these but does not always add the runtime directory to PATH, so a default /// loader-based resolution would fail with 0x8007007E on otherwise correctly /// installed machines. @@ -205,7 +205,7 @@ internal static class NdiNative /// distinguishes the sample format; for NDI 6 the only common value is /// FLTP (32-bit float, planar, one channel-plane after another). /// channel_stride_in_bytes is the byte distance between the start - /// of channel N and channel N+1 — for FLTP that's no_samples * 4. + /// of channel N and channel N+1 — for FLTP that's no_samples * 4. /// Total buffer size = no_channels * channel_stride_in_bytes. /// [StructLayout(LayoutKind.Sequential)] diff --git a/src/TeamsISO.Engine.NdiInterop/NdiNativeLibraryResolver.cs b/src/Dragon-ISO.Engine.NdiInterop/NdiNativeLibraryResolver.cs similarity index 90% rename from src/TeamsISO.Engine.NdiInterop/NdiNativeLibraryResolver.cs rename to src/Dragon-ISO.Engine.NdiInterop/NdiNativeLibraryResolver.cs index 26ada5c..b7a51e5 100644 --- a/src/TeamsISO.Engine.NdiInterop/NdiNativeLibraryResolver.cs +++ b/src/Dragon-ISO.Engine.NdiInterop/NdiNativeLibraryResolver.cs @@ -1,7 +1,7 @@ -using System.Reflection; +using System.Reflection; using System.Runtime.InteropServices; -namespace TeamsISO.Engine.NdiInterop; +namespace DragonISO.Engine.NdiInterop; /// /// Resolves the NDI native library (Processing.NDI.Lib.x64.dll) from the @@ -27,8 +27,8 @@ internal static class NdiNativeLibraryResolver /// /// NDI runtime install-dir environment variables, in preference order. - /// V6 is what TeamsISO is built against; V5/V4 are listed as graceful fallbacks - /// for installs that pre-date V6 — the runtime probe will still report a + /// V6 is what Dragon-ISO is built against; V5/V4 are listed as graceful fallbacks + /// for installs that pre-date V6 — the runtime probe will still report a /// version mismatch, but at least the DLL will load and the engine can surface /// a clear alert instead of dying with DllNotFoundException. /// @@ -59,7 +59,7 @@ internal static class NdiNativeLibraryResolver return handle; } - // Fall through to default loader (PATH, app dir, etc.) — preserves the + // Fall through to default loader (PATH, app dir, etc.) — preserves the // chance that someone added the NDI dir to PATH manually. return IntPtr.Zero; } diff --git a/src/TeamsISO.Engine.NdiInterop/NdiPInvokeHandles.cs b/src/Dragon-ISO.Engine.NdiInterop/NdiPInvokeHandles.cs similarity index 94% rename from src/TeamsISO.Engine.NdiInterop/NdiPInvokeHandles.cs rename to src/Dragon-ISO.Engine.NdiInterop/NdiPInvokeHandles.cs index ae67830..dfe6ad0 100644 --- a/src/TeamsISO.Engine.NdiInterop/NdiPInvokeHandles.cs +++ b/src/Dragon-ISO.Engine.NdiInterop/NdiPInvokeHandles.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Interop; +using DragonISO.Engine.Interop; -namespace TeamsISO.Engine.NdiInterop; +namespace DragonISO.Engine.NdiInterop; internal sealed class NdiPInvokeFindHandle : NdiFindHandle { diff --git a/src/TeamsISO.Engine.NdiInterop/NdiVersion.cs b/src/Dragon-ISO.Engine.NdiInterop/NdiVersion.cs similarity index 95% rename from src/TeamsISO.Engine.NdiInterop/NdiVersion.cs rename to src/Dragon-ISO.Engine.NdiInterop/NdiVersion.cs index b2d1d2b..a62b981 100644 --- a/src/TeamsISO.Engine.NdiInterop/NdiVersion.cs +++ b/src/Dragon-ISO.Engine.NdiInterop/NdiVersion.cs @@ -1,10 +1,10 @@ -namespace TeamsISO.Engine.NdiInterop; +namespace DragonISO.Engine.NdiInterop; /// /// Constants describing the NDI SDK version this build was compiled against. /// The runtime version reported by is compared against /// by the engine's runtime probe to detect -/// installations that pre-date or post-date the SDK headers (per spec §6). +/// installations that pre-date or post-date the SDK headers (per spec §6). /// public static class NdiVersion { diff --git a/src/TeamsISO.Engine.NdiInterop/Placeholder.cs b/src/Dragon-ISO.Engine.NdiInterop/Placeholder.cs similarity index 82% rename from src/TeamsISO.Engine.NdiInterop/Placeholder.cs rename to src/Dragon-ISO.Engine.NdiInterop/Placeholder.cs index 08eb145..c5b11dd 100644 --- a/src/TeamsISO.Engine.NdiInterop/Placeholder.cs +++ b/src/Dragon-ISO.Engine.NdiInterop/Placeholder.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.NdiInterop; +namespace DragonISO.Engine.NdiInterop; /// /// Phase A placeholder. The production P/Invoke implementations of INdiInterop diff --git a/src/TeamsISO.Engine/Controller/IIsoController.cs b/src/Dragon-ISO.Engine/Controller/IIsoController.cs similarity index 93% rename from src/TeamsISO.Engine/Controller/IIsoController.cs rename to src/Dragon-ISO.Engine/Controller/IIsoController.cs index 0ba1eb1..652310c 100644 --- a/src/TeamsISO.Engine/Controller/IIsoController.cs +++ b/src/Dragon-ISO.Engine/Controller/IIsoController.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Controller; +namespace DragonISO.Engine.Controller; /// /// Top-of-engine API the WPF host (Phase C) and any future control APIs (OSC / WebSocket in v2.0) @@ -55,7 +55,7 @@ public interface IIsoController : IAsyncDisposable /// /// The per-participant override for this pipeline's /// , or null when the participant - /// is following global settings. Reads are unsynchronized snapshots — + /// is following global settings. Reads are unsynchronized snapshots — /// safe for UI consumption but don't rely on equality with the value /// that a concurrent just wrote. /// @@ -66,7 +66,7 @@ public interface IIsoController : IAsyncDisposable /// = null removes the override (pipeline /// reverts to global settings). If a pipeline for the participant is /// currently running, it's stopped and restarted with the new - /// settings — the operator sees a brief signal drop during the swap. + /// settings — the operator sees a brief signal drop during the swap. /// Persists alongside the assignment to config.json so the override /// survives process restarts. /// @@ -74,7 +74,7 @@ public interface IIsoController : IAsyncDisposable /// /// Updates the NDI group configuration and persists it. Group changes apply on next process - /// restart — rebuilding finder/sender handles mid-flight would orphan running pipelines. + /// restart — rebuilding finder/sender handles mid-flight would orphan running pipelines. /// Task SetGroupSettingsAsync(NdiGroupSettings groupSettings, CancellationToken cancellationToken); @@ -90,7 +90,7 @@ public interface IIsoController : IAsyncDisposable /// Per-output recording on/off. When enabled, each subsequently-started ISO writes /// its normalized output to /<display-name>/. /// Already-running ISOs are not retroactively recorded (would require restarting - /// their pipelines, which can hiccup live output) — the operator should disable + + /// their pipelines, which can hiccup live output) — the operator should disable + /// re-enable a participant to start recording it. /// void SetRecording(bool enabled, string? outputDirectory); diff --git a/src/TeamsISO.Engine/Controller/IsoController.cs b/src/Dragon-ISO.Engine/Controller/IsoController.cs similarity index 94% rename from src/TeamsISO.Engine/Controller/IsoController.cs rename to src/Dragon-ISO.Engine/Controller/IsoController.cs index 689c27f..ab02643 100644 --- a/src/TeamsISO.Engine/Controller/IsoController.cs +++ b/src/Dragon-ISO.Engine/Controller/IsoController.cs @@ -1,14 +1,14 @@ -using System.Reactive.Linq; +using System.Reactive.Linq; using System.Reactive.Subjects; using System.Threading.Channels; using Microsoft.Extensions.Logging; -using TeamsISO.Engine.Discovery; -using TeamsISO.Engine.Domain; -using TeamsISO.Engine.Interop; -using TeamsISO.Engine.Persistence; -using TeamsISO.Engine.Pipeline; +using DragonISO.Engine.Discovery; +using DragonISO.Engine.Domain; +using DragonISO.Engine.Interop; +using DragonISO.Engine.Persistence; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.Controller; +namespace DragonISO.Engine.Controller; /// /// Default implementation of . @@ -36,7 +36,7 @@ public sealed class IsoController : IIsoController // marker-drop API which needs to fan out to every running recorder. private readonly Dictionary _recorders = new(); // Per-participant FrameProcessingSettings overrides. Null entry / missing - // key → use global settings. Set entry → that pipeline runs at the + // key → use global settings. Set entry → that pipeline runs at the // override values regardless of global. Persisted to config.json alongside // the IsoAssignment record so it survives process restarts. private readonly Dictionary _overrides = new(); @@ -103,7 +103,7 @@ public sealed class IsoController : IIsoController throw new InvalidOperationException("Controller already started."); _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); - // Runtime probe — surface alert if mismatch but don't fail startup. + // Runtime probe — surface alert if mismatch but don't fail startup. var probeResult = _runtimeProbe.Probe(); if (probeResult is NdiRuntimeProbeResult.Mismatch mismatch) { @@ -180,7 +180,7 @@ public sealed class IsoController : IIsoController IRecorderSink? recorder = null; if (shouldRecord) { - // Per-pipeline recorder instance — each ISO writes to its own + // Per-pipeline recorder instance — each ISO writes to its own // subdirectory keyed by display name. Wrapping in try/catch so a // recorder construction failure (no logger, weird platform) never // takes down EnableIsoAsync. @@ -199,7 +199,7 @@ public sealed class IsoController : IIsoController OutputGroups = outputGroups, Recorder = recorder, // Pass the directory whenever we have a recorder, regardless of the - // global flag — the per-call override may have forced one even when + // global flag — the per-call override may have forced one even when // global recording is off. RecordingOutputDirectory = recorder is not null ? recordingDirectory : null, RecorderDisplayName = p.DisplayName, @@ -263,7 +263,7 @@ public sealed class IsoController : IIsoController wasRunning = _pipelines.ContainsKey(participantId); // Custom name lives on the assignment; for now there's only ever // one in-flight name (the one passed to EnableIsoAsync), and we - // don't track that — so the restart path uses the default name. + // don't track that — so the restart path uses the default name. // OK for v2; per-pipeline custom names can be retrofitted later. customName = null; } @@ -282,7 +282,7 @@ public sealed class IsoController : IIsoController } else { - // Pipeline not running — just persist the override so it takes + // Pipeline not running — just persist the override so it takes // effect the NEXT time the operator enables this participant. await PersistAssignmentsAsync(cancellationToken); } @@ -290,7 +290,7 @@ public sealed class IsoController : IIsoController /// /// Updates the NDI group configuration. Note: existing finder/sender handles aren't - /// rebuilt — group changes take effect on the next process restart, since rebuilding + /// rebuilt — group changes take effect on the next process restart, since rebuilding /// the live finder mid-flight would orphan in-flight participants. The settings /// panel surfaces this caveat to the user. /// @@ -399,7 +399,7 @@ public sealed class IsoController : IIsoController } private static string DefaultOutputName(Guid participantId) => - $"TEAMSISO_{participantId.ToString("N")[..8].ToUpperInvariant()}"; + $"Dragon-ISO_{participantId.ToString("N")[..8].ToUpperInvariant()}"; public async ValueTask DisposeAsync() { diff --git a/src/TeamsISO.Engine/Discovery/DiscoveryEvent.cs b/src/Dragon-ISO.Engine/Discovery/DiscoveryEvent.cs similarity index 71% rename from src/TeamsISO.Engine/Discovery/DiscoveryEvent.cs rename to src/Dragon-ISO.Engine/Discovery/DiscoveryEvent.cs index eb3fd2b..7be12fd 100644 --- a/src/TeamsISO.Engine/Discovery/DiscoveryEvent.cs +++ b/src/Dragon-ISO.Engine/Discovery/DiscoveryEvent.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Discovery; +namespace DragonISO.Engine.Discovery; public abstract record DiscoveryEvent { diff --git a/src/TeamsISO.Engine/Discovery/NdiDiscoveryService.cs b/src/Dragon-ISO.Engine/Discovery/NdiDiscoveryService.cs similarity index 93% rename from src/TeamsISO.Engine/Discovery/NdiDiscoveryService.cs rename to src/Dragon-ISO.Engine/Discovery/NdiDiscoveryService.cs index 67dda9c..4a0e4a2 100644 --- a/src/TeamsISO.Engine/Discovery/NdiDiscoveryService.cs +++ b/src/Dragon-ISO.Engine/Discovery/NdiDiscoveryService.cs @@ -1,8 +1,8 @@ -using System.Threading.Channels; +using System.Threading.Channels; using Microsoft.Extensions.Logging; -using TeamsISO.Engine.Interop; +using DragonISO.Engine.Interop; -namespace TeamsISO.Engine.Discovery; +namespace DragonISO.Engine.Discovery; /// /// Polls at a fixed cadence, diffs the @@ -35,11 +35,11 @@ public sealed class NdiDiscoveryService /// /// Request that the next poll tick rebuild the underlying NDI finder. Useful right /// after the operator changes discovery groups or applies a new transcoder topology - /// — without this, the finder is bound to the groups it was created with and never + /// — without this, the finder is bound to the groups it was created with and never /// sees new sources from the just-configured group. Honored on the next /// tick: the old finder is disposed, a fresh one is created, /// and the seen-set is cleared so all currently-visible sources re-fire as - /// . Cheap (idempotent) — extra Refresh calls + /// . Cheap (idempotent) — extra Refresh calls /// while a refresh is already pending are coalesced. /// public void RequestRefresh() => Interlocked.Exchange(ref _refreshRequested, 1); @@ -94,14 +94,14 @@ public sealed class NdiDiscoveryService /// AND it's been >10s since the last rebuild. /// /// Both rules apply backoff so we don't churn during legitimate empty - /// periods (no meeting active, etc.) — the rebuild is cheap but the log + /// periods (no meeting active, etc.) — the rebuild is cheap but the log /// noise isn't useful. /// public async Task RunAsync(TimeSpan pollInterval, CancellationToken cancellationToken) { try { - // Immediate first poll — PeriodicTimer.WaitForNextTickAsync would + // Immediate first poll — PeriodicTimer.WaitForNextTickAsync would // wait the full interval otherwise, costing us 200-500ms at cold // start when operators are most impatient. try { PollOnce(); } catch (Exception ex) { _logger.LogWarning(ex, "Initial discovery poll failed."); } @@ -127,7 +127,7 @@ public sealed class NdiDiscoveryService RebuildFinder("operator request"); lastRebuildAt = now; } - // Auto-healing rebuilds — see ShouldAutoRebuild. + // Auto-healing rebuilds — see ShouldAutoRebuild. else if (_previous.Count == 0) { var decision = ShouldAutoRebuild( @@ -160,7 +160,7 @@ public sealed class NdiDiscoveryService /// finder alone." Caller is responsible for tracking the timestamps and /// updating lastRebuildAt after the rebuild. /// - /// Public + static for unit-testability — the time-based rules are easy to + /// Public + static for unit-testability — the time-based rules are easy to /// regress and hard to spot in integration testing. /// /// Rules: diff --git a/src/TeamsISO.Engine/Discovery/NdiSourceParser.cs b/src/Dragon-ISO.Engine/Discovery/NdiSourceParser.cs similarity index 81% rename from src/TeamsISO.Engine/Discovery/NdiSourceParser.cs rename to src/Dragon-ISO.Engine/Discovery/NdiSourceParser.cs index dbf2a24..d71714a 100644 --- a/src/TeamsISO.Engine/Discovery/NdiSourceParser.cs +++ b/src/Dragon-ISO.Engine/Discovery/NdiSourceParser.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Discovery; +namespace DragonISO.Engine.Discovery; /// /// Parses NDI source strings emitted by Microsoft Teams. @@ -9,17 +9,17 @@ namespace TeamsISO.Engine.Discovery; /// outputs. Both are recognized. /// /// Legacy (older Teams desktop): -/// "MACHINE (Teams)" — auto-mixed active-speaker output -/// "MACHINE (Teams Audio)" — audio-only mix -/// "MACHINE (Teams Screen Share)" — screen share -/// "MACHINE (Teams - Display Name)" — per-participant +/// "MACHINE (Teams)" — auto-mixed active-speaker output +/// "MACHINE (Teams Audio)" — audio-only mix +/// "MACHINE (Teams Screen Share)" — screen share +/// "MACHINE (Teams - Display Name)" — per-participant /// /// Current (the new Microsoft Teams desktop client, observed 2026): -/// "MACHINE (MS Teams - Active Speaker)" — auto-mixed active-speaker output -/// "MACHINE (MS Teams - Audio Mix)" — audio-only mix -/// "MACHINE (MS Teams - Screen Share)" — screen share -/// "MACHINE (MS Teams - (Local))" — the local user's own preview -/// "MACHINE (MS Teams - Display Name)" — per-participant +/// "MACHINE (MS Teams - Active Speaker)" — auto-mixed active-speaker output +/// "MACHINE (MS Teams - Audio Mix)" — audio-only mix +/// "MACHINE (MS Teams - Screen Share)" — screen share +/// "MACHINE (MS Teams - (Local))" — the local user's own preview +/// "MACHINE (MS Teams - Display Name)" — per-participant /// /// "Microsoft Teams" is also accepted as a defensive future-proof brand prefix. /// @@ -77,7 +77,7 @@ public static class NdiSourceParser var rest = inner[brand.Length..].TrimStart(); - // Legacy "MACHINE (Teams)" — bare brand, no suffix → active speaker. + // Legacy "MACHINE (Teams)" — bare brand, no suffix → active speaker. if (rest.Length == 0) return new NdiSource(fullName, machine, NdiSourceKind.ActiveSpeaker, DisplayName: null); diff --git a/src/TeamsISO.Engine/Discovery/ParticipantTracker.cs b/src/Dragon-ISO.Engine/Discovery/ParticipantTracker.cs similarity index 97% rename from src/TeamsISO.Engine/Discovery/ParticipantTracker.cs rename to src/Dragon-ISO.Engine/Discovery/ParticipantTracker.cs index a3c0fe3..47bc747 100644 --- a/src/TeamsISO.Engine/Discovery/ParticipantTracker.cs +++ b/src/Dragon-ISO.Engine/Discovery/ParticipantTracker.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Discovery; +namespace DragonISO.Engine.Discovery; /// /// Maintains the operator-facing participant list, applying the rename heuristic @@ -140,7 +140,7 @@ public sealed class ParticipantTracker /// /// Removes the synthetic auto-mix row's CurrentSource. Crucially does NOT add to - /// — the auto-mix row's identity is already stable + /// — the auto-mix row's identity is already stable /// via the deterministic v5 GUID, so re-add restores it without needing the /// rename-window heuristic, and we must not let an active-speaker disappearance /// poison the rename matcher for a Participant joining the same machine within diff --git a/src/TeamsISO.Engine/Domain/EngineAlert.cs b/src/Dragon-ISO.Engine/Domain/EngineAlert.cs similarity index 82% rename from src/TeamsISO.Engine/Domain/EngineAlert.cs rename to src/Dragon-ISO.Engine/Domain/EngineAlert.cs index b9189cd..d77453c 100644 --- a/src/TeamsISO.Engine/Domain/EngineAlert.cs +++ b/src/Dragon-ISO.Engine/Domain/EngineAlert.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Domain; +namespace DragonISO.Engine.Domain; /// /// Structured engine alerts for UI banner display and ops logging. @@ -9,7 +9,7 @@ public abstract record EngineAlert(string Message) : EngineAlert($"NDI runtime version mismatch: detected {DetectedVersion}, expected {ExpectedVersion}."); public sealed record OutputNameCollision(string Name) - : EngineAlert($"Another TeamsISO instance on the LAN is emitting an output named '{Name}'."); + : EngineAlert($"Another Dragon-ISO instance on the LAN is emitting an output named '{Name}'."); public sealed record PipelineError(Guid ParticipantId, string Reason) : EngineAlert($"Pipeline {ParticipantId} entered Error: {Reason}"); diff --git a/src/TeamsISO.Engine/Domain/EngineConfig.cs b/src/Dragon-ISO.Engine/Domain/EngineConfig.cs similarity index 93% rename from src/TeamsISO.Engine/Domain/EngineConfig.cs rename to src/Dragon-ISO.Engine/Domain/EngineConfig.cs index eb2a09c..a56bc00 100644 --- a/src/TeamsISO.Engine/Domain/EngineConfig.cs +++ b/src/Dragon-ISO.Engine/Domain/EngineConfig.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Domain; +namespace DragonISO.Engine.Domain; public sealed record EngineConfig( FrameProcessingSettings Global, diff --git a/src/TeamsISO.Engine/Domain/Enums.cs b/src/Dragon-ISO.Engine/Domain/Enums.cs similarity index 93% rename from src/TeamsISO.Engine/Domain/Enums.cs rename to src/Dragon-ISO.Engine/Domain/Enums.cs index e18418b..ce1ee85 100644 --- a/src/TeamsISO.Engine/Domain/Enums.cs +++ b/src/Dragon-ISO.Engine/Domain/Enums.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Domain; +namespace DragonISO.Engine.Domain; public enum NdiSourceKind { diff --git a/src/TeamsISO.Engine/Domain/FrameProcessingSettings.cs b/src/Dragon-ISO.Engine/Domain/FrameProcessingSettings.cs similarity index 97% rename from src/TeamsISO.Engine/Domain/FrameProcessingSettings.cs rename to src/Dragon-ISO.Engine/Domain/FrameProcessingSettings.cs index 8ac57f5..d675978 100644 --- a/src/TeamsISO.Engine/Domain/FrameProcessingSettings.cs +++ b/src/Dragon-ISO.Engine/Domain/FrameProcessingSettings.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Domain; +namespace DragonISO.Engine.Domain; public sealed record FrameProcessingSettings( TargetFramerate Framerate, diff --git a/src/TeamsISO.Engine/Domain/IsoAssignment.cs b/src/Dragon-ISO.Engine/Domain/IsoAssignment.cs similarity index 83% rename from src/TeamsISO.Engine/Domain/IsoAssignment.cs rename to src/Dragon-ISO.Engine/Domain/IsoAssignment.cs index 612f4bc..c00f6e8 100644 --- a/src/TeamsISO.Engine/Domain/IsoAssignment.cs +++ b/src/Dragon-ISO.Engine/Domain/IsoAssignment.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Domain; +namespace DragonISO.Engine.Domain; /// /// Operator's intent for an ISO output. Persisted to config.json. @@ -7,7 +7,7 @@ namespace TeamsISO.Engine.Domain; /// that takes precedence over the global /// defaults for THIS participant only. null means "follow global settings" /// (the common case). Non-null lets the operator run a 30 fps 720p Mixed -/// pipeline next to a 60 fps 1080p Isolated one — useful when downstream +/// pipeline next to a 60 fps 1080p Isolated one — useful when downstream /// switchers expect specific formats per source slot. /// public sealed record IsoAssignment( diff --git a/src/TeamsISO.Engine/Domain/IsoHealthStats.cs b/src/Dragon-ISO.Engine/Domain/IsoHealthStats.cs similarity index 90% rename from src/TeamsISO.Engine/Domain/IsoHealthStats.cs rename to src/Dragon-ISO.Engine/Domain/IsoHealthStats.cs index 335043c..57b3c68 100644 --- a/src/TeamsISO.Engine/Domain/IsoHealthStats.cs +++ b/src/Dragon-ISO.Engine/Domain/IsoHealthStats.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Domain; +namespace DragonISO.Engine.Domain; public sealed record IsoHealthStats( long FramesIn, @@ -25,7 +25,7 @@ public sealed record IsoHealthStats( /// 1.0 is full-scale clip. The UI typically displays this as a decaying /// VU bar in the participants DataGrid. /// - /// Currently always 0.0 — the engine's NDI receiver doesn't capture audio + /// Currently always 0.0 — the engine's NDI receiver doesn't capture audio /// frames yet (video-only). The field exists so the UI scaffolding is /// in place; the audio capture path is a focused engine follow-up that /// adds NdiReceiver audio-frame handling, peak computation, and diff --git a/src/TeamsISO.Engine/Domain/IsoOutput.cs b/src/Dragon-ISO.Engine/Domain/IsoOutput.cs similarity index 78% rename from src/TeamsISO.Engine/Domain/IsoOutput.cs rename to src/Dragon-ISO.Engine/Domain/IsoOutput.cs index 4a660f4..65f9e52 100644 --- a/src/TeamsISO.Engine/Domain/IsoOutput.cs +++ b/src/Dragon-ISO.Engine/Domain/IsoOutput.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Domain; +namespace DragonISO.Engine.Domain; public sealed record IsoOutput( Guid ParticipantId, diff --git a/src/TeamsISO.Engine/Domain/NdiGroupSettings.cs b/src/Dragon-ISO.Engine/Domain/NdiGroupSettings.cs similarity index 71% rename from src/TeamsISO.Engine/Domain/NdiGroupSettings.cs rename to src/Dragon-ISO.Engine/Domain/NdiGroupSettings.cs index 3ae230a..dc4de57 100644 --- a/src/TeamsISO.Engine/Domain/NdiGroupSettings.cs +++ b/src/Dragon-ISO.Engine/Domain/NdiGroupSettings.cs @@ -1,9 +1,9 @@ -namespace TeamsISO.Engine.Domain; +namespace DragonISO.Engine.Domain; /// -/// Network-layer NDI configuration. Lets the operator place TeamsISO inside an NDI +/// Network-layer NDI configuration. Lets the operator place Dragon-ISO inside an NDI /// "transcoder" topology where the upstream (Teams) outputs are confined to a private -/// group so they don't pollute the production network, while TeamsISO's own normalized +/// group so they don't pollute the production network, while Dragon-ISO's own normalized /// outputs broadcast on the standard "Public" group consumed by the switcher. /// /// Both fields are comma-separated lists of NDI group names. Whitespace around commas @@ -13,11 +13,11 @@ namespace TeamsISO.Engine.Domain; /// /// Groups the engine's finder subscribes to when enumerating sources. Set this to /// the private group your Teams machine is configured to broadcast on (e.g. -/// "teamsiso-input") when you want to hide raw Teams outputs from the rest of +/// "Dragon-ISO-input") when you want to hide raw Teams outputs from the rest of /// the network. /// /// -/// Groups TeamsISO's own ISO senders broadcast on. Leave at the default for the +/// Groups Dragon-ISO's own ISO senders broadcast on. Leave at the default for the /// normal case where downstream switchers receive over the standard /// "Public" group; override only for split-audience setups. /// diff --git a/src/TeamsISO.Engine/Domain/NdiSource.cs b/src/Dragon-ISO.Engine/Domain/NdiSource.cs similarity index 86% rename from src/TeamsISO.Engine/Domain/NdiSource.cs rename to src/Dragon-ISO.Engine/Domain/NdiSource.cs index 2394db0..31f37ff 100644 --- a/src/TeamsISO.Engine/Domain/NdiSource.cs +++ b/src/Dragon-ISO.Engine/Domain/NdiSource.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Domain; +namespace DragonISO.Engine.Domain; /// /// Raw discovery record parsed from an NDI source string emitted by Microsoft Teams. diff --git a/src/TeamsISO.Engine/Domain/Participant.cs b/src/Dragon-ISO.Engine/Domain/Participant.cs similarity index 89% rename from src/TeamsISO.Engine/Domain/Participant.cs rename to src/Dragon-ISO.Engine/Domain/Participant.cs index 2a38b67..ec85954 100644 --- a/src/TeamsISO.Engine/Domain/Participant.cs +++ b/src/Dragon-ISO.Engine/Domain/Participant.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Domain; +namespace DragonISO.Engine.Domain; /// /// Operator-facing identity for a single human in the meeting. diff --git a/src/TeamsISO.Engine/TeamsISO.Engine.csproj b/src/Dragon-ISO.Engine/Dragon-ISO.Engine.csproj similarity index 81% rename from src/TeamsISO.Engine/TeamsISO.Engine.csproj rename to src/Dragon-ISO.Engine/Dragon-ISO.Engine.csproj index 848fc51..7f58117 100644 --- a/src/TeamsISO.Engine/TeamsISO.Engine.csproj +++ b/src/Dragon-ISO.Engine/Dragon-ISO.Engine.csproj @@ -1,14 +1,14 @@ - + net8.0 - @@ -24,7 +24,7 @@ - + diff --git a/src/TeamsISO.Engine/Interop/INdiInterop.cs b/src/Dragon-ISO.Engine/Interop/INdiInterop.cs similarity index 93% rename from src/TeamsISO.Engine/Interop/INdiInterop.cs rename to src/Dragon-ISO.Engine/Interop/INdiInterop.cs index 045052a..4778c63 100644 --- a/src/TeamsISO.Engine/Interop/INdiInterop.cs +++ b/src/Dragon-ISO.Engine/Interop/INdiInterop.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Pipeline; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.Interop; +namespace DragonISO.Engine.Interop; /// /// Test seam over the NDI SDK. Production: P/Invoke shim. Tests: FakeNdiInterop. @@ -38,7 +38,7 @@ public interface INdiInterop /// audio is queued (a polling caller drives a UI VU bar; we don't want it /// to block on a video-only sender). /// - /// Default implementation returns null — the + /// Default implementation returns null — the /// in tests doesn't simulate audio; the production /// NdiInteropPInvoke overrides this with the real read. /// diff --git a/src/TeamsISO.Engine/Interop/NdiFindHandle.cs b/src/Dragon-ISO.Engine/Interop/NdiFindHandle.cs similarity index 81% rename from src/TeamsISO.Engine/Interop/NdiFindHandle.cs rename to src/Dragon-ISO.Engine/Interop/NdiFindHandle.cs index fec94a6..b34151b 100644 --- a/src/TeamsISO.Engine/Interop/NdiFindHandle.cs +++ b/src/Dragon-ISO.Engine/Interop/NdiFindHandle.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Interop; +namespace DragonISO.Engine.Interop; /// Opaque handle to an NDI Find instance. Implementation-private. public abstract class NdiFindHandle : IDisposable diff --git a/src/TeamsISO.Engine/Interop/NdiReceiverHandle.cs b/src/Dragon-ISO.Engine/Interop/NdiReceiverHandle.cs similarity index 70% rename from src/TeamsISO.Engine/Interop/NdiReceiverHandle.cs rename to src/Dragon-ISO.Engine/Interop/NdiReceiverHandle.cs index c988da6..17dca7e 100644 --- a/src/TeamsISO.Engine/Interop/NdiReceiverHandle.cs +++ b/src/Dragon-ISO.Engine/Interop/NdiReceiverHandle.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Interop; +namespace DragonISO.Engine.Interop; public abstract class NdiReceiverHandle : IDisposable { diff --git a/src/TeamsISO.Engine/Interop/NdiRuntimeProbe.cs b/src/Dragon-ISO.Engine/Interop/NdiRuntimeProbe.cs similarity index 97% rename from src/TeamsISO.Engine/Interop/NdiRuntimeProbe.cs rename to src/Dragon-ISO.Engine/Interop/NdiRuntimeProbe.cs index ed2278a..1ef30f2 100644 --- a/src/TeamsISO.Engine/Interop/NdiRuntimeProbe.cs +++ b/src/Dragon-ISO.Engine/Interop/NdiRuntimeProbe.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Interop; +namespace DragonISO.Engine.Interop; /// /// Result of an NDI runtime version check. diff --git a/src/TeamsISO.Engine/Interop/NdiSenderHandle.cs b/src/Dragon-ISO.Engine/Interop/NdiSenderHandle.cs similarity index 70% rename from src/TeamsISO.Engine/Interop/NdiSenderHandle.cs rename to src/Dragon-ISO.Engine/Interop/NdiSenderHandle.cs index ad8f645..0d30a42 100644 --- a/src/TeamsISO.Engine/Interop/NdiSenderHandle.cs +++ b/src/Dragon-ISO.Engine/Interop/NdiSenderHandle.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Interop; +namespace DragonISO.Engine.Interop; public abstract class NdiSenderHandle : IDisposable { diff --git a/src/TeamsISO.Engine/Logging/EngineLogging.cs b/src/Dragon-ISO.Engine/Logging/EngineLogging.cs similarity index 84% rename from src/TeamsISO.Engine/Logging/EngineLogging.cs rename to src/Dragon-ISO.Engine/Logging/EngineLogging.cs index c5952aa..db38d22 100644 --- a/src/TeamsISO.Engine/Logging/EngineLogging.cs +++ b/src/Dragon-ISO.Engine/Logging/EngineLogging.cs @@ -1,44 +1,44 @@ -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; -namespace TeamsISO.Engine.Logging; +namespace DragonISO.Engine.Logging; /// /// Convenience factory for an wired to Serilog. Two flavors: /// for headless / Console-mode use (writes only to stdout), /// and for the WPF host (writes to stdout AND to a rolling -/// daily file under %LOCALAPPDATA%\TeamsISO\Logs so support has something to ask for +/// daily file under %LOCALAPPDATA%\Dragon-ISO\Logs so support has something to ask for /// when things break). /// public static class EngineLogging { /// /// Default filename for the rolling-file sink. Serilog rotates on size and date with - /// the suffix it inserts before the extension, so "teamsiso.log" becomes - /// "teamsiso20260508.log", "teamsiso20260508_001.log", etc. + /// the suffix it inserts before the extension, so "DragonISO.log" becomes + /// "Dragon-ISO20260508.log", "Dragon-ISO20260508_001.log", etc. /// - private const string LogFileName = "teamsiso.log"; + private const string LogFileName = "DragonISO.log"; /// /// Default directory for the rolling-file sink: - /// %LOCALAPPDATA%\TeamsISO\Logs\. Created on first write if it doesn't exist. + /// %LOCALAPPDATA%\Dragon-ISO\Logs\. Created on first write if it doesn't exist. /// public static string DefaultLogDirectory => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - "TeamsISO", + "Dragon-ISO", "Logs"); /// - /// Console-only factory. Used by TeamsISO.Console where stdout is the right surface + /// Console-only factory. Used by DragonISO.Console where stdout is the right surface /// and a file sink would be redundant with shell redirection. /// public static ILoggerFactory CreateConsole(LogLevel minimum = LogLevel.Information) { var serilog = new LoggerConfiguration() .MinimumLevel.Is(MapLevel(minimum)) - .Enrich.WithProperty("Component", "TeamsISO.Engine") + .Enrich.WithProperty("Component", "DragonISO.Engine") .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] [{Component}] {Message:lj}{NewLine}{Exception}") .CreateLogger(); @@ -62,7 +62,7 @@ public static class EngineLogging var serilog = new LoggerConfiguration() .MinimumLevel.Is(MapLevel(minimum)) - .Enrich.WithProperty("Component", "TeamsISO.Engine") + .Enrich.WithProperty("Component", "DragonISO.Engine") .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] [{Component}] {Message:lj}{NewLine}{Exception}") .WriteTo.File( @@ -84,7 +84,7 @@ public static class EngineLogging // the injected ILogger, not Serilog.Log.* directly. var factory = new SerilogLoggerFactory(serilog, dispose: true); // Surface the path at startup so support has it without digging. - factory.CreateLogger("TeamsISO.Engine") + factory.CreateLogger("DragonISO.Engine") .LogInformation("Diagnostic logs writing to: {LogDirectory}", dir); return factory; } diff --git a/src/TeamsISO.Engine/Persistence/ConfigStore.cs b/src/Dragon-ISO.Engine/Persistence/ConfigStore.cs similarity index 94% rename from src/TeamsISO.Engine/Persistence/ConfigStore.cs rename to src/Dragon-ISO.Engine/Persistence/ConfigStore.cs index 13fef05..64a5f59 100644 --- a/src/TeamsISO.Engine/Persistence/ConfigStore.cs +++ b/src/Dragon-ISO.Engine/Persistence/ConfigStore.cs @@ -1,9 +1,9 @@ -using System.Text.Json; +using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.Extensions.Logging; -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Persistence; +namespace DragonISO.Engine.Persistence; /// /// Loads and saves as JSON. diff --git a/src/TeamsISO.Engine/Pipeline/AudioPeakComputer.cs b/src/Dragon-ISO.Engine/Pipeline/AudioPeakComputer.cs similarity index 84% rename from src/TeamsISO.Engine/Pipeline/AudioPeakComputer.cs rename to src/Dragon-ISO.Engine/Pipeline/AudioPeakComputer.cs index b603b87..c011ff8 100644 --- a/src/TeamsISO.Engine/Pipeline/AudioPeakComputer.cs +++ b/src/Dragon-ISO.Engine/Pipeline/AudioPeakComputer.cs @@ -1,16 +1,16 @@ -using System.Runtime.InteropServices; +using System.Runtime.InteropServices; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Computes a single peak amplitude (in [0.0, 1.0]) from one NDI audio frame. /// -/// NDI 6's preferred audio format is NDIlib_FourCC_audio_type_FLTP — +/// NDI 6's preferred audio format is NDIlib_FourCC_audio_type_FLTP — /// 32-bit IEEE float, planar (one contiguous chunk per channel). Values are /// nominally normalized to [-1, 1]; brief excursions past 1 during transient /// clipping are clamped here. We compute a max-absolute peak across every /// sample of every channel rather than RMS so the UI VU bar reads -/// "loudest part of the buffer" — the same convention OBS / Resolve / Studio +/// "loudest part of the buffer" — the same convention OBS / Resolve / Studio /// Monitor use for their meters. /// /// Pulled out of so the math is unit-testable @@ -20,10 +20,10 @@ namespace TeamsISO.Engine.Pipeline; /// public static class AudioPeakComputer { - /// FourCC for FLTP — 32-bit float, planar layout. 'F','L','T','p'. + /// FourCC for FLTP — 32-bit float, planar layout. 'F','L','T','p'. public const uint FourCC_FLTP = 0x70544c46; - /// FourCC for FLT — 32-bit float, interleaved. 'F','L','T',' '. Rarely seen but cheap to handle. + /// FourCC for FLT — 32-bit float, interleaved. 'F','L','T',' '. Rarely seen but cheap to handle. public const uint FourCC_FLT = 0x20544c46; /// FourCC for PCM 16-bit signed integer, interleaved. Some legacy senders use this. 'P','C','M','s'. @@ -38,7 +38,7 @@ public static class AudioPeakComputer /// The NDI audio FourCC (see the constants on this class). /// /// Total sample count across all channels (e.g. no_samples * no_channels - /// for FLTP — channels are concatenated planes, but every sample contributes). + /// for FLTP — channels are concatenated planes, but every sample contributes). /// public static double ComputePeak(ReadOnlySpan data, uint fourCC, int totalSamples) { @@ -48,7 +48,7 @@ public static class AudioPeakComputer { FourCC_FLTP or FourCC_FLT => ComputePeakFloat32(data, totalSamples), FourCC_PCMs16 => ComputePeakInt16(data, totalSamples), - _ => 0.0, // unknown format — surface silence rather than throw + _ => 0.0, // unknown format — surface silence rather than throw }; } @@ -56,7 +56,7 @@ public static class AudioPeakComputer { // 4 bytes per sample. Cap by what's actually in the buffer in case // the caller's totalSamples disagrees with the byte length (defensive - // — a misreporting source shouldn't take down the receiver loop). + // — a misreporting source shouldn't take down the receiver loop). var available = Math.Min(totalSamples, data.Length / 4); if (available <= 0) return 0.0; diff --git a/src/TeamsISO.Engine/Pipeline/ExponentialBackoff.cs b/src/Dragon-ISO.Engine/Pipeline/ExponentialBackoff.cs similarity index 97% rename from src/TeamsISO.Engine/Pipeline/ExponentialBackoff.cs rename to src/Dragon-ISO.Engine/Pipeline/ExponentialBackoff.cs index 5e6da15..5caf2f9 100644 --- a/src/TeamsISO.Engine/Pipeline/ExponentialBackoff.cs +++ b/src/Dragon-ISO.Engine/Pipeline/ExponentialBackoff.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Exponential backoff policy used by 's restart supervisor. diff --git a/src/TeamsISO.Engine/Pipeline/FrameProcessor.cs b/src/Dragon-ISO.Engine/Pipeline/FrameProcessor.cs similarity index 97% rename from src/TeamsISO.Engine/Pipeline/FrameProcessor.cs rename to src/Dragon-ISO.Engine/Pipeline/FrameProcessor.cs index 7da6f9d..73d66cb 100644 --- a/src/TeamsISO.Engine/Pipeline/FrameProcessor.cs +++ b/src/Dragon-ISO.Engine/Pipeline/FrameProcessor.cs @@ -1,8 +1,8 @@ -using System.Threading.Channels; +using System.Threading.Channels; using Microsoft.Extensions.Logging; -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Per-ISO frame timing engine. Implements closest-frame strategy: at each tick, diff --git a/src/TeamsISO.Engine/Pipeline/IFrameClock.cs b/src/Dragon-ISO.Engine/Pipeline/IFrameClock.cs similarity index 79% rename from src/TeamsISO.Engine/Pipeline/IFrameClock.cs rename to src/Dragon-ISO.Engine/Pipeline/IFrameClock.cs index b7980e7..cf179ba 100644 --- a/src/TeamsISO.Engine/Pipeline/IFrameClock.cs +++ b/src/Dragon-ISO.Engine/Pipeline/IFrameClock.cs @@ -1,8 +1,8 @@ -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Test seam over the wall clock. Production: . -/// Tests: FakeFrameClock in TeamsISO.Engine.Tests. +/// Tests: FakeFrameClock in DragonISO.Engine.Tests. /// public interface IFrameClock { diff --git a/src/TeamsISO.Engine/Pipeline/IFrameScaler.cs b/src/Dragon-ISO.Engine/Pipeline/IFrameScaler.cs similarity index 68% rename from src/TeamsISO.Engine/Pipeline/IFrameScaler.cs rename to src/Dragon-ISO.Engine/Pipeline/IFrameScaler.cs index 925b385..e241c93 100644 --- a/src/TeamsISO.Engine/Pipeline/IFrameScaler.cs +++ b/src/Dragon-ISO.Engine/Pipeline/IFrameScaler.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; public interface IFrameScaler { diff --git a/src/TeamsISO.Engine/Pipeline/IRecorderSink.cs b/src/Dragon-ISO.Engine/Pipeline/IRecorderSink.cs similarity index 88% rename from src/TeamsISO.Engine/Pipeline/IRecorderSink.cs rename to src/Dragon-ISO.Engine/Pipeline/IRecorderSink.cs index 9004f66..afa0517 100644 --- a/src/TeamsISO.Engine/Pipeline/IRecorderSink.cs +++ b/src/Dragon-ISO.Engine/Pipeline/IRecorderSink.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Tap point for per-ISO output recording. Each can be @@ -12,7 +12,7 @@ namespace TeamsISO.Engine.Pipeline; /// 3. is called once when the pipeline stops or fails. /// /// Implementations must be tolerant of out-of-order calls (Close before Open, -/// double Close, WriteFrame after Close) — the supervisor's restart logic can +/// double Close, WriteFrame after Close) — the supervisor's restart logic can /// race in unusual ways. The simplest correct implementation is to track an /// _isOpen flag and short-circuit when not open. /// @@ -32,7 +32,7 @@ public interface IRecorderSink : IAsyncDisposable void Open(string participantDisplayName, string outputDirectory, int width, int height, double fps); /// - /// Write one processed frame. Implementations should not block — if encoding is + /// Write one processed frame. Implementations should not block — if encoding is /// expensive, queue the frame to a worker thread and return promptly. Returning /// false means the recorder dropped the frame (disk full, queue overflow); the /// pipeline carries on regardless so a recorder failure never kills the live ISO. @@ -49,8 +49,8 @@ public interface IRecorderSink : IAsyncDisposable /// /// Drop a timestamped marker into the recording. Used by the operator to - /// chapter a recording in real time — "host intro starts here", "guest - /// answer", etc. — so post-production can jump to the right moment without + /// chapter a recording in real time — "host intro starts here", "guest + /// answer", etc. — so post-production can jump to the right moment without /// scrubbing through the raw stream. The label is free-form; an empty /// label means "unnamed marker." No-op when not recording. /// diff --git a/src/TeamsISO.Engine/Pipeline/IsoPipeline.cs b/src/Dragon-ISO.Engine/Pipeline/IsoPipeline.cs similarity index 97% rename from src/TeamsISO.Engine/Pipeline/IsoPipeline.cs rename to src/Dragon-ISO.Engine/Pipeline/IsoPipeline.cs index 3bd064d..a7f8dba 100644 --- a/src/TeamsISO.Engine/Pipeline/IsoPipeline.cs +++ b/src/Dragon-ISO.Engine/Pipeline/IsoPipeline.cs @@ -1,10 +1,10 @@ -using System.IO; +using System.IO; using System.Threading.Channels; using Microsoft.Extensions.Logging; -using TeamsISO.Engine.Domain; -using TeamsISO.Engine.Interop; +using DragonISO.Engine.Domain; +using DragonISO.Engine.Interop; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Per-ISO unit. Owns one capture loop, one frame processor, one send loop, and the @@ -37,7 +37,7 @@ public sealed class IsoPipeline : IAsyncDisposable private DateTimeOffset? _lastReceivedAt; // Most recent ProcessedFrame, for the UI thumbnail. We hold a reference (not a - // copy) — the FrameProcessor allocates new arrays per frame so the captured + // copy) — the FrameProcessor allocates new arrays per frame so the captured // ReadOnlyMemory stays valid until GC reclaims it. UI consumers read // this lazily at ~1Hz; transient null reads (between supervisor restarts) // are handled at the call site. @@ -106,7 +106,7 @@ public sealed class IsoPipeline : IAsyncDisposable IncomingHeight: h) { State = State, - // Peak is the high-water mark since the last GetStats() call — + // Peak is the high-water mark since the last GetStats() call — // ConsumeAudioPeak resets it to 0 atomically, so the next read // reflects only what arrived in the next polling interval. // 0.0 means silence, no audio yet, or the sender is video-only. @@ -164,7 +164,7 @@ public sealed class IsoPipeline : IAsyncDisposable /// Test ctor. The caller supplies the inner runner directly so failures and lifetimes /// can be controlled from a unit test. /// - internal IsoPipeline( + public IsoPipeline( Guid participantId, Func runInner, ExponentialBackoff backoff, @@ -217,7 +217,7 @@ public sealed class IsoPipeline : IAsyncDisposable }, onFrame: frame => { - // Snapshot dimensions only — don't hold the RawFrame reference past + // Snapshot dimensions only — don't hold the RawFrame reference past // the channel write so the GC can reclaim it on schedule, and so a // late stats read can never resurrect a dropped frame's pixel buffer. Volatile.Write(ref _lastWidth, frame.Width); @@ -275,7 +275,7 @@ public sealed class IsoPipeline : IAsyncDisposable try { await _runInner(ct); - // Inner exited normally (typically only on cancellation) — leave the loop. + // Inner exited normally (typically only on cancellation) — leave the loop. break; } catch (OperationCanceledException) @@ -313,7 +313,7 @@ public sealed class IsoPipeline : IAsyncDisposable } /// - /// Default inner pipeline: spins up receiver → processor → sender on bounded channels + /// Default inner pipeline: spins up receiver → processor → sender on bounded channels /// and awaits all three. Throws if any of them throws. /// /// The optional / / @@ -431,7 +431,7 @@ public sealed class IsoPipeline : IAsyncDisposable /// /// Channel-writer wrapper that feeds every successfully-written /// to an . Opens the - /// recorder lazily on the first frame (so we know the actual width/height — + /// recorder lazily on the first frame (so we know the actual width/height — /// the FrameProcessor resolves the resolution enum to concrete dimensions /// only after the first scale, and that's what shows up in the ProcessedFrame). /// @@ -464,7 +464,7 @@ public sealed class IsoPipeline : IAsyncDisposable // Lazy-open after the first successful write so we have concrete // dimensions. We deliberately try-catch here: a recorder failure // (disk full, permission denied) must NOT prevent the live ISO from - // continuing — the user's downstream switcher is the production + // continuing — the user's downstream switcher is the production // surface, the recording is the archive copy. try { diff --git a/src/TeamsISO.Engine/Pipeline/IsoPipelineConfig.cs b/src/Dragon-ISO.Engine/Pipeline/IsoPipelineConfig.cs similarity index 86% rename from src/TeamsISO.Engine/Pipeline/IsoPipelineConfig.cs rename to src/Dragon-ISO.Engine/Pipeline/IsoPipelineConfig.cs index 1968d33..71680a6 100644 --- a/src/TeamsISO.Engine/Pipeline/IsoPipelineConfig.cs +++ b/src/Dragon-ISO.Engine/Pipeline/IsoPipelineConfig.cs @@ -1,9 +1,9 @@ -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// -/// Per-pipeline configuration — identifies the participant, the source it captures, +/// Per-pipeline configuration — identifies the participant, the source it captures, /// the output it emits, and the global processing settings to apply. /// public sealed record IsoPipelineConfig( @@ -12,7 +12,7 @@ public sealed record IsoPipelineConfig( string OutputName, FrameProcessingSettings Settings) { - /// Default no-signal threshold per spec §4. + /// Default no-signal threshold per spec §4. public TimeSpan SlateThreshold { get; init; } = TimeSpan.FromSeconds(2.5); /// Bounded raw-frame channel capacity (drop-oldest backpressure). @@ -47,7 +47,7 @@ public sealed record IsoPipelineConfig( /// /// Operator-facing display name; used by the recorder to derive the filename. /// Distinct from (the NDI source name, which is for - /// machine consumption) — the recorder wants something human-readable. + /// machine consumption) — the recorder wants something human-readable. /// public string? RecorderDisplayName { get; init; } } diff --git a/src/TeamsISO.Engine/Pipeline/ManagedNearestNeighborFrameScaler.cs b/src/Dragon-ISO.Engine/Pipeline/ManagedNearestNeighborFrameScaler.cs similarity index 90% rename from src/TeamsISO.Engine/Pipeline/ManagedNearestNeighborFrameScaler.cs rename to src/Dragon-ISO.Engine/Pipeline/ManagedNearestNeighborFrameScaler.cs index 0b93dbf..cae2a6b 100644 --- a/src/TeamsISO.Engine/Pipeline/ManagedNearestNeighborFrameScaler.cs +++ b/src/Dragon-ISO.Engine/Pipeline/ManagedNearestNeighborFrameScaler.cs @@ -1,10 +1,10 @@ -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Pure-managed BGRA frame scaler with nearest-neighbor sampling and aspect-mode handling. -/// Suitable for v1.0 ship; libyuv is a v1.5 perf optimization (per spec §6 deferred items). +/// Suitable for v1.0 ship; libyuv is a v1.5 perf optimization (per spec §6 deferred items). /// public sealed class ManagedNearestNeighborFrameScaler : IFrameScaler { @@ -34,7 +34,7 @@ public sealed class ManagedNearestNeighborFrameScaler : IFrameScaler // Nearest-neighbor scale into the (drawX, drawY, drawW, drawH) region. for (var dy = 0; dy < drawH; dy++) { - // Map dest-row inside drawn rect → source row + // Map dest-row inside drawn rect → source row var sy = (int)((long)dy * source.Height / drawH); if (sy >= source.Height) sy = source.Height - 1; var srcRowStart = sy * source.Width * 4; @@ -66,20 +66,20 @@ public sealed class ManagedNearestNeighborFrameScaler : IFrameScaler case AspectMode.Pillarbox: case AspectMode.Letterbox: { - // Both modes preserve aspect ratio and fit within the destination — the difference is + // Both modes preserve aspect ratio and fit within the destination — the difference is // descriptive (which dimension gets bars), but the math is the same: scale to fit. var srcAspect = (double)srcW / srcH; var dstAspect = (double)dstW / dstH; int w, h; if (srcAspect > dstAspect) { - // Source is wider → letterbox (fill width, bars top/bottom). + // Source is wider → letterbox (fill width, bars top/bottom). w = dstW; h = (int)Math.Round(dstW / srcAspect); } else { - // Source is taller/narrower → pillarbox (fill height, bars left/right). + // Source is taller/narrower → pillarbox (fill height, bars left/right). h = dstH; w = (int)Math.Round(dstH * srcAspect); } diff --git a/src/TeamsISO.Engine/Pipeline/MediaFoundationRecorderSink.cs b/src/Dragon-ISO.Engine/Pipeline/MediaFoundationRecorderSink.cs similarity index 90% rename from src/TeamsISO.Engine/Pipeline/MediaFoundationRecorderSink.cs rename to src/Dragon-ISO.Engine/Pipeline/MediaFoundationRecorderSink.cs index 579ba98..00ed11d 100644 --- a/src/TeamsISO.Engine/Pipeline/MediaFoundationRecorderSink.cs +++ b/src/Dragon-ISO.Engine/Pipeline/MediaFoundationRecorderSink.cs @@ -1,14 +1,14 @@ -// Real-time H.264 recorder using Windows Media Foundation's SinkWriter. +// Real-time H.264 recorder using Windows Media Foundation's SinkWriter. // Gated behind MF_AVAILABLE because activating it requires: // -// 1. `dotnet add src/TeamsISO.Engine package Vortice.MediaFoundation --version 3.6.x` +// 1. `dotnet add src/DragonISO.Engine package Vortice.MediaFoundation --version 3.6.x` // 2. Add `$(DefineConstants);MF_AVAILABLE` -// to `TeamsISO.Engine.csproj` +// to `DragonISO.Engine.csproj` // 3. Swap the `RawBgraRecorderSink` instantiation in `IsoController.EnableIsoAsync` // for `MediaFoundationRecorderSink` // // This file is here so the swap is one line + a NuGet add when an operator is -// ready to trade off the dependency for ~10× smaller recordings (1080p60 raw +// ready to trade off the dependency for ~10× smaller recordings (1080p60 raw // BGRA = ~500 MB/s; H.264 at the same input ~50 MB/s). // // We write to .mp4 (H.264 + AAC) rather than .mkv because Media Foundation's @@ -21,12 +21,12 @@ using Microsoft.Extensions.Logging; using Vortice.MediaFoundation; using static Vortice.MediaFoundation.MediaFactory; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// that encodes incoming /// stream to H.264 in an .mp4 container via Windows Media Foundation's -/// SinkWriter API. Inline encoder — no subprocess, no FFmpeg, no extra disk +/// SinkWriter API. Inline encoder — no subprocess, no FFmpeg, no extra disk /// passes. /// /// Lifecycle matches the contract documented on : @@ -73,11 +73,11 @@ public sealed class MediaFoundationRecorderSink : IRecorderSink _writer = MFCreateSinkWriterFromURL(_outputPath, null, attrs); - // Output type — H.264 baseline, target bitrate scaled by resolution. + // Output type — H.264 baseline, target bitrate scaled by resolution. var outType = MFCreateMediaType(); outType.MajorType = MediaTypeGuids.Video; outType.SubType = VideoFormatGuids.H264; - outType.AvgBitrate = (uint)(width * height * fps * 0.07); // ~0.07 bits/pixel — decent quality + outType.AvgBitrate = (uint)(width * height * fps * 0.07); // ~0.07 bits/pixel — decent quality outType.Set(MediaTypeAttributeKeys.InterlaceMode, (uint)VideoInterlaceMode.Progressive); outType.Set(MediaTypeAttributeKeys.FrameSize, ((long)width << 32) | (uint)height); outType.Set(MediaTypeAttributeKeys.FrameRate, ((long)(fps * 1000) << 32) | 1000U); @@ -85,7 +85,7 @@ public sealed class MediaFoundationRecorderSink : IRecorderSink _streamIndex = _writer.AddStream(outType); - // Input type — BGRA32. MF will internally convert to NV12 for the H.264 encoder. + // Input type — BGRA32. MF will internally convert to NV12 for the H.264 encoder. var inType = MFCreateMediaType(); inType.MajorType = MediaTypeGuids.Video; inType.SubType = VideoFormatGuids.RGB32; @@ -103,7 +103,7 @@ public sealed class MediaFoundationRecorderSink : IRecorderSink _framesWritten = 0; _framesDropped = 0; IsRecording = true; - _logger?.LogInformation("MF recorder open: {Path} ({W}×{H}@{Fps:F2})", _outputPath, width, height, fps); + _logger?.LogInformation("MF recorder open: {Path} ({W}×{H}@{Fps:F2})", _outputPath, width, height, fps); } public bool WriteFrame(ProcessedFrame frame) diff --git a/src/TeamsISO.Engine/Pipeline/NdiReceiver.cs b/src/Dragon-ISO.Engine/Pipeline/NdiReceiver.cs similarity index 93% rename from src/TeamsISO.Engine/Pipeline/NdiReceiver.cs rename to src/Dragon-ISO.Engine/Pipeline/NdiReceiver.cs index 9997ded..40861fa 100644 --- a/src/TeamsISO.Engine/Pipeline/NdiReceiver.cs +++ b/src/Dragon-ISO.Engine/Pipeline/NdiReceiver.cs @@ -1,8 +1,8 @@ -using System.Threading.Channels; +using System.Threading.Channels; using Microsoft.Extensions.Logging; -using TeamsISO.Engine.Interop; +using DragonISO.Engine.Interop; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Wraps an receiver handle and pushes captured frames into a @@ -24,7 +24,7 @@ public sealed class NdiReceiver : IDisposable // - Audio frames arrive at ~50Hz (1024-sample blocks @ 48kHz). // - UI stats polling reads at 1Hz. // If we only published the most recent buffer's peak, the UI would see - // exactly one of every ~50 audio frames per second — loud transients + // exactly one of every ~50 audio frames per second — loud transients // between reads would be invisible. By keeping the running max and // consuming it on read, the UI sees the true peak across the entire // polling interval, which is the actual behavior of a peak VU meter. @@ -52,7 +52,7 @@ public sealed class NdiReceiver : IDisposable /// /// Reads the peak audio amplitude (in [0.0, 1.0]) seen since the last /// call and resets the high-water mark to zero. The reset semantics are - /// what makes this a true peak meter — between two consecutive reads + /// what makes this a true peak meter — between two consecutive reads /// the caller sees the loudest sample that crossed the receiver, not /// just whatever the latest buffer happened to contain. /// @@ -69,7 +69,7 @@ public sealed class NdiReceiver : IDisposable /// /// Non-consuming peek at the current peak high-water mark. Useful for /// observability paths that need to read the value without affecting the - /// max-since-last-read behavior — for example, an external diagnostics + /// max-since-last-read behavior — for example, an external diagnostics /// dashboard that polls more often than the UI. /// public double PeekAudioPeak() @@ -94,7 +94,7 @@ public sealed class NdiReceiver : IDisposable /// updates the high-water peak. Test seam mirroring . /// The CAS loop is needed because two writers (this method, called from /// the audio capture thread) and one reader (, - /// called from the UI poll thread) compete for the same field — a plain + /// called from the UI poll thread) compete for the same field — a plain /// Volatile.Write would lose updates if Consume fires between our read /// and write. /// @@ -107,7 +107,7 @@ public sealed class NdiReceiver : IDisposable do { current = Volatile.Read(ref _audioPeakBits); - // If our peak is no louder than what's already there, leave it — + // If our peak is no louder than what's already there, leave it — // somebody else (the audio thread itself, on a previous frame) // already published a higher max. if (peak.Value <= BitConverter.Int64BitsToDouble(current)) return; @@ -146,10 +146,10 @@ public sealed class NdiReceiver : IDisposable { // Audio frames arrive at the source's frame rate (typically // 48kHz delivered in 1024-sample chunks ~= 50Hz). A 50ms - // poll matches that cadence — we won't miss frames and we + // poll matches that cadence — we won't miss frames and we // won't busy-spin if the source is video-only. // - // Audio loop failures are logged but never re-thrown — a + // Audio loop failures are logged but never re-thrown — a // misbehaving audio path must NEVER tear down the live // video pipeline. The UI VU bar will simply freeze at its // last value. diff --git a/src/TeamsISO.Engine/Pipeline/NdiSender.cs b/src/Dragon-ISO.Engine/Pipeline/NdiSender.cs similarity index 95% rename from src/TeamsISO.Engine/Pipeline/NdiSender.cs rename to src/Dragon-ISO.Engine/Pipeline/NdiSender.cs index 97d463d..259186a 100644 --- a/src/TeamsISO.Engine/Pipeline/NdiSender.cs +++ b/src/Dragon-ISO.Engine/Pipeline/NdiSender.cs @@ -1,8 +1,8 @@ -using System.Threading.Channels; +using System.Threading.Channels; using Microsoft.Extensions.Logging; -using TeamsISO.Engine.Interop; +using DragonISO.Engine.Interop; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Pulls processed frames from a channel and forwards them to . diff --git a/src/TeamsISO.Engine/Pipeline/PassthroughFrameScaler.cs b/src/Dragon-ISO.Engine/Pipeline/PassthroughFrameScaler.cs similarity index 90% rename from src/TeamsISO.Engine/Pipeline/PassthroughFrameScaler.cs rename to src/Dragon-ISO.Engine/Pipeline/PassthroughFrameScaler.cs index a26c5e4..fcfceea 100644 --- a/src/TeamsISO.Engine/Pipeline/PassthroughFrameScaler.cs +++ b/src/Dragon-ISO.Engine/Pipeline/PassthroughFrameScaler.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Phase A scaler. Copies the source frame's pixel buffer through unchanged and tags the diff --git a/src/TeamsISO.Engine/Pipeline/PeriodicTimerFrameClock.cs b/src/Dragon-ISO.Engine/Pipeline/PeriodicTimerFrameClock.cs similarity index 94% rename from src/TeamsISO.Engine/Pipeline/PeriodicTimerFrameClock.cs rename to src/Dragon-ISO.Engine/Pipeline/PeriodicTimerFrameClock.cs index cfacce9..c9d4b79 100644 --- a/src/TeamsISO.Engine/Pipeline/PeriodicTimerFrameClock.cs +++ b/src/Dragon-ISO.Engine/Pipeline/PeriodicTimerFrameClock.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; public sealed class PeriodicTimerFrameClock : IFrameClock, IDisposable { diff --git a/src/TeamsISO.Engine/Pipeline/ProcessedFrame.cs b/src/Dragon-ISO.Engine/Pipeline/ProcessedFrame.cs similarity index 86% rename from src/TeamsISO.Engine/Pipeline/ProcessedFrame.cs rename to src/Dragon-ISO.Engine/Pipeline/ProcessedFrame.cs index 25300d7..a7cb559 100644 --- a/src/TeamsISO.Engine/Pipeline/ProcessedFrame.cs +++ b/src/Dragon-ISO.Engine/Pipeline/ProcessedFrame.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// A frame after framerate, resolution, and aspect normalization. Ready to send. diff --git a/src/TeamsISO.Engine/Pipeline/RawBgraRecorderSink.cs b/src/Dragon-ISO.Engine/Pipeline/RawBgraRecorderSink.cs similarity index 93% rename from src/TeamsISO.Engine/Pipeline/RawBgraRecorderSink.cs rename to src/Dragon-ISO.Engine/Pipeline/RawBgraRecorderSink.cs index 8140858..5e121ed 100644 --- a/src/TeamsISO.Engine/Pipeline/RawBgraRecorderSink.cs +++ b/src/Dragon-ISO.Engine/Pipeline/RawBgraRecorderSink.cs @@ -1,9 +1,9 @@ -using System.IO; +using System.IO; using System.Text.Json; using System.Threading.Channels; using Microsoft.Extensions.Logging; -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Concrete that writes the processed BGRA stream to @@ -15,10 +15,10 @@ namespace TeamsISO.Engine.Pipeline; /// /// Files produced under <outputDir>/<sanitized-display-name>/: /// -/// video.bgra — raw concatenated frames at width*height*4 bytes each. -/// manifest.json — width, height, fps, format, started/ended timestamps, +/// video.bgra — raw concatenated frames at width*height*4 bytes each. +/// manifest.json — width, height, fps, format, started/ended timestamps, /// frame count. Lets a post-processor reconstruct timing without parsing the .bgra. -/// convert.cmd — one-liner that pipes the .bgra into ffmpeg to produce +/// convert.cmd — one-liner that pipes the .bgra into ffmpeg to produce /// a final output.mkv at H.264. Operators just double-click. /// /// @@ -26,7 +26,7 @@ namespace TeamsISO.Engine.Pipeline; /// recording at the default 720p30 takes ~150 GB. Operators should record at the /// lowest acceptable resolution / framerate, or enable recording only on the /// participants they intend to keep. A future MediaFoundationRecorderSink -/// would compress in-process and reduce this 10× — see _NEXT.md. +/// would compress in-process and reduce this 10× — see _NEXT.md. /// /// Threading: writes are serialized through a single bounded channel so the /// pipeline's processor thread never blocks on disk I/O. If the disk can't keep @@ -63,8 +63,8 @@ public sealed class RawBgraRecorderSink : IRecorderSink { _logger = logger; // Bounded queue with DropOldest: if the disk falls behind, lose the - // oldest frames and keep recording — better than blocking the pipeline. - // 240 frames ≈ 4 seconds @ 60 fps; gives us a buffer for transient + // oldest frames and keep recording — better than blocking the pipeline. + // 240 frames ≈ 4 seconds @ 60 fps; gives us a buffer for transient // disk hiccups without unbounded RAM growth on a stuck volume. _queue = Channel.CreateBounded(new BoundedChannelOptions(240) { @@ -196,7 +196,7 @@ public sealed class RawBgraRecorderSink : IRecorderSink var manifest = new { - schema = "teamsiso-recorder/v1", + schema = "Dragon-ISO-recorder/v1", participantDisplayName = _displayName, width = _width, height = _height, diff --git a/src/TeamsISO.Engine/Pipeline/RawFrame.cs b/src/Dragon-ISO.Engine/Pipeline/RawFrame.cs similarity index 84% rename from src/TeamsISO.Engine/Pipeline/RawFrame.cs rename to src/Dragon-ISO.Engine/Pipeline/RawFrame.cs index 417d06b..cac9679 100644 --- a/src/TeamsISO.Engine/Pipeline/RawFrame.cs +++ b/src/Dragon-ISO.Engine/Pipeline/RawFrame.cs @@ -1,7 +1,7 @@ -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// -/// A frame as captured from an NDI receiver. Pixel buffer is opaque to the engine — its +/// A frame as captured from an NDI receiver. Pixel buffer is opaque to the engine — its /// shape is determined by the NDI receive format. Timestamp is the source's reported time. /// public sealed record RawFrame( diff --git a/src/TeamsISO.Engine/Pipeline/SolidFrameRenderer.cs b/src/Dragon-ISO.Engine/Pipeline/SolidFrameRenderer.cs similarity index 93% rename from src/TeamsISO.Engine/Pipeline/SolidFrameRenderer.cs rename to src/Dragon-ISO.Engine/Pipeline/SolidFrameRenderer.cs index 7457d82..3fd18f5 100644 --- a/src/TeamsISO.Engine/Pipeline/SolidFrameRenderer.cs +++ b/src/Dragon-ISO.Engine/Pipeline/SolidFrameRenderer.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Generates a solid-color BGRA frame for use as a "no signal" slate. diff --git a/src/TeamsISO.Engine/Pipeline/TestPatternGenerator.cs b/src/Dragon-ISO.Engine/Pipeline/TestPatternGenerator.cs similarity index 90% rename from src/TeamsISO.Engine/Pipeline/TestPatternGenerator.cs rename to src/Dragon-ISO.Engine/Pipeline/TestPatternGenerator.cs index 831baa6..802a38b 100644 --- a/src/TeamsISO.Engine/Pipeline/TestPatternGenerator.cs +++ b/src/Dragon-ISO.Engine/Pipeline/TestPatternGenerator.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.Engine.Pipeline; +namespace DragonISO.Engine.Pipeline; /// /// Generates synthetic BGRA test-pattern frames for diagnosing NDI setups @@ -6,12 +6,12 @@ namespace TeamsISO.Engine.Pipeline; /// moving sweep band and a small frame-counter readout in the corner so /// the operator can visually confirm frames are flowing in real time. /// -/// Static stateless API — caller bumps the frame counter each tick. Each +/// Static stateless API — caller bumps the frame counter each tick. Each /// call allocates a fresh BGRA byte[] so the caller can hand it straight /// to via a wrapper /// without holding shared buffers. /// -/// Used by TeamsISO.Console --test-pattern; could also be wired +/// Used by DragonISO.Console --test-pattern; could also be wired /// into the WPF host as a "synthetic source" toggle for demoing. /// public static class TestPatternGenerator @@ -54,7 +54,7 @@ public static class TestPatternGenerator var (b, g, r) = BarColors[barIdx]; if (isSweep) { - // Brighten the bar 32 BGR units (clamped) — visible moving band. + // Brighten the bar 32 BGR units (clamped) — visible moving band. b = (byte)Math.Min(255, b + 32); g = (byte)Math.Min(255, g + 32); r = (byte)Math.Min(255, r + 32); diff --git a/src/TeamsISO.App/Services/ControlSurface/Endpoints/TeamsEndpoints.cs b/src/TeamsISO.App/Services/ControlSurface/Endpoints/TeamsEndpoints.cs deleted file mode 100644 index 12068b8..0000000 --- a/src/TeamsISO.App/Services/ControlSurface/Endpoints/TeamsEndpoints.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace TeamsISO.App.Services; - -// /teams/* route handlers — UIAutomation-driven in-call controls. -// -// POST /teams/mute → InvokeTeams(ToggleMute, "mute") -// POST /teams/camera → InvokeTeams(ToggleCamera, "camera") -// POST /teams/leave → InvokeTeams(LeaveCall, "leave") -// POST /teams/share → InvokeTeams(OpenShareTray, "share") -// POST /teams/raise-hand → InvokeTeams(ToggleRaiseHand, "raise-hand") -public sealed partial class ControlSurfaceServer -{ - private object InvokeTeams(Func invoke, string action) - { - var result = invoke(); - return new - { - ok = result == TeamsControlBridge.InvokeResult.Invoked, - action, - result = result.ToString(), - }; - } -} diff --git a/src/tests/TeamsISO.App.Tests/TeamsISO.App.Tests.csproj b/src/tests/Dragon-ISO.App.Tests/Dragon-ISO.App.Tests.csproj similarity index 83% rename from src/tests/TeamsISO.App.Tests/TeamsISO.App.Tests.csproj rename to src/tests/Dragon-ISO.App.Tests/Dragon-ISO.App.Tests.csproj index 579bc21..ad8ac34 100644 --- a/src/tests/TeamsISO.App.Tests/TeamsISO.App.Tests.csproj +++ b/src/tests/Dragon-ISO.App.Tests/Dragon-ISO.App.Tests.csproj @@ -1,14 +1,14 @@ - + @@ -39,7 +39,7 @@ - + diff --git a/src/tests/TeamsISO.App.Tests/Fakes/StubIsoController.cs b/src/tests/Dragon-ISO.App.Tests/Fakes/StubIsoController.cs similarity index 95% rename from src/tests/TeamsISO.App.Tests/Fakes/StubIsoController.cs rename to src/tests/Dragon-ISO.App.Tests/Fakes/StubIsoController.cs index 20a2a05..8129c69 100644 --- a/src/tests/TeamsISO.App.Tests/Fakes/StubIsoController.cs +++ b/src/tests/Dragon-ISO.App.Tests/Fakes/StubIsoController.cs @@ -1,10 +1,10 @@ -using System.Reactive.Linq; +using System.Reactive.Linq; using System.Reactive.Subjects; -using TeamsISO.Engine.Controller; -using TeamsISO.Engine.Domain; -using TeamsISO.Engine.Pipeline; +using DragonISO.Engine.Controller; +using DragonISO.Engine.Domain; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.App.Tests.Fakes; +namespace DragonISO.App.Tests.Fakes; // Minimal IIsoController stub for tests that need to instantiate // services in the App layer (ControlSurfaceServer, OscBridge, etc.) diff --git a/src/tests/TeamsISO.App.Tests/Integration/IntegrationTests.cs b/src/tests/Dragon-ISO.App.Tests/Integration/IntegrationTests.cs similarity index 87% rename from src/tests/TeamsISO.App.Tests/Integration/IntegrationTests.cs rename to src/tests/Dragon-ISO.App.Tests/Integration/IntegrationTests.cs index a848472..a887347 100644 --- a/src/tests/TeamsISO.App.Tests/Integration/IntegrationTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Integration/IntegrationTests.cs @@ -1,32 +1,32 @@ -using System.Net; +using System.Net; using System.Net.Http; using System.Net.Sockets; using System.Text.Json; using System.Windows; using System.Windows.Media; using FluentAssertions; -using TeamsISO.App.Services; -using TeamsISO.App.Tests.Fakes; -using TeamsISO.App.ViewModels; -using TeamsISO.Engine.Domain; +using DragonISO.App.Services; +using DragonISO.App.Tests.Fakes; +using DragonISO.App.ViewModels; +using DragonISO.Engine.Domain; using Xunit; -namespace TeamsISO.App.Tests.Integration; +namespace DragonISO.App.Tests.Integration; // End-to-end-ish integration tests that need a live WPF Application + // STA dispatcher. All three live in one class + share a // WpfHostFixture so Application is created exactly once for the -// suite (Application is one-per-AppDomain — multiple test classes +// suite (Application is one-per-AppDomain — multiple test classes // trying to construct it independently collide). // // Coverage per the punch list: -// • App-startup headless smoke — construct App's bootstrap layers +// • App-startup headless smoke — construct App's bootstrap layers // on STA, verify XAML resource resolution + theme apply + VM // wiring + MainWindow construction. -// • ControlSurface integration — boot the server on an ephemeral +// • ControlSurface integration — boot the server on an ephemeral // port, populate a real view-model, hit /participants, verify // the JSON includes the live participant. -// • Theme swap — Dark → Light dictionary swap, brush key resolves +// • Theme swap — Dark → Light dictionary swap, brush key resolves // to a different value afterward. [Collection(WpfHostCollection.Name)] public sealed class IntegrationTests @@ -51,7 +51,7 @@ public sealed class IntegrationTests dicts.Clear(); dicts.Add(new ResourceDictionary { - Source = new Uri("pack://application:,,,/TeamsISO;component/Themes/Theme.Dark.xaml", UriKind.Absolute), + Source = new Uri("pack://application:,,,/DragonISO;component/Themes/Theme.Dark.xaml", UriKind.Absolute), }); }); } @@ -63,7 +63,7 @@ public sealed class IntegrationTests // production code path) and that the two theme files // produce different brushes for the same key. End-to-end // exercise of the resource pipeline that doesn't depend on - // Application.Resources global state — both dicts are + // Application.Resources global state — both dicts are // loaded fresh in this call. // // We don't test ThemeManager.SwapColorDictionary here @@ -79,13 +79,13 @@ public sealed class IntegrationTests var darkDict = new ResourceDictionary { Source = new Uri( - "pack://application:,,,/TeamsISO;component/Themes/Theme.Dark.xaml", + "pack://application:,,,/DragonISO;component/Themes/Theme.Dark.xaml", UriKind.Absolute), }; var lightDict = new ResourceDictionary { Source = new Uri( - "pack://application:,,,/TeamsISO;component/Themes/Theme.Light.xaml", + "pack://application:,,,/DragonISO;component/Themes/Theme.Light.xaml", UriKind.Absolute), }; @@ -115,7 +115,7 @@ public sealed class IntegrationTests _wpf.Application.Resources.MergedDictionaries.Add(new ResourceDictionary { Source = new Uri( - "pack://application:,,,/TeamsISO;component/Themes/WildDragonTheme.xaml", + "pack://application:,,,/DragonISO;component/Themes/WildDragonTheme.xaml", UriKind.Absolute), }); }); diff --git a/src/tests/TeamsISO.App.Tests/Integration/WpfHostFixture.cs b/src/tests/Dragon-ISO.App.Tests/Integration/WpfHostFixture.cs similarity index 97% rename from src/tests/TeamsISO.App.Tests/Integration/WpfHostFixture.cs rename to src/tests/Dragon-ISO.App.Tests/Integration/WpfHostFixture.cs index 5351ac9..77de28a 100644 --- a/src/tests/TeamsISO.App.Tests/Integration/WpfHostFixture.cs +++ b/src/tests/Dragon-ISO.App.Tests/Integration/WpfHostFixture.cs @@ -1,8 +1,8 @@ -using System.Threading; +using System.Threading; using System.Windows; using System.Windows.Threading; -namespace TeamsISO.App.Tests.Integration; +namespace DragonISO.App.Tests.Integration; /// /// Shared WPF Application + STA dispatcher fixture. Created once for diff --git a/src/tests/TeamsISO.App.Tests/MeetingTitleExtractionTests.cs b/src/tests/Dragon-ISO.App.Tests/MeetingTitleExtractionTests.cs similarity index 90% rename from src/tests/TeamsISO.App.Tests/MeetingTitleExtractionTests.cs rename to src/tests/Dragon-ISO.App.Tests/MeetingTitleExtractionTests.cs index 083fc79..4ff78ce 100644 --- a/src/tests/TeamsISO.App.Tests/MeetingTitleExtractionTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/MeetingTitleExtractionTests.cs @@ -1,11 +1,11 @@ -using TeamsISO.App.ViewModels; +using DragonISO.App.ViewModels; using Xunit; -namespace TeamsISO.App.Tests; +namespace DragonISO.App.Tests; /// /// Validates the title-stripping heuristic used by the IN-CALL bar pill. -/// Teams' raw window title is the format the user actually sees (or doesn't — +/// Teams' raw window title is the format the user actually sees (or doesn't — /// when auto-hide is on); we want to surface the meaningful meeting-name /// portion without the "| Microsoft Teams" suffix bloating the pill. /// @@ -36,7 +36,7 @@ public class MeetingTitleExtractionTests [Fact] public void BareAppTitle_ReturnsEmpty() { - // "Microsoft Teams" alone means no meeting context — pill should + // "Microsoft Teams" alone means no meeting context — pill should // stay at plain "IN CALL" rather than appending a meaningless title. Assert.Equal(string.Empty, MainViewModel.ExtractMeetingTitle("Microsoft Teams")); } @@ -47,7 +47,7 @@ public class MeetingTitleExtractionTests var long_ = new string('A', 100) + " | Microsoft Teams"; var result = MainViewModel.ExtractMeetingTitle(long_); Assert.True(result.Length <= 50); - Assert.EndsWith("…", result); + Assert.EndsWith("…", result); } [Fact] diff --git a/src/tests/TeamsISO.App.Tests/Services/ControlSurfaceServerTests.cs b/src/tests/Dragon-ISO.App.Tests/Services/ControlSurfaceServerTests.cs similarity index 90% rename from src/tests/TeamsISO.App.Tests/Services/ControlSurfaceServerTests.cs rename to src/tests/Dragon-ISO.App.Tests/Services/ControlSurfaceServerTests.cs index af96675..3882165 100644 --- a/src/tests/TeamsISO.App.Tests/Services/ControlSurfaceServerTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/ControlSurfaceServerTests.cs @@ -1,21 +1,21 @@ -using System.Net; +using System.Net; using System.Net.Http; using System.Net.Sockets; using FluentAssertions; -using TeamsISO.App.Services; -using TeamsISO.App.Tests.Fakes; +using DragonISO.App.Services; +using DragonISO.App.Tests.Fakes; -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; // End-to-end-ish smoke tests for ControlSurfaceServer. Each test boots -// the server on an OS-assigned free port (127.0.0.1 only — no urlacl +// the server on an OS-assigned free port (127.0.0.1 only — no urlacl // required), makes a real HTTP request via HttpClient, and asserts // against the response. The tests share a StubIsoController and a -// null view-model — endpoints that need a UI dispatcher degrade +// null view-model — endpoints that need a UI dispatcher degrade // gracefully (return empty arrays) which is enough to verify the // route table. // -// We don't exercise the WebSocket path here — ClientWebSocket adds +// We don't exercise the WebSocket path here — ClientWebSocket adds // non-trivial timing complexity and the upgrade is verified by the // 426/101 status arc of `/ws` on a non-WS GET (we hit it and confirm // the server doesn't 500). @@ -52,7 +52,7 @@ public sealed class ControlSurfaceServerTests res.StatusCode.Should().Be(HttpStatusCode.OK); var body = await res.Content.ReadAsStringAsync(); - body.Should().Contain("\"product\":\"TeamsISO\""); + body.Should().Contain("\"product\":\"Dragon-ISO\""); body.Should().Contain("\"endpoints\""); } finally @@ -69,7 +69,7 @@ public sealed class ControlSurfaceServerTests // object {error:"not found"}) rather than null, so the response // pipeline writes 200 OK with that body instead of branching to // 404. The body is the disambiguator, matching the rest of the - // surface's "200 + {ok:false,error:…}" convention. Pinning this + // surface's "200 + {ok:false,error:…}" convention. Pinning this // so a deliberate move to a true 404 is a conscious decision, // not an accident. var (server, client, _) = await BootAsync(); @@ -91,7 +91,7 @@ public sealed class ControlSurfaceServerTests [Fact] public async Task GetParticipants_Returns200_WithEmptyListWhenNoViewModel() { - // No dispatcher / no view-model in tests — the endpoint should + // No dispatcher / no view-model in tests — the endpoint should // gracefully return participants=[] rather than throwing. var (server, client, _) = await BootAsync(); try @@ -134,7 +134,7 @@ public sealed class ControlSurfaceServerTests [Fact] public async Task PostPresetApply_MissingPreset_RespondsWithOkFalseAndPresetNotFound() { - // Preset name that demonstrably doesn't exist on disk → endpoint + // Preset name that demonstrably doesn't exist on disk → endpoint // returns 200 with {"ok":false,"error":"preset not found",...}. // We don't 404 on missing presets because the operator may have // typed the wrong name; clearer payload is friendlier. diff --git a/src/tests/TeamsISO.App.Tests/Services/NotesServiceTests.cs b/src/tests/Dragon-ISO.App.Tests/Services/NotesServiceTests.cs similarity index 80% rename from src/tests/TeamsISO.App.Tests/Services/NotesServiceTests.cs rename to src/tests/Dragon-ISO.App.Tests/Services/NotesServiceTests.cs index a37821c..6bcc947 100644 --- a/src/tests/TeamsISO.App.Tests/Services/NotesServiceTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/NotesServiceTests.cs @@ -1,12 +1,12 @@ -using System.IO; +using System.IO; using FluentAssertions; -using TeamsISO.App.Services; +using DragonISO.App.Services; -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; -// Unit tests for NotesService — the append-only show-notes log. +// Unit tests for NotesService — the append-only show-notes log. // Uses the DirectoryOverride seam so writes land in a tempdir and -// don't pollute the dev's real %LOCALAPPDATA%\TeamsISO\Notes folder. +// don't pollute the dev's real %LOCALAPPDATA%\Dragon-ISO\Notes folder. // // Shares NotesStateCollection with any sibling class that mutates // NotesService.DirectoryOverride (the same static-state-shared-via- @@ -19,7 +19,7 @@ public sealed class NotesServiceTests : IDisposable public NotesServiceTests() { - _tempDir = Path.Combine(Path.GetTempPath(), $"teamsiso-notes-{Guid.NewGuid():N}"); + _tempDir = Path.Combine(Path.GetTempPath(), $"Dragon-ISO-notes-{Guid.NewGuid():N}"); _previousOverride = NotesService.DirectoryOverride; NotesService.DirectoryOverride = _tempDir; } @@ -39,8 +39,8 @@ public sealed class NotesServiceTests : IDisposable ok.Should().BeTrue(); File.Exists(NotesService.TodayPath).Should().BeTrue(); var content = File.ReadAllText(NotesService.TodayPath); - content.Should().StartWith("# TeamsISO show notes — "); - content.Should().Contain("— first note"); + content.Should().StartWith("# Dragon-ISO show notes — "); + content.Should().Contain("— first note"); } [Fact] @@ -49,9 +49,9 @@ public sealed class NotesServiceTests : IDisposable NotesService.Append("checkpoint"); var content = File.ReadAllText(NotesService.TodayPath); - // Each appended line follows "- **HH:mm:ss** — " so a + // Each appended line follows "- **HH:mm:ss** — " so a // reader can scan the file as Markdown without preprocessing. - content.Should().MatchRegex(@"- \*\*\d{2}:\d{2}:\d{2}\*\* — checkpoint"); + content.Should().MatchRegex(@"- \*\*\d{2}:\d{2}:\d{2}\*\* — checkpoint"); } [Fact] @@ -66,7 +66,7 @@ public sealed class NotesServiceTests : IDisposable content.Should().Contain("beta"); content.Should().Contain("gamma"); // Header written exactly once, not before every line. - var headerCount = content.Split("# TeamsISO show notes —").Length - 1; + var headerCount = content.Split("# Dragon-ISO show notes —").Length - 1; headerCount.Should().Be(1); } @@ -76,7 +76,7 @@ public sealed class NotesServiceTests : IDisposable NotesService.Append(" padded "); var content = File.ReadAllText(NotesService.TodayPath); - content.Should().Contain("— padded"); + content.Should().Contain("— padded"); content.Should().NotContain(" padded "); // leading-whitespace gone } diff --git a/src/tests/TeamsISO.App.Tests/Services/NotesStateCollection.cs b/src/tests/Dragon-ISO.App.Tests/Services/NotesStateCollection.cs similarity index 91% rename from src/tests/TeamsISO.App.Tests/Services/NotesStateCollection.cs rename to src/tests/Dragon-ISO.App.Tests/Services/NotesStateCollection.cs index 8e8c25b..92d8bea 100644 --- a/src/tests/TeamsISO.App.Tests/Services/NotesStateCollection.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/NotesStateCollection.cs @@ -1,4 +1,4 @@ -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; /// /// Serializes any test class that mutates diff --git a/src/tests/TeamsISO.App.Tests/Services/OperatorPresetStoreTests.cs b/src/tests/Dragon-ISO.App.Tests/Services/OperatorPresetStoreTests.cs similarity index 96% rename from src/tests/TeamsISO.App.Tests/Services/OperatorPresetStoreTests.cs rename to src/tests/Dragon-ISO.App.Tests/Services/OperatorPresetStoreTests.cs index 86519fd..5d5d44d 100644 --- a/src/tests/TeamsISO.App.Tests/Services/OperatorPresetStoreTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/OperatorPresetStoreTests.cs @@ -1,18 +1,18 @@ -using System.IO; +using System.IO; using FluentAssertions; -using TeamsISO.App.Services; +using DragonISO.App.Services; -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; /// /// Unit tests for . Each test redirects /// the store's file path to a per-test temp path via the internal /// PathOverride hook so the operator's real -/// %LOCALAPPDATA%\TeamsISO\presets.json is never touched. +/// %LOCALAPPDATA%\Dragon-ISO\presets.json is never touched. /// /// IDisposable on the test class cleans up the temp path after each /// test. Shares with any other -/// class that mutates — +/// class that mutates — /// xUnit's parallel execution would otherwise let a sibling class's /// ctor clobber our path mid-test. /// @@ -28,7 +28,7 @@ public sealed class OperatorPresetStoreTests : IDisposable // without creating it, and let OperatorPresetStore.Save() create as needed. _tempPath = Path.Combine( Path.GetTempPath(), - $"teamsiso-presets-test-{Guid.NewGuid():N}.json"); + $"Dragon-ISO-presets-test-{Guid.NewGuid():N}.json"); OperatorPresetStore.PathOverride = _tempPath; } @@ -197,7 +197,7 @@ public sealed class OperatorPresetStoreTests : IDisposable { OperatorPresetStore.Save(MakePreset("ShowA", ("Jane", true))); var bundle = OperatorPresetStore.ExportAllAsJson(); - // Modify in-memory: change Jane → Bob, then import without overwrite. + // Modify in-memory: change Jane → Bob, then import without overwrite. OperatorPresetStore.Save(MakePreset("ShowA", ("Bob", true))); var result = OperatorPresetStore.ImportBundle(bundle, overwrite: false); diff --git a/src/tests/TeamsISO.App.Tests/Services/OscBridgeDispatchTests.cs b/src/tests/Dragon-ISO.App.Tests/Services/OscBridgeDispatchTests.cs similarity index 81% rename from src/tests/TeamsISO.App.Tests/Services/OscBridgeDispatchTests.cs rename to src/tests/Dragon-ISO.App.Tests/Services/OscBridgeDispatchTests.cs index f44bb7c..51ed6a3 100644 --- a/src/tests/TeamsISO.App.Tests/Services/OscBridgeDispatchTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/OscBridgeDispatchTests.cs @@ -1,21 +1,21 @@ -using System.IO; +using System.IO; using FluentAssertions; -using TeamsISO.App.Services; -using TeamsISO.App.Tests.Fakes; +using DragonISO.App.Services; +using DragonISO.App.Tests.Fakes; -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; // Tests for the OscBridge.DispatchAsync routing. We construct // OscMessage instances directly (skipping the UDP receive loop) and // assert that the right address resolves to the right controller call. // // The toggle / preset paths require Application.Current.Dispatcher, -// which doesn't exist in xUnit's default execution context — those +// which doesn't exist in xUnit's default execution context — those // paths return early on the null check, so we verify the bail rather // than the happy path. The full toggle path is covered in branch 11's // integration test that boots a real dispatcher. // -// Shares NotesStateCollection with NotesServiceTests — both classes +// Shares NotesStateCollection with NotesServiceTests — both classes // mutate NotesService.DirectoryOverride and would otherwise race. [Collection(NotesStateCollection.Name)] public sealed class OscBridgeDispatchTests : IDisposable @@ -25,7 +25,7 @@ public sealed class OscBridgeDispatchTests : IDisposable public OscBridgeDispatchTests() { - _tempNotesDir = Path.Combine(Path.GetTempPath(), $"teamsiso-osc-{Guid.NewGuid():N}"); + _tempNotesDir = Path.Combine(Path.GetTempPath(), $"Dragon-ISO-osc-{Guid.NewGuid():N}"); _previousNotesOverride = NotesService.DirectoryOverride; NotesService.DirectoryOverride = _tempNotesDir; } @@ -40,7 +40,7 @@ public sealed class OscBridgeDispatchTests : IDisposable private static (OscBridge Bridge, StubIsoController Controller) NewBridge() { var controller = new StubIsoController(); - // OscBridge takes Func — returning null exercises + // OscBridge takes Func — returning null exercises // the "no VM yet" graceful path in handlers that need it. var bridge = new OscBridge(controller, () => null, logger: null); return (bridge, controller); @@ -51,7 +51,7 @@ public sealed class OscBridgeDispatchTests : IDisposable { var (bridge, controller) = NewBridge(); - await bridge.DispatchAsync(new OscMessage { Address = "/teamsiso/refresh-discovery" }); + await bridge.DispatchAsync(new OscMessage { Address = "/Dragon-ISO/refresh-discovery" }); controller.RefreshDiscoveryCalled.Should().BeTrue(); } @@ -61,7 +61,7 @@ public sealed class OscBridgeDispatchTests : IDisposable { var (bridge, controller) = NewBridge(); - await bridge.DispatchAsync(new OscMessage { Address = "/teamsiso/nope/never" }); + await bridge.DispatchAsync(new OscMessage { Address = "/Dragon-ISO/nope/never" }); controller.RefreshDiscoveryCalled.Should().BeFalse(); controller.EnableCalls.Should().BeEmpty(); @@ -75,7 +75,7 @@ public sealed class OscBridgeDispatchTests : IDisposable await bridge.DispatchAsync(new OscMessage { - Address = "/teamsiso/notes", + Address = "/Dragon-ISO/notes", TypeTag = ",s", Args = new object[] { "tracked through OSC" }, }); @@ -89,10 +89,10 @@ public sealed class OscBridgeDispatchTests : IDisposable { // Without a view-model, the stop-all path returns before touching // the controller. The point of this test is to pin that the bail - // is clean — no thrown exception, no controller traffic. + // is clean — no thrown exception, no controller traffic. var (bridge, controller) = NewBridge(); - await bridge.DispatchAsync(new OscMessage { Address = "/teamsiso/stop-all" }); + await bridge.DispatchAsync(new OscMessage { Address = "/Dragon-ISO/stop-all" }); controller.DisableCalls.Should().BeEmpty(); } @@ -100,14 +100,14 @@ public sealed class OscBridgeDispatchTests : IDisposable [Fact] public async Task IsoByNameAddress_NoOpsWhenViewModelIsNull() { - // /teamsiso/iso "Jane" 1 — verifies the bail when no VM is + // /Dragon-ISO/iso "Jane" 1 — verifies the bail when no VM is // wired; doesn't fire EnableIsoAsync. The dispatcher-equipped // version of this round-trip lives in branch 11. var (bridge, controller) = NewBridge(); await bridge.DispatchAsync(new OscMessage { - Address = "/teamsiso/iso", + Address = "/Dragon-ISO/iso", TypeTag = ",sT", Args = new object[] { "Jane", true }, }); diff --git a/src/tests/TeamsISO.App.Tests/Services/OscMessageTests.cs b/src/tests/Dragon-ISO.App.Tests/Services/OscMessageTests.cs similarity index 92% rename from src/tests/TeamsISO.App.Tests/Services/OscMessageTests.cs rename to src/tests/Dragon-ISO.App.Tests/Services/OscMessageTests.cs index be7361b..6a0eee1 100644 --- a/src/tests/TeamsISO.App.Tests/Services/OscMessageTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/OscMessageTests.cs @@ -1,9 +1,9 @@ -using System.IO; +using System.IO; using System.Text; using FluentAssertions; -using TeamsISO.App.Services; +using DragonISO.App.Services; -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; /// /// Tests for the minimal OSC 1.0 parser inside . @@ -22,7 +22,7 @@ public class OscMessageTests [Fact] public void TryParse_BundleMarker_ReturnsNull() { - // Bundles start with "#bundle\0..." — we don't support them. + // Bundles start with "#bundle\0..." — we don't support them. var bytes = Encoding.ASCII.GetBytes("#bundle\0"); OscMessage.TryParse(bytes).Should().BeNull(); } @@ -30,7 +30,7 @@ public class OscMessageTests [Fact] public void TryParse_AddressOnly_ParsesAddress() { - // "/foo\0\0\0\0" — 4-char address + null + 3 pad = 8 bytes + // "/foo\0\0\0\0" — 4-char address + null + 3 pad = 8 bytes var bytes = OscPacket("/foo"); var msg = OscMessage.TryParse(bytes); msg.Should().NotBeNull(); @@ -47,10 +47,10 @@ public class OscMessageTests [Fact] public void TryParse_StringArg_RoundTrips() { - var bytes = OscPacket("/teamsiso/iso", ",s", "Jane"); + var bytes = OscPacket("/Dragon-ISO/iso", ",s", "Jane"); var msg = OscMessage.TryParse(bytes); msg.Should().NotBeNull(); - msg!.Address.Should().Be("/teamsiso/iso"); + msg!.Address.Should().Be("/Dragon-ISO/iso"); msg.GetStringArg(0).Should().Be("Jane"); } @@ -118,7 +118,7 @@ public class OscMessageTests [Fact] public void TryParse_StringPlusInt_BothParse() { - var bytes = OscPacket("/teamsiso/iso", ",si", "Jane", 1); + var bytes = OscPacket("/Dragon-ISO/iso", ",si", "Jane", 1); var msg = OscMessage.TryParse(bytes); msg.Should().NotBeNull(); msg!.GetStringArg(0).Should().Be("Jane"); @@ -136,7 +136,7 @@ public class OscMessageTests [Fact] public void TryParse_MissingTypeTagComma_ReturnsNull() { - // Type tag must start with ','. "ix" has no leading comma — invalid. + // Type tag must start with ','. "ix" has no leading comma — invalid. var bytes = OscPacket("/x", "ix"); OscMessage.TryParse(bytes).Should().BeNull(); } @@ -190,7 +190,7 @@ public class OscMessageTests case 's': WriteOscString(ms, (string)args[argIdx++]); break; case 'T': case 'F': break; // no payload default: - // Unknown tag — emit no payload. Mirrors how a hostile sender + // Unknown tag — emit no payload. Mirrors how a hostile sender // might construct a malformed packet; the parser should reject. break; } diff --git a/src/tests/TeamsISO.App.Tests/Services/OutputNameTemplateTests.cs b/src/tests/Dragon-ISO.App.Tests/Services/OutputNameTemplateTests.cs similarity index 80% rename from src/tests/TeamsISO.App.Tests/Services/OutputNameTemplateTests.cs rename to src/tests/Dragon-ISO.App.Tests/Services/OutputNameTemplateTests.cs index 368952f..c197141 100644 --- a/src/tests/TeamsISO.App.Tests/Services/OutputNameTemplateTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/OutputNameTemplateTests.cs @@ -1,7 +1,7 @@ -using FluentAssertions; -using TeamsISO.App.Services; +using FluentAssertions; +using DragonISO.App.Services; -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; /// /// Token-expansion + sanitization tests for . @@ -18,9 +18,9 @@ public class OutputNameTemplateTests public void Render_DefaultTemplate_RendersSpeakerDisplayName() { var name = OutputNameTemplate.Render(OutputNameTemplate.DefaultTemplate, TestId, "Jane"); - // Default is "{name}" since 0.9.0-rc19 — produces the speaker name + // Default is "{name}" since 0.9.0-rc19 — produces the speaker name // directly so downstream switchers see human-readable identifiers. - // Previously was "TEAMSISO_{guid}"; see DefaultTemplate's xmldoc. + // Previously was "Dragon-ISO_{guid}"; see DefaultTemplate's xmldoc. name.Should().Be("Jane"); } @@ -30,27 +30,27 @@ public class OutputNameTemplateTests // The "{name}" default would render to an empty string for a // participant with no display name yet (Teams sometimes delivers // DisplayName a tick after the join event). The empty-name - // fallback substitutes TEAMSISO_{guid} so the NDI sender is + // fallback substitutes Dragon-ISO_{guid} so the NDI sender is // always uniquely identifiable. Without this, the engine would // throw on an empty sender name. var name = OutputNameTemplate.Render(OutputNameTemplate.DefaultTemplate, TestId, ""); - name.Should().Be("TEAMSISO_11223344"); + name.Should().Be("Dragon-ISO_11223344"); } [Fact] public void Render_DefaultTemplate_WhitespaceName_FallsBackToGuidPrefix() { - // Mirror of the empty-name case — whitespace-only display names + // Mirror of the empty-name case — whitespace-only display names // sanitize down to empty and should trigger the same fallback. var name = OutputNameTemplate.Render(OutputNameTemplate.DefaultTemplate, TestId, " "); - name.Should().Be("TEAMSISO_11223344"); + name.Should().Be("Dragon-ISO_11223344"); } [Fact] public void Render_NameToken_UsesSanitizedDisplayName() { - var name = OutputNameTemplate.Render("TEAMSISO_{name}", TestId, "Jane Doe"); - name.Should().Be("TEAMSISO_Jane_Doe", because: "spaces become underscores in the sanitizer"); + var name = OutputNameTemplate.Render("Dragon-ISO_{name}", TestId, "Jane Doe"); + name.Should().Be("Dragon-ISO_Jane_Doe", because: "spaces become underscores in the sanitizer"); } [Fact] @@ -59,12 +59,12 @@ public class OutputNameTemplateTests // NDI accepts more chars than we allow, but we keep it conservative // (alphanumeric + underscore + hyphen + period). Anything else is dropped // or converted to underscore (whitespace). - var name = OutputNameTemplate.Render("{name}", TestId, "Jane (PM) — Lead!"); - // Expect: "Jane_PM__Lead" — parens dropped, em-dash dropped, exclamation dropped. + var name = OutputNameTemplate.Render("{name}", TestId, "Jane (PM) — Lead!"); + // Expect: "Jane_PM__Lead" — parens dropped, em-dash dropped, exclamation dropped. // Whitespace runs collapse into adjacent underscores. name.Should().NotContain("("); name.Should().NotContain(")"); - name.Should().NotContain("—"); + name.Should().NotContain("—"); name.Should().NotContain("!"); name.Should().Contain("Jane"); name.Should().Contain("Lead"); @@ -84,7 +84,7 @@ public class OutputNameTemplateTests // Sanitization may transform spaces in machine names, so just assert non-empty // and that it contains the machine name's alphanumeric-ish chars. name.Should().NotBeNullOrEmpty(); - // MachineName itself is sanitized in render — equality check would be brittle. + // MachineName itself is sanitized in render — equality check would be brittle. } [Fact] @@ -92,7 +92,7 @@ public class OutputNameTemplateTests { var name = OutputNameTemplate.Render("session_{timestamp}", TestId, "Jane"); // yyyyMMdd_HHmmss is 15 chars + underscore separator = 16. - // Combined with "session_" prefix → length should be at least 23. + // Combined with "session_" prefix → length should be at least 23. name.Should().StartWith("session_"); name.Length.Should().BeGreaterThan("session_".Length + 14); } diff --git a/src/tests/TeamsISO.App.Tests/Services/PresetApplierTests.cs b/src/tests/Dragon-ISO.App.Tests/Services/PresetApplierTests.cs similarity index 93% rename from src/tests/TeamsISO.App.Tests/Services/PresetApplierTests.cs rename to src/tests/Dragon-ISO.App.Tests/Services/PresetApplierTests.cs index 95150f0..25d6583 100644 --- a/src/tests/TeamsISO.App.Tests/Services/PresetApplierTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/PresetApplierTests.cs @@ -1,15 +1,15 @@ -using System.IO; +using System.IO; using FluentAssertions; -using TeamsISO.App.Services; -using TeamsISO.App.Tests.Fakes; -using TeamsISO.App.ViewModels; -using TeamsISO.Engine.Domain; +using DragonISO.App.Services; +using DragonISO.App.Tests.Fakes; +using DragonISO.App.ViewModels; +using DragonISO.Engine.Domain; -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; // PresetApplier reconciles a saved preset's per-display-name assignments // against the live participant view-model list. Tests pin the four -// transitions (enable→stay, disable→stay, off→enable, on→disable) plus +// transitions (enable→stay, disable→stay, off→enable, on→disable) plus // the partial-meeting path where the preset references participants // who aren't currently present. // @@ -25,7 +25,7 @@ public sealed class PresetApplierTests : IDisposable public PresetApplierTests() { - _tempPresets = Path.Combine(Path.GetTempPath(), $"teamsiso-presets-{Guid.NewGuid():N}.json"); + _tempPresets = Path.Combine(Path.GetTempPath(), $"Dragon-ISO-presets-{Guid.NewGuid():N}.json"); _previousPresetOverride = OperatorPresetStore.PathOverride; OperatorPresetStore.PathOverride = _tempPresets; } @@ -106,7 +106,7 @@ public sealed class PresetApplierTests : IDisposable result.Matched.Should().Be(1); result.Changed.Should().Be(0, - "the participant is already enabled; preset says enabled — no controller traffic"); + "the participant is already enabled; preset says enabled — no controller traffic"); controller.EnableCalls.Should().BeEmpty(); controller.DisableCalls.Should().BeEmpty(); } diff --git a/src/tests/TeamsISO.App.Tests/Services/PresetStoreCollection.cs b/src/tests/Dragon-ISO.App.Tests/Services/PresetStoreCollection.cs similarity index 75% rename from src/tests/TeamsISO.App.Tests/Services/PresetStoreCollection.cs rename to src/tests/Dragon-ISO.App.Tests/Services/PresetStoreCollection.cs index 0428c82..743b4ea 100644 --- a/src/tests/TeamsISO.App.Tests/Services/PresetStoreCollection.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/PresetStoreCollection.cs @@ -1,8 +1,8 @@ -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; /// /// Serializes any test class that mutates -/// OperatorPresetStore.PathOverride — without this, xUnit runs +/// OperatorPresetStore.PathOverride — without this, xUnit runs /// fixtures in parallel across the assembly and a sibling class can /// clobber the path mid-test, leading to flakes that look like data /// corruption. diff --git a/src/tests/TeamsISO.App.Tests/Services/ThemeManagerTests.cs b/src/tests/Dragon-ISO.App.Tests/Services/ThemeManagerTests.cs similarity index 91% rename from src/tests/TeamsISO.App.Tests/Services/ThemeManagerTests.cs rename to src/tests/Dragon-ISO.App.Tests/Services/ThemeManagerTests.cs index 1a02596..c582ed3 100644 --- a/src/tests/TeamsISO.App.Tests/Services/ThemeManagerTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/ThemeManagerTests.cs @@ -1,9 +1,9 @@ -using FluentAssertions; -using TeamsISO.App.Services; +using FluentAssertions; +using DragonISO.App.Services; -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; -// Unit tests for ThemeManager — exercise the resolve / set / toggle +// Unit tests for ThemeManager — exercise the resolve / set / toggle // state machine behind the test-only constructor that takes stub seams // instead of touching HKCU and %LOCALAPPDATA%. Apply() and the // SystemEvents subscription are intentionally NOT exercised here: @@ -51,7 +51,7 @@ public sealed class ThemeManagerTests [Fact] public void Toggle_FromSystemDark_PinsToOppositeOfCurrent() { - // System currently resolves to Dark → toggle should flip + // System currently resolves to Dark → toggle should flip // *preference* to Light (the opposite of the currently-displayed // theme), not back to System. The point of the click is a // visible change. @@ -96,7 +96,7 @@ public sealed class ThemeManagerTests [Theory] [InlineData("invalid")] - [InlineData("dark")] // case-sensitive — we accept exactly Dark + [InlineData("dark")] // case-sensitive — we accept exactly Dark [InlineData("LIGHT")] [InlineData("")] public void Set_RejectsInvalidPreferenceWithArgumentException(string bad) @@ -124,7 +124,7 @@ public sealed class ThemeManagerTests public void Constructor_DefaultsToSystem_WhenLoadReturnsInvalidValue() { // A prefs file written by a future version with an unknown - // value mustn't poison the in-memory state — invalid loads + // value mustn't poison the in-memory state — invalid loads // fall back to the default, same as a missing file. var tm = NewManager(initialPreference: "Rainbow"); diff --git a/src/tests/TeamsISO.App.Tests/Services/UpdateCheckerTests.cs b/src/tests/Dragon-ISO.App.Tests/Services/UpdateCheckerTests.cs similarity index 87% rename from src/tests/TeamsISO.App.Tests/Services/UpdateCheckerTests.cs rename to src/tests/Dragon-ISO.App.Tests/Services/UpdateCheckerTests.cs index 4771b63..024f86e 100644 --- a/src/tests/TeamsISO.App.Tests/Services/UpdateCheckerTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/UpdateCheckerTests.cs @@ -1,16 +1,16 @@ -using System.IO; +using System.IO; using FluentAssertions; -using TeamsISO.App.Services; +using DragonISO.App.Services; -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; // UpdateChecker unit tests. // -// We don't exercise CheckAsync (the real HTTP call against Forgejo) — +// We don't exercise CheckAsync (the real HTTP call against Forgejo) — // tests must not depend on the network. Coverage instead: -// • TryParseSemVer: version-comparison parsing across the inputs the +// • TryParseSemVer: version-comparison parsing across the inputs the // real release stream produces. -// • CheckIfDueAsync throttle: a recent cooldown stamp short-circuits +// • CheckIfDueAsync throttle: a recent cooldown stamp short-circuits // and returns null *before* CheckAsync runs (which would otherwise // fire an HTTP request). public sealed class UpdateCheckerTests : IDisposable @@ -20,7 +20,7 @@ public sealed class UpdateCheckerTests : IDisposable public UpdateCheckerTests() { - _tempDir = Path.Combine(Path.GetTempPath(), $"teamsiso-update-{Guid.NewGuid():N}"); + _tempDir = Path.Combine(Path.GetTempPath(), $"Dragon-ISO-update-{Guid.NewGuid():N}"); Directory.CreateDirectory(_tempDir); _previousOverride = UpdateChecker.StateDirectoryOverride; UpdateChecker.StateDirectoryOverride = _tempDir; @@ -57,7 +57,7 @@ public sealed class UpdateCheckerTests : IDisposable [Fact] public void TryParseSemVer_OrderingIsSemantic() { - // The CheckAsync comparison is "latest > current" — pin the + // The CheckAsync comparison is "latest > current" — pin the // ordering across the version arc the release process actually // produces. var older = UpdateChecker.TryParseSemVer("v0.1.0")!; @@ -88,7 +88,7 @@ public sealed class UpdateCheckerTests : IDisposable [Fact] public async Task CheckIfDueAsync_ReturnsNull_WhenStampIsOldButCooldownIsLargerThanGap() { - // Edge case: stamp 1h old, cooldown 24h → still suppressed. + // Edge case: stamp 1h old, cooldown 24h → still suppressed. File.WriteAllText( Path.Combine(_tempDir, "last-update-check.txt"), DateTimeOffset.UtcNow.AddHours(-1).ToString("o")); @@ -101,7 +101,7 @@ public sealed class UpdateCheckerTests : IDisposable [Fact] public void LaunchCheckEnabled_RoundTrips() { - // Default (no flag file) → enabled. + // Default (no flag file) → enabled. UpdateChecker.LaunchCheckEnabled.Should().BeTrue(); UpdateChecker.LaunchCheckEnabled = false; diff --git a/src/tests/TeamsISO.App.Tests/Services/WindowStateStoreTests.cs b/src/tests/Dragon-ISO.App.Tests/Services/WindowStateStoreTests.cs similarity index 86% rename from src/tests/TeamsISO.App.Tests/Services/WindowStateStoreTests.cs rename to src/tests/Dragon-ISO.App.Tests/Services/WindowStateStoreTests.cs index e61aeda..f16c5ac 100644 --- a/src/tests/TeamsISO.App.Tests/Services/WindowStateStoreTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/Services/WindowStateStoreTests.cs @@ -1,10 +1,10 @@ -using System.IO; +using System.IO; using System.Text.Json; using System.Windows; using FluentAssertions; -using TeamsISO.App.Services; +using DragonISO.App.Services; -namespace TeamsISO.App.Tests.Services; +namespace DragonISO.App.Tests.Services; // Round-trip tests for WindowStateStore.Save / TryApply. Constructing a // real WPF Window inside an xUnit fact is awkward (no Application.Run, @@ -15,7 +15,7 @@ namespace TeamsISO.App.Tests.Services; // shape WindowStateStore writes. // // The full Window.Left/Width property writes inside TryApply aren't -// covered here — they require a WPF Window instance, which means an +// covered here — they require a WPF Window instance, which means an // Application.Current + dispatcher. We instead cover the bail paths // (file missing, too-small, off-screen) which is where regressions // typically land. @@ -26,7 +26,7 @@ public sealed class WindowStateStoreTests : IDisposable public WindowStateStoreTests() { - _tempPath = Path.Combine(Path.GetTempPath(), $"teamsiso-window-{Guid.NewGuid():N}.json"); + _tempPath = Path.Combine(Path.GetTempPath(), $"Dragon-ISO-window-{Guid.NewGuid():N}.json"); _previousOverride = WindowStateStore.PathOverride; WindowStateStore.PathOverride = _tempPath; } @@ -49,7 +49,7 @@ public sealed class WindowStateStoreTests : IDisposable // Write a Snapshot record through the same JsonSerializer.Serialize // call WindowStateStore.Save uses; read it back and verify all // five fields survive. Coverage gap (Save's own Window reads) - // intentional — see file header. + // intentional — see file header. var snap = new WindowStateStore.Snapshot( Left: 120, Top: 80, Width: 1024, Height: 768, State: WindowState.Maximized); WriteSnapshot(_tempPath, snap); @@ -72,7 +72,7 @@ public sealed class WindowStateStoreTests : IDisposable // We can't construct a Window without STA; we *can* exercise // the bail path that returns before any Window property is // touched by passing null and catching the NRE through the - // store's own try/catch — which makes TryApply return false. + // store's own try/catch — which makes TryApply return false. var result = WindowStateStore.TryApply(null!); result.Should().BeFalse(); @@ -81,7 +81,7 @@ public sealed class WindowStateStoreTests : IDisposable [Fact] public void TryApply_TooSmallSnapshot_RejectsBeforeTouchingWindow() { - // 100×100 is below the 320×240 floor. TryApply should return + // 100×100 is below the 320×240 floor. TryApply should return // false without throwing on the null window. WriteSnapshot(_tempPath, new WindowStateStore.Snapshot(0, 0, 100, 100, WindowState.Normal)); @@ -93,7 +93,7 @@ public sealed class WindowStateStoreTests : IDisposable [Fact] public void TryApply_AbsurdlyLargeSnapshot_RejectsBeforeTouchingWindow() { - // 20000×20000 is above the safety ceiling. Again no throw. + // 20000×20000 is above the safety ceiling. Again no throw. WriteSnapshot(_tempPath, new WindowStateStore.Snapshot(0, 0, 20000, 20000, WindowState.Normal)); var result = WindowStateStore.TryApply(null!); @@ -104,7 +104,7 @@ public sealed class WindowStateStoreTests : IDisposable [Fact] public void TryApply_FullyOffScreenSnapshot_RejectsBeforeTouchingWindow() { - // Way off the virtual screen — no corner falls inside any + // Way off the virtual screen — no corner falls inside any // monitor's working area. WriteSnapshot(_tempPath, new WindowStateStore.Snapshot( Left: -99999, Top: -99999, Width: 800, Height: 600, State: WindowState.Normal)); diff --git a/src/tests/TeamsISO.App.Tests/ViewModels/CommandPaletteMatchesTests.cs b/src/tests/Dragon-ISO.App.Tests/ViewModels/CommandPaletteMatchesTests.cs similarity index 88% rename from src/tests/TeamsISO.App.Tests/ViewModels/CommandPaletteMatchesTests.cs rename to src/tests/Dragon-ISO.App.Tests/ViewModels/CommandPaletteMatchesTests.cs index 466bbd3..3577f87 100644 --- a/src/tests/TeamsISO.App.Tests/ViewModels/CommandPaletteMatchesTests.cs +++ b/src/tests/Dragon-ISO.App.Tests/ViewModels/CommandPaletteMatchesTests.cs @@ -1,14 +1,14 @@ -using FluentAssertions; -using TeamsISO.App.ViewModels; +using FluentAssertions; +using DragonISO.App.ViewModels; -namespace TeamsISO.App.Tests.ViewModels; +namespace DragonISO.App.Tests.ViewModels; -// Unit tests for the CommandPaletteViewModel.Matches predicate — the +// Unit tests for the CommandPaletteViewModel.Matches predicate — the // case-insensitive Contains check across Label / Category / Keywords // that powers the v2 Ctrl+K filter. // // We don't build a full CommandPaletteViewModel here (that requires a -// MainViewModel + IIsoController fake — out of scope). Matches is the +// MainViewModel + IIsoController fake — out of scope). Matches is the // behaviorally-relevant unit; pinning it across a representative // query set guards against accidental regressions when someone adds a // scoring algorithm or swaps Contains for StartsWith. @@ -18,19 +18,19 @@ public sealed class CommandPaletteMatchesTests new(category, label, keywords, Shortcut: null, Invoke: () => { }); [Theory] - // Label substrings — the dominant match path + // Label substrings — the dominant match path [InlineData("Quick", "Stop all ISOs", null, "stop", true)] [InlineData("Quick", "Stop all ISOs", null, "STOP", true)] // case-insensitive [InlineData("Quick", "Stop all ISOs", null, "all", true)] [InlineData("Quick", "Stop all ISOs", null, "ISO", true)] - // Category match — operator types the section name + // Category match — operator types the section name [InlineData("Teams", "Mute / unmute", null, "teams", true)] [InlineData("App", "Help", null, "app", true)] - // Keywords match — synonym path. The Network/topology command has + // Keywords match — synonym path. The Network/topology command has // "ndi groups isolate" in its keywords blob. [InlineData("Network", "Apply transcoder topology", "ndi groups isolate", "ndi", true)] [InlineData("Network", "Apply transcoder topology", "ndi groups isolate", "isolate", true)] - // No-match — none of label/category/keywords contain the query + // No-match — none of label/category/keywords contain the query [InlineData("Quick", "Stop all ISOs", null, "espresso", false)] [InlineData("Teams", "Mute / unmute", "microphone audio toggle", "monitor", false)] public void Matches_Predicate(string category, string label, string? keywords, string query, bool expected) @@ -43,7 +43,7 @@ public sealed class CommandPaletteMatchesTests public void Matches_OperatorTypingShortToken_HitsExpectedCategorySpread() { // "mute" should match the Teams command but not the App theme - // commands — pins the cross-category selectivity that makes + // commands — pins the cross-category selectivity that makes // the palette useful at all. If a future change makes Matches // too permissive (e.g. by indexing the Invoke delegate's // method name), the second assertion catches it. @@ -70,7 +70,7 @@ public sealed class CommandPaletteMatchesTests Cmd("Teams", "Mute / unmute", "microphone audio silence toggle"), Cmd("Teams", "Toggle camera", "video webcam on off"), Cmd("Teams", "Leave call", "exit end disconnect quit"), - Cmd("Network", "Apply transcoder topology", "ndi groups isolate teamsiso-input private"), + Cmd("Network", "Apply transcoder topology", "ndi groups isolate Dragon-ISO-input private"), Cmd("App", "Theme: dark", "appearance night mode"), Cmd("App", "Theme: light", "appearance day mode bright"), Cmd("App", "Theme: follow Windows", "system auto"), @@ -83,7 +83,7 @@ public sealed class CommandPaletteMatchesTests Hits("stop").Should().Be(1); Hits("ndi").Should().Be(2, "Refresh discovery (NDI in keywords) + Apply transcoder topology"); // "App" matches case-insensitively against the four App-category - // commands AND substring-matches inside "Apply transcoder topology" — + // commands AND substring-matches inside "Apply transcoder topology" — // a real operator typing "app" would see five rows, which is // exactly what Contains delivers. Pinning this so a future move // to a stricter (StartsWith / token-boundary) algorithm has to diff --git a/src/tests/TeamsISO.Engine.IntegrationTests/TeamsISO.Engine.IntegrationTests.csproj b/src/tests/Dragon-ISO.Engine.IntegrationTests/Dragon-ISO.Engine.IntegrationTests.csproj similarity index 74% rename from src/tests/TeamsISO.Engine.IntegrationTests/TeamsISO.Engine.IntegrationTests.csproj rename to src/tests/Dragon-ISO.Engine.IntegrationTests/Dragon-ISO.Engine.IntegrationTests.csproj index b85c9ae..f7daf1a 100644 --- a/src/tests/TeamsISO.Engine.IntegrationTests/TeamsISO.Engine.IntegrationTests.csproj +++ b/src/tests/Dragon-ISO.Engine.IntegrationTests/Dragon-ISO.Engine.IntegrationTests.csproj @@ -1,29 +1,29 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - - - - - - - - + + + + net8.0 + enable + enable + + false + true + + - - - - - + + + + + + + + + + + + + + + + + diff --git a/src/tests/TeamsISO.Engine.IntegrationTests/IntegrationTestsScaffold.cs b/src/tests/Dragon-ISO.Engine.IntegrationTests/IntegrationTestsScaffold.cs similarity index 82% rename from src/tests/TeamsISO.Engine.IntegrationTests/IntegrationTestsScaffold.cs rename to src/tests/Dragon-ISO.Engine.IntegrationTests/IntegrationTestsScaffold.cs index 05ebdf6..f658279 100644 --- a/src/tests/TeamsISO.Engine.IntegrationTests/IntegrationTestsScaffold.cs +++ b/src/tests/Dragon-ISO.Engine.IntegrationTests/IntegrationTestsScaffold.cs @@ -1,13 +1,13 @@ -using System.Runtime.Versioning; +using System.Runtime.Versioning; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; -using TeamsISO.Engine.Interop; -using TeamsISO.Engine.NdiInterop; +using DragonISO.Engine.Interop; +using DragonISO.Engine.NdiInterop; -namespace TeamsISO.Engine.IntegrationTests; +namespace DragonISO.Engine.IntegrationTests; /// -/// Integration tests that talk to a real NDI runtime — gated behind +/// Integration tests that talk to a real NDI runtime — gated behind /// requires=ndi so the default CI run skips them. Run locally with: /// dotnet test --filter requires=ndi /// @@ -44,7 +44,7 @@ public class NdiInteropIntegrationTests using (var finder = interop.CreateFinder()) { finder.Should().NotBeNull(because: "default-group finder must construct successfully"); - // Snapshot any visible sources — exercises the path; we don't assert on count + // Snapshot any visible sources — exercises the path; we don't assert on count // because the test environment's NDI sources are unknowable. _ = interop.GetCurrentSources(finder); } @@ -52,9 +52,9 @@ public class NdiInteropIntegrationTests [Theory] [Trait("requires", "ndi")] - [InlineData("teamsiso-test-input")] - [InlineData("teamsiso-test-input,production")] - [InlineData(" teamsiso-test-input ")] + [InlineData("Dragon-ISO-test-input")] + [InlineData("Dragon-ISO-test-input,production")] + [InlineData(" Dragon-ISO-test-input ")] public void Finder_CustomGroups_DoesNotThrow(string groups) { using var interop = NewInterop(); @@ -74,7 +74,7 @@ public class NdiInteropIntegrationTests { using var interop = NewInterop(); - using (var sender = interop.CreateSender("TEAMSISO_TEST_DEFAULT")) + using (var sender = interop.CreateSender("Dragon-ISO_TEST_DEFAULT")) { sender.Should().NotBeNull(); } @@ -86,7 +86,7 @@ public class NdiInteropIntegrationTests { using var interop = NewInterop(); - using (var sender = interop.CreateSender("TEAMSISO_TEST_GROUPED", "teamsiso-test-output")) + using (var sender = interop.CreateSender("Dragon-ISO_TEST_GROUPED", "Dragon-ISO-test-output")) { sender.Should().NotBeNull(); } @@ -101,7 +101,7 @@ public class NdiInteropIntegrationTests // network-layer regressions (firewall, mDNS, multicast disable). using var interop = NewInterop(); - var uniqueName = $"TEAMSISO_LOOP_{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}"; + var uniqueName = $"Dragon-ISO_LOOP_{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}"; using var sender = interop.CreateSender(uniqueName); using var finder = interop.CreateFinder(); diff --git a/src/tests/TeamsISO.Engine.IntegrationTests/PipelineFrameRoundTripTests.cs b/src/tests/Dragon-ISO.Engine.IntegrationTests/PipelineFrameRoundTripTests.cs similarity index 70% rename from src/tests/TeamsISO.Engine.IntegrationTests/PipelineFrameRoundTripTests.cs rename to src/tests/Dragon-ISO.Engine.IntegrationTests/PipelineFrameRoundTripTests.cs index 0f8c84e..66c3a15 100644 --- a/src/tests/TeamsISO.Engine.IntegrationTests/PipelineFrameRoundTripTests.cs +++ b/src/tests/Dragon-ISO.Engine.IntegrationTests/PipelineFrameRoundTripTests.cs @@ -1,18 +1,18 @@ -using System.Runtime.Versioning; +using System.Runtime.Versioning; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; -using TeamsISO.Engine.Domain; -using TeamsISO.Engine.NdiInterop; -using TeamsISO.Engine.Pipeline; +using DragonISO.Engine.Domain; +using DragonISO.Engine.NdiInterop; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.IntegrationTests; +namespace DragonISO.Engine.IntegrationTests; /// /// End-to-end pipeline test: synthesize a fake Teams source, run an /// against it, and assert the resulting normalized /// output stream yields frames at the configured 1080p target size. This is /// the closest unit-test analog to "did the engine actually do its job?" -/// — exercises NdiReceiver, FrameProcessor, ManagedNearestNeighborFrameScaler, +/// — exercises NdiReceiver, FrameProcessor, ManagedNearestNeighborFrameScaler, /// and NdiSender as a single chain through the production P/Invoke shim. /// /// Marked [Trait("requires","ndi")] so default CI skips it; run locally with @@ -25,13 +25,13 @@ public class PipelineFrameRoundTripTests [Trait("requires", "ndi")] public async Task Pipeline_ReceivesFromTeamsLikeSource_AndEmitsAt1080pTarget() { - // ─── 1. Source pump ───────────────────────────────────────────────── + // ─── 1. Source pump ───────────────────────────────────────────────── // Spin up an NDI sender broadcasting an unambiguous, unique source // name. We use the literal "Teams - " form so this works even // if a parallel test happens to be running at the same time. var token = Guid.NewGuid().ToString("N")[..6].ToUpperInvariant(); var sourceShortName = $"Teams - FrameRT_{token}"; - var outputName = $"TEAMSISO_RT_{token}"; + var outputName = $"Dragon-ISO_RT_{token}"; using var interop = new NdiInteropPInvoke(NullLogger.Instance); using var fakeSender = interop.CreateSender(sourceShortName); @@ -40,14 +40,14 @@ public class PipelineFrameRoundTripTests // something obviously-not-zero to pick up. Pump at ~30 fps; the engine // is configured for 59.94 fps target so it'll either re-emit our // frames (slate threshold not yet reached) or interpolate via the - // last-frame re-emit path — either is fine for the dimensions check. + // last-frame re-emit path — either is fine for the dimensions check. const int srcW = 640, srcH = 360; var pixelBuffer = new byte[srcW * srcH * 4]; for (var i = 0; i < pixelBuffer.Length; i += 4) { pixelBuffer[i + 0] = 0xF0; // B pixelBuffer[i + 1] = 0xED; // G - pixelBuffer[i + 2] = 0x97; // R → ~Wild Dragon cyan #97EDF0 in BGRA + pixelBuffer[i + 2] = 0x97; // R → ~Wild Dragon cyan #97EDF0 in BGRA pixelBuffer[i + 3] = 0xFF; // A } @@ -66,13 +66,13 @@ public class PipelineFrameRoundTripTests try { - // ─── 2. Wait for our fake source to be discoverable ───────────── + // ─── 2. Wait for our fake source to be discoverable ───────────── using var finder = interop.CreateFinder(); var fullSourceName = await WaitForSourceAsync(interop, finder, sourceShortName, TimeSpan.FromSeconds(5)); fullSourceName.Should().NotBeNullOrEmpty( because: "fake Teams sender should be visible to a same-process finder within 5s"); - // ─── 3. Build the production IsoPipeline against it ──────────── + // ─── 3. Build the production IsoPipeline against it ──────────── var settings = FrameProcessingSettings.Default; // 1080p, 59.94 fps, Pillarbox var clock = new PeriodicTimerFrameClock(settings.FramerateHz); var scaler = new ManagedNearestNeighborFrameScaler(); @@ -85,12 +85,12 @@ public class PipelineFrameRoundTripTests NullLoggerFactory.Instance); await pipeline.StartAsync(); - // ─── 4. Wait for the pipeline's output sender to appear ──────── + // ─── 4. Wait for the pipeline's output sender to appear ──────── var outputFullName = await WaitForSourceAsync(interop, finder, outputName, TimeSpan.FromSeconds(5)); outputFullName.Should().NotBeNullOrEmpty( - because: "IsoPipeline must broadcast a TEAMSISO_* sender within 5s of StartAsync"); + because: "IsoPipeline must broadcast a Dragon-ISO_* sender within 5s of StartAsync"); - // ─── 5. Receive a frame from the normalized output ───────────── + // ─── 5. Receive a frame from the normalized output ───────────── using var receiver = interop.CreateReceiver(outputFullName!); RawFrame? captured = null; var captureDeadline = DateTime.UtcNow.AddSeconds(8); @@ -104,13 +104,13 @@ public class PipelineFrameRoundTripTests captured.Should().NotBeNull( because: "the pipeline output must yield at least one normalized frame within 8s"); - // ─── 6. Assert the normalized dimensions match the target ────── + // ─── 6. Assert the normalized dimensions match the target ────── var (expectedW, expectedH) = settings.ResolutionSize; captured!.Width.Should().Be(expectedW, because: $"the pipeline normalizes to {expectedW}x{expectedH} per FrameProcessingSettings.Default"); captured.Height.Should().Be(expectedH); - // ─── 7. Stop pipeline cleanly ────────────────────────────────── + // ─── 7. Stop pipeline cleanly ────────────────────────────────── await pipeline.StopAsync(); } finally @@ -128,7 +128,7 @@ public class PipelineFrameRoundTripTests /// private static async Task WaitForSourceAsync( NdiInteropPInvoke interop, - TeamsISO.Engine.Interop.NdiFindHandle finder, + DragonISO.Engine.Interop.NdiFindHandle finder, string needle, TimeSpan timeout) { diff --git a/src/tests/TeamsISO.Engine.Tests/Controller/IsoControllerTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Controller/IsoControllerTests.cs similarity index 89% rename from src/tests/TeamsISO.Engine.Tests/Controller/IsoControllerTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Controller/IsoControllerTests.cs index 700e3ee..1a12319 100644 --- a/src/tests/TeamsISO.Engine.Tests/Controller/IsoControllerTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Controller/IsoControllerTests.cs @@ -1,13 +1,13 @@ -using System.Reactive.Linq; +using System.Reactive.Linq; using Microsoft.Extensions.Logging.Abstractions; -using TeamsISO.Engine.Controller; -using TeamsISO.Engine.Domain; -using TeamsISO.Engine.Interop; -using TeamsISO.Engine.Persistence; -using TeamsISO.Engine.Pipeline; -using TeamsISO.Engine.Tests.Fakes; +using DragonISO.Engine.Controller; +using DragonISO.Engine.Domain; +using DragonISO.Engine.Interop; +using DragonISO.Engine.Persistence; +using DragonISO.Engine.Pipeline; +using DragonISO.Engine.Tests.Fakes; -namespace TeamsISO.Engine.Tests.Controller; +namespace DragonISO.Engine.Tests.Controller; public class IsoControllerTests : IDisposable { @@ -21,7 +21,7 @@ public class IsoControllerTests : IDisposable public IsoControllerTests() { - _dir = Path.Combine(Path.GetTempPath(), $"teamsiso-controller-{Guid.NewGuid():N}"); + _dir = Path.Combine(Path.GetTempPath(), $"Dragon-ISO-controller-{Guid.NewGuid():N}"); Directory.CreateDirectory(_dir); _store = new ConfigStore(Path.Combine(_dir, "config.json"), NullLogger.Instance); _interop = new FakeNdiInterop { RuntimeVersion = "6.0.0" }; @@ -83,10 +83,10 @@ public class IsoControllerTests : IDisposable _interop.Sources.Add("PC1 (Teams - Jane)"); var pid = await WaitForFirstParticipantAsync(controller); - await controller.EnableIsoAsync(pid, customName: "TEAMSISO_JANE", CancellationToken.None); + await controller.EnableIsoAsync(pid, customName: "Dragon-ISO_JANE", CancellationToken.None); _factoryCalls.Should().HaveCount(1); - _factoryCalls[0].OutputName.Should().Be("TEAMSISO_JANE"); + _factoryCalls[0].OutputName.Should().Be("Dragon-ISO_JANE"); _factoryCalls[0].SourceName.Should().Be("PC1 (Teams - Jane)"); } @@ -130,12 +130,12 @@ public class IsoControllerTests : IDisposable _store.Save(new EngineConfig( FrameProcessingSettings.Default, Array.Empty(), - new NdiGroupSettings(DiscoveryGroups: "teamsiso-input", OutputGroups: null))); + new NdiGroupSettings(DiscoveryGroups: "Dragon-ISO-input", OutputGroups: null))); await using var controller = NewController(); - _interop.LastFinderGroups.Should().Be("teamsiso-input"); - controller.GroupSettings.DiscoveryGroups.Should().Be("teamsiso-input"); + _interop.LastFinderGroups.Should().Be("Dragon-ISO-input"); + controller.GroupSettings.DiscoveryGroups.Should().Be("Dragon-ISO-input"); } [Fact] @@ -162,7 +162,7 @@ public class IsoControllerTests : IDisposable public async Task SetGroupSettingsAsync_PersistsToConfigStore() { await using var controller = NewController(); - var newGroups = new NdiGroupSettings(DiscoveryGroups: "teamsiso-input", OutputGroups: "Public"); + var newGroups = new NdiGroupSettings(DiscoveryGroups: "Dragon-ISO-input", OutputGroups: "Public"); await controller.SetGroupSettingsAsync(newGroups, CancellationToken.None); @@ -204,7 +204,7 @@ public class IsoControllerTests : IDisposable [Fact] public async Task AddRecordingMarker_NoOpsCleanly_WhenNoActiveRecorders() { - // No pipelines have ever started → no recorders are attached. + // No pipelines have ever started → no recorders are attached. // AddRecordingMarker must not throw on the empty-recorder path // (the UI Ctrl+M binding fires regardless of recording state). await using var controller = NewController(); @@ -235,7 +235,7 @@ public class IsoControllerTests : IDisposable var emitsBefore = seenLists.Count; - // Trigger a refresh — the discovery loop should re-emit. We + // Trigger a refresh — the discovery loop should re-emit. We // don't care exactly how many emissions land, just that the // observable kept producing rather than stalling. controller.RefreshDiscovery(); diff --git a/src/tests/TeamsISO.Engine.Tests/Discovery/NdiDiscoveryServiceTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Discovery/NdiDiscoveryServiceTests.cs similarity index 92% rename from src/tests/TeamsISO.Engine.Tests/Discovery/NdiDiscoveryServiceTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Discovery/NdiDiscoveryServiceTests.cs index a8527aa..fb13c11 100644 --- a/src/tests/TeamsISO.Engine.Tests/Discovery/NdiDiscoveryServiceTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Discovery/NdiDiscoveryServiceTests.cs @@ -1,9 +1,9 @@ -using System.Threading.Channels; +using System.Threading.Channels; using Microsoft.Extensions.Logging.Abstractions; -using TeamsISO.Engine.Discovery; -using TeamsISO.Engine.Tests.Fakes; +using DragonISO.Engine.Discovery; +using DragonISO.Engine.Tests.Fakes; -namespace TeamsISO.Engine.Tests.Discovery; +namespace DragonISO.Engine.Tests.Discovery; public class NdiDiscoveryServiceTests { @@ -68,7 +68,7 @@ public class NdiDiscoveryServiceTests } // ============================================================ - // ShouldAutoRebuild — pure function gating the auto-heal path + // ShouldAutoRebuild — pure function gating the auto-heal path // ============================================================ // // Two rules under test: @@ -101,7 +101,7 @@ public class NdiDiscoveryServiceTests [Fact] public void ShouldAutoRebuild_NeverSeenSource_RecentRebuild_HoldsOff() { - // sinceStart qualifies, but the last rebuild was 2s ago — back off. + // sinceStart qualifies, but the last rebuild was 2s ago — back off. NdiDiscoveryService.ShouldAutoRebuild( sinceStart: TimeSpan.FromSeconds(20), sinceLastSeen: null, @@ -122,7 +122,7 @@ public class NdiDiscoveryServiceTests [Fact] public void ShouldAutoRebuild_HadSources_NowEmpty_Recently_HoldsOff() { - // 10s since last source seen — still inside the 15s grace window. + // 10s since last source seen — still inside the 15s grace window. NdiDiscoveryService.ShouldAutoRebuild( sinceStart: TimeSpan.FromMinutes(5), sinceLastSeen: TimeSpan.FromSeconds(10), @@ -133,7 +133,7 @@ public class NdiDiscoveryServiceTests [Fact] public void ShouldAutoRebuild_HadSources_NowEmpty_RecentRebuild_HoldsOff() { - // Grace window expired, but we just rebuilt 8s ago — back off. + // Grace window expired, but we just rebuilt 8s ago — back off. NdiDiscoveryService.ShouldAutoRebuild( sinceStart: TimeSpan.FromMinutes(5), sinceLastSeen: TimeSpan.FromSeconds(30), diff --git a/src/tests/TeamsISO.Engine.Tests/Discovery/ParticipantTrackerTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Discovery/ParticipantTrackerTests.cs similarity index 98% rename from src/tests/TeamsISO.Engine.Tests/Discovery/ParticipantTrackerTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Discovery/ParticipantTrackerTests.cs index e9268b0..fca5a52 100644 --- a/src/tests/TeamsISO.Engine.Tests/Discovery/ParticipantTrackerTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Discovery/ParticipantTrackerTests.cs @@ -1,7 +1,7 @@ -using TeamsISO.Engine.Discovery; -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Discovery; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Tests.Discovery; +namespace DragonISO.Engine.Tests.Discovery; public class ParticipantTrackerTests { @@ -142,7 +142,7 @@ public class ParticipantTrackerTests // Regression for the discovery-refresh path: when the operator clicks "Refresh" // we deliberately re-emit Added events for sources that were already known // (clearing the discovery service's seen-set so it re-fires everything coming - // back from the rebuilt finder). The tracker must coalesce these — minting a + // back from the rebuilt finder). The tracker must coalesce these — minting a // new Guid would orphan the operator's running ISO and visibly duplicate the // row in the participants list. var time = T0; diff --git a/src/tests/TeamsISO.Engine.Tests/Domain/EnumSanityTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Domain/EnumSanityTests.cs similarity index 92% rename from src/tests/TeamsISO.Engine.Tests/Domain/EnumSanityTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Domain/EnumSanityTests.cs index 1af3d28..e8afb16 100644 --- a/src/tests/TeamsISO.Engine.Tests/Domain/EnumSanityTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Domain/EnumSanityTests.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Tests.Domain; +namespace DragonISO.Engine.Tests.Domain; public class EnumSanityTests { diff --git a/src/tests/TeamsISO.Engine.Tests/Domain/NdiSourceParserTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Domain/NdiSourceParserTests.cs similarity index 91% rename from src/tests/TeamsISO.Engine.Tests/Domain/NdiSourceParserTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Domain/NdiSourceParserTests.cs index d9f21f3..8e94111 100644 --- a/src/tests/TeamsISO.Engine.Tests/Domain/NdiSourceParserTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Domain/NdiSourceParserTests.cs @@ -1,14 +1,14 @@ -using TeamsISO.Engine.Discovery; -using TeamsISO.Engine.Domain; +using DragonISO.Engine.Discovery; +using DragonISO.Engine.Domain; -namespace TeamsISO.Engine.Tests.Domain; +namespace DragonISO.Engine.Tests.Domain; public class NdiSourceParserTests { [Theory] // ----- Legacy "Teams" brand (older Teams desktop) ----- [InlineData("WORKSTATION-01 (Teams - Jane Doe)", "WORKSTATION-01", NdiSourceKind.Participant, "Jane Doe")] - [InlineData("PROD-PC (Teams - Élise O'Connor)", "PROD-PC", NdiSourceKind.Participant, "Élise O'Connor")] + [InlineData("PROD-PC (Teams - Élise O'Connor)", "PROD-PC", NdiSourceKind.Participant, "Élise O'Connor")] [InlineData("HOST (Teams - Smith, Bob (PM))", "HOST", NdiSourceKind.Participant, "Smith, Bob (PM)")] // ----- Current "MS Teams" brand (the new Teams desktop client) ----- [InlineData("WOOGLIN (MS Teams - Brendon Power)", "WOOGLIN", NdiSourceKind.Participant, "Brendon Power")] diff --git a/src/tests/TeamsISO.Engine.Tests/TeamsISO.Engine.Tests.csproj b/src/tests/Dragon-ISO.Engine.Tests/Dragon-ISO.Engine.Tests.csproj similarity index 74% rename from src/tests/TeamsISO.Engine.Tests/TeamsISO.Engine.Tests.csproj rename to src/tests/Dragon-ISO.Engine.Tests/Dragon-ISO.Engine.Tests.csproj index d4e0928..e543fd9 100644 --- a/src/tests/TeamsISO.Engine.Tests/TeamsISO.Engine.Tests.csproj +++ b/src/tests/Dragon-ISO.Engine.Tests/Dragon-ISO.Engine.Tests.csproj @@ -1,34 +1,34 @@ - - - - net8.0 - enable - enable - - false - true - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - - - - - - - - - - - - + + + + net8.0 + enable + enable + + false + true + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + diff --git a/src/tests/TeamsISO.Engine.Tests/Fakes/FakeFrameClock.cs b/src/tests/Dragon-ISO.Engine.Tests/Fakes/FakeFrameClock.cs similarity index 93% rename from src/tests/TeamsISO.Engine.Tests/Fakes/FakeFrameClock.cs rename to src/tests/Dragon-ISO.Engine.Tests/Fakes/FakeFrameClock.cs index e28c94c..b003e51 100644 --- a/src/tests/TeamsISO.Engine.Tests/Fakes/FakeFrameClock.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Fakes/FakeFrameClock.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Pipeline; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.Tests.Fakes; +namespace DragonISO.Engine.Tests.Fakes; /// /// Manual-tick clock. Tests advance the clock and trigger the awaiter explicitly. diff --git a/src/tests/TeamsISO.Engine.Tests/Fakes/FakeNdiInterop.cs b/src/tests/Dragon-ISO.Engine.Tests/Fakes/FakeNdiInterop.cs similarity index 94% rename from src/tests/TeamsISO.Engine.Tests/Fakes/FakeNdiInterop.cs rename to src/tests/Dragon-ISO.Engine.Tests/Fakes/FakeNdiInterop.cs index 394745c..f5e35bb 100644 --- a/src/tests/TeamsISO.Engine.Tests/Fakes/FakeNdiInterop.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Fakes/FakeNdiInterop.cs @@ -1,8 +1,8 @@ -using System.Collections.Concurrent; -using TeamsISO.Engine.Interop; -using TeamsISO.Engine.Pipeline; +using System.Collections.Concurrent; +using DragonISO.Engine.Interop; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.Tests.Fakes; +namespace DragonISO.Engine.Tests.Fakes; /// /// In-memory test double for . Tests configure source lists and frame @@ -51,7 +51,7 @@ public sealed class FakeNdiInterop : INdiInterop var key = ((FakeReceiverHandle)receiver).Source; if (ReceiverAudioPeaks.TryGetValue(key, out var q) && q.TryDequeue(out var peak)) return peak; - return null; // no audio queued — simulate timeout + return null; // no audio queued — simulate timeout } public NdiSenderHandle CreateSender(string outputName, string? groups = null) diff --git a/src/tests/TeamsISO.Engine.Tests/GlobalUsings.cs b/src/tests/Dragon-ISO.Engine.Tests/GlobalUsings.cs similarity index 100% rename from src/tests/TeamsISO.Engine.Tests/GlobalUsings.cs rename to src/tests/Dragon-ISO.Engine.Tests/GlobalUsings.cs diff --git a/src/tests/TeamsISO.Engine.Tests/Interop/NdiInteropNormalizeGroupsTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Interop/NdiInteropNormalizeGroupsTests.cs similarity index 60% rename from src/tests/TeamsISO.Engine.Tests/Interop/NdiInteropNormalizeGroupsTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Interop/NdiInteropNormalizeGroupsTests.cs index f36a4ed..792c348 100644 --- a/src/tests/TeamsISO.Engine.Tests/Interop/NdiInteropNormalizeGroupsTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Interop/NdiInteropNormalizeGroupsTests.cs @@ -1,18 +1,18 @@ -using System.Runtime.Versioning; -using TeamsISO.Engine.NdiInterop; +using System.Runtime.Versioning; +using DragonISO.Engine.NdiInterop; -namespace TeamsISO.Engine.Tests.Interop; +namespace DragonISO.Engine.Tests.Interop; // NdiInteropPInvoke is marked [SupportedOSPlatform("windows")] because it // P/Invokes the Windows-only NDI runtime. The pure NormalizeGroups helper // doesn't actually touch native code, but it inherits the platform tag from // the enclosing class. Re-declaring SupportedOSPlatform here silences CA1416 -// — these tests still only run on Windows (the Engine.Tests project itself +// — these tests still only run on Windows (the Engine.Tests project itself // is platform-agnostic but xunit only schedules them when the OS supports). [SupportedOSPlatform("windows")] // NdiInteropPInvoke.NormalizeGroups is internal; the engine tests project has -// access via InternalsVisibleTo applied to TeamsISO.Engine.NdiInterop. +// access via InternalsVisibleTo applied to DragonISO.Engine.NdiInterop. public class NdiInteropNormalizeGroupsTests { [Theory] @@ -23,11 +23,11 @@ public class NdiInteropNormalizeGroupsTests [InlineData("public", "Public")] // lowercase -> canonical (the bug fix) [InlineData("PUBLIC", "Public")] // shouty -> canonical [InlineData("PuBlIc", "Public")] // mixed case -> canonical - [InlineData("teamsiso-input", "teamsiso-input")] // custom group: pass through - [InlineData("Public,teamsiso-input", "Public,teamsiso-input")] - [InlineData("public,teamsiso-input", "Public,teamsiso-input")] // mixed list normalizes the standard one only - [InlineData("teamsiso-input,PUBLIC", "teamsiso-input,Public")] - [InlineData(" public , teamsiso-input ", "Public,teamsiso-input")] // whitespace trimmed per part + [InlineData("Dragon-ISO-input", "Dragon-ISO-input")] // custom group: pass through + [InlineData("Public,Dragon-ISO-input", "Public,Dragon-ISO-input")] + [InlineData("public,Dragon-ISO-input", "Public,Dragon-ISO-input")] // mixed list normalizes the standard one only + [InlineData("Dragon-ISO-input,PUBLIC", "Dragon-ISO-input,Public")] + [InlineData(" public , Dragon-ISO-input ", "Public,Dragon-ISO-input")] // whitespace trimmed per part public void NormalizeGroups_Maps(string? input, string? expected) { NdiInteropPInvoke.NormalizeGroups(input).Should().Be(expected); diff --git a/src/tests/TeamsISO.Engine.Tests/Interop/NdiRuntimeProbeTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Interop/NdiRuntimeProbeTests.cs similarity index 88% rename from src/tests/TeamsISO.Engine.Tests/Interop/NdiRuntimeProbeTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Interop/NdiRuntimeProbeTests.cs index c4d9258..b989b5f 100644 --- a/src/tests/TeamsISO.Engine.Tests/Interop/NdiRuntimeProbeTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Interop/NdiRuntimeProbeTests.cs @@ -1,7 +1,7 @@ -using TeamsISO.Engine.Interop; -using TeamsISO.Engine.Tests.Fakes; +using DragonISO.Engine.Interop; +using DragonISO.Engine.Tests.Fakes; -namespace TeamsISO.Engine.Tests.Interop; +namespace DragonISO.Engine.Tests.Interop; public class NdiRuntimeProbeTests { diff --git a/src/tests/TeamsISO.Engine.Tests/Logging/EngineLoggingTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Logging/EngineLoggingTests.cs similarity index 90% rename from src/tests/TeamsISO.Engine.Tests/Logging/EngineLoggingTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Logging/EngineLoggingTests.cs index 5c949bc..5585450 100644 --- a/src/tests/TeamsISO.Engine.Tests/Logging/EngineLoggingTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Logging/EngineLoggingTests.cs @@ -1,8 +1,8 @@ -using FluentAssertions; +using FluentAssertions; using Microsoft.Extensions.Logging; -using TeamsISO.Engine.Logging; +using DragonISO.Engine.Logging; -namespace TeamsISO.Engine.Tests.Logging; +namespace DragonISO.Engine.Tests.Logging; public class EngineLoggingTests : IDisposable { @@ -10,7 +10,7 @@ public class EngineLoggingTests : IDisposable public EngineLoggingTests() { - _dir = Path.Combine(Path.GetTempPath(), $"teamsiso-log-{Guid.NewGuid():N}"); + _dir = Path.Combine(Path.GetTempPath(), $"Dragon-ISO-log-{Guid.NewGuid():N}"); } public void Dispose() @@ -21,7 +21,7 @@ public class EngineLoggingTests : IDisposable [Fact] public void CreateDefault_AllLoggers_WriteToFile() { - // Multiple ILoggers from the same factory must all land in the file sink — + // Multiple ILoggers from the same factory must all land in the file sink — // catches regressions in CreateDefault wiring (e.g. if SerilogLoggerFactory // swaps to Log.Logger silently and our static singleton isn't set). var factory = EngineLogging.CreateDefault(LogLevel.Information, logDirectoryOverride: _dir); diff --git a/src/tests/TeamsISO.Engine.Tests/Persistence/ConfigStoreTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Persistence/ConfigStoreTests.cs similarity index 87% rename from src/tests/TeamsISO.Engine.Tests/Persistence/ConfigStoreTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Persistence/ConfigStoreTests.cs index fa156e2..528fa6c 100644 --- a/src/tests/TeamsISO.Engine.Tests/Persistence/ConfigStoreTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Persistence/ConfigStoreTests.cs @@ -1,8 +1,8 @@ -using Microsoft.Extensions.Logging.Abstractions; -using TeamsISO.Engine.Domain; -using TeamsISO.Engine.Persistence; +using Microsoft.Extensions.Logging.Abstractions; +using DragonISO.Engine.Domain; +using DragonISO.Engine.Persistence; -namespace TeamsISO.Engine.Tests.Persistence; +namespace DragonISO.Engine.Tests.Persistence; public class ConfigStoreTests : IDisposable { @@ -10,7 +10,7 @@ public class ConfigStoreTests : IDisposable public ConfigStoreTests() { - _dir = Path.Combine(Path.GetTempPath(), $"teamsiso-tests-{Guid.NewGuid():N}"); + _dir = Path.Combine(Path.GetTempPath(), $"Dragon-ISO-tests-{Guid.NewGuid():N}"); Directory.CreateDirectory(_dir); } @@ -36,7 +36,7 @@ public class ConfigStoreTests : IDisposable AspectMode.Letterbox, AudioMode.Isolated), new[] { - new IsoAssignment(Guid.NewGuid(), IsEnabled: true, CustomOutputName: "TEAMSISO_A"), + new IsoAssignment(Guid.NewGuid(), IsEnabled: true, CustomOutputName: "Dragon-ISO_A"), new IsoAssignment(Guid.NewGuid(), IsEnabled: false, CustomOutputName: null), }); diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/AudioPeakComputerTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/AudioPeakComputerTests.cs similarity index 95% rename from src/tests/TeamsISO.Engine.Tests/Pipeline/AudioPeakComputerTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Pipeline/AudioPeakComputerTests.cs index e7e4cfb..382af71 100644 --- a/src/tests/TeamsISO.Engine.Tests/Pipeline/AudioPeakComputerTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/AudioPeakComputerTests.cs @@ -1,8 +1,8 @@ -using System.Runtime.InteropServices; -using TeamsISO.Engine.Pipeline; +using System.Runtime.InteropServices; +using DragonISO.Engine.Pipeline; using Xunit; -namespace TeamsISO.Engine.Tests.Pipeline; +namespace DragonISO.Engine.Tests.Pipeline; public class AudioPeakComputerTests { @@ -22,7 +22,7 @@ public class AudioPeakComputerTests [Fact] public void UnknownFourCC_ReturnsZero_RatherThanThrow() { - // Receiver loop must never crash on an unrecognized format — better to + // Receiver loop must never crash on an unrecognized format — better to // show silence on the meter than to take down the pipeline. var floats = new[] { 0.5f, -0.5f }; var bytes = AsBytes(floats); @@ -77,7 +77,7 @@ public class AudioPeakComputerTests public void Fltp_TotalSamplesSmallerThanBuffer_OnlyConsumesReportedRange() { // The reported range covers only the first 3 floats. The 4th - // (largest) is past `totalSamples` and must be ignored — otherwise we'd + // (largest) is past `totalSamples` and must be ignored — otherwise we'd // be reading beyond what the source said it wrote. var floats = new[] { 0.1f, -0.2f, 0.3f, 0.99f }; var bytes = AsBytes(floats); @@ -128,7 +128,7 @@ public class AudioPeakComputerTests var samples = new[] { (short)0, (short)16384, (short)-16383 }; var bytes = AsBytes(samples); var peak = AudioPeakComputer.ComputePeak(bytes, AudioPeakComputer.FourCC_PCMs16, samples.Length); - // 16384 / 32767 ≈ 0.500015; tolerate small precision drift. + // 16384 / 32767 ≈ 0.500015; tolerate small precision drift. Assert.InRange(peak, 0.49, 0.51); } diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/ExponentialBackoffTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/ExponentialBackoffTests.cs similarity index 92% rename from src/tests/TeamsISO.Engine.Tests/Pipeline/ExponentialBackoffTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Pipeline/ExponentialBackoffTests.cs index f182fba..08b19b1 100644 --- a/src/tests/TeamsISO.Engine.Tests/Pipeline/ExponentialBackoffTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/ExponentialBackoffTests.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Pipeline; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.Tests.Pipeline; +namespace DragonISO.Engine.Tests.Pipeline; public class ExponentialBackoffTests { diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/FrameProcessorTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/FrameProcessorTests.cs similarity index 95% rename from src/tests/TeamsISO.Engine.Tests/Pipeline/FrameProcessorTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Pipeline/FrameProcessorTests.cs index bd423c9..d3c8c3a 100644 --- a/src/tests/TeamsISO.Engine.Tests/Pipeline/FrameProcessorTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/FrameProcessorTests.cs @@ -1,10 +1,10 @@ -using System.Threading.Channels; +using System.Threading.Channels; using Microsoft.Extensions.Logging.Abstractions; -using TeamsISO.Engine.Domain; -using TeamsISO.Engine.Pipeline; -using TeamsISO.Engine.Tests.Fakes; +using DragonISO.Engine.Domain; +using DragonISO.Engine.Pipeline; +using DragonISO.Engine.Tests.Fakes; -namespace TeamsISO.Engine.Tests.Pipeline; +namespace DragonISO.Engine.Tests.Pipeline; public class FrameProcessorTests { diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/IsoPipelineStatsTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/IsoPipelineStatsTests.cs similarity index 95% rename from src/tests/TeamsISO.Engine.Tests/Pipeline/IsoPipelineStatsTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Pipeline/IsoPipelineStatsTests.cs index bc6cddf..6a300a0 100644 --- a/src/tests/TeamsISO.Engine.Tests/Pipeline/IsoPipelineStatsTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/IsoPipelineStatsTests.cs @@ -1,15 +1,15 @@ -using System.Threading.Channels; +using System.Threading.Channels; using FluentAssertions; using Microsoft.Extensions.Logging.Abstractions; -using TeamsISO.Engine.Domain; -using TeamsISO.Engine.Pipeline; +using DragonISO.Engine.Domain; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.Tests.Pipeline; +namespace DragonISO.Engine.Tests.Pipeline; /// /// Targets the IsoPipeline stats wiring (FPS ring buffer + drops/dups surfaced /// from FrameProcessor). The production-ctor's runner pumps the receiver in a -/// background thread, so we drive the FrameProcessor directly here — that's +/// background thread, so we drive the FrameProcessor directly here — that's /// where FramesDropped and FramesDuplicated are computed. /// public class FrameProcessorStatsTests @@ -52,7 +52,7 @@ public class FrameProcessorStatsTests [Fact] public async Task FrameProcessor_DuplicatesLastFrame_WhenNoNewArrival() { - // First tick: a single frame. Second tick: nothing new — should re-emit + // First tick: a single frame. Second tick: nothing new — should re-emit // the last frame (within slate threshold) and increment FramesDuplicated. var raw = Channel.CreateUnbounded(); var processed = Channel.CreateUnbounded(); diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/IsoPipelineTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/IsoPipelineTests.cs similarity index 92% rename from src/tests/TeamsISO.Engine.Tests/Pipeline/IsoPipelineTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Pipeline/IsoPipelineTests.cs index 765d844..e9746ed 100644 --- a/src/tests/TeamsISO.Engine.Tests/Pipeline/IsoPipelineTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/IsoPipelineTests.cs @@ -1,8 +1,8 @@ -using Microsoft.Extensions.Logging.Abstractions; -using TeamsISO.Engine.Domain; -using TeamsISO.Engine.Pipeline; +using Microsoft.Extensions.Logging.Abstractions; +using DragonISO.Engine.Domain; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.Tests.Pipeline; +namespace DragonISO.Engine.Tests.Pipeline; public class IsoPipelineTests { @@ -88,7 +88,7 @@ public class IsoPipelineTests await pipeline.StartAsync(); - // Wait for supervisor to give up (max 5 attempts × ~1ms delay) + // Wait for supervisor to give up (max 5 attempts × ~1ms delay) var deadline = DateTime.UtcNow.AddSeconds(2); while (pipeline.State != IsoState.Error && DateTime.UtcNow < deadline) await Task.Delay(20); diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/ManagedNearestNeighborFrameScalerTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/ManagedNearestNeighborFrameScalerTests.cs similarity index 95% rename from src/tests/TeamsISO.Engine.Tests/Pipeline/ManagedNearestNeighborFrameScalerTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Pipeline/ManagedNearestNeighborFrameScalerTests.cs index 1ab4a1f..589abd0 100644 --- a/src/tests/TeamsISO.Engine.Tests/Pipeline/ManagedNearestNeighborFrameScalerTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/ManagedNearestNeighborFrameScalerTests.cs @@ -1,7 +1,7 @@ -using TeamsISO.Engine.Domain; -using TeamsISO.Engine.Pipeline; +using DragonISO.Engine.Domain; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.Tests.Pipeline; +namespace DragonISO.Engine.Tests.Pipeline; public class ManagedNearestNeighborFrameScalerTests { diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/NdiReceiverTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/NdiReceiverTests.cs similarity index 92% rename from src/tests/TeamsISO.Engine.Tests/Pipeline/NdiReceiverTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Pipeline/NdiReceiverTests.cs index 3741280..fc7c75c 100644 --- a/src/tests/TeamsISO.Engine.Tests/Pipeline/NdiReceiverTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/NdiReceiverTests.cs @@ -1,9 +1,9 @@ -using System.Threading.Channels; +using System.Threading.Channels; using Microsoft.Extensions.Logging.Abstractions; -using TeamsISO.Engine.Pipeline; -using TeamsISO.Engine.Tests.Fakes; +using DragonISO.Engine.Pipeline; +using DragonISO.Engine.Tests.Fakes; -namespace TeamsISO.Engine.Tests.Pipeline; +namespace DragonISO.Engine.Tests.Pipeline; public class NdiReceiverTests { @@ -88,7 +88,7 @@ public class NdiReceiverTests // First Consume returns the peak receiver.ConsumeAudioPeak().Should().BeApproximately(0.42, precision: 0.0001); - // Second Consume returns 0 — Consume has reset semantics + // Second Consume returns 0 — Consume has reset semantics receiver.ConsumeAudioPeak().Should().Be(0.0); } @@ -96,7 +96,7 @@ public class NdiReceiverTests public void CaptureAudioOnce_KeepsHighWaterMarkAcrossMultipleFrames() { // Three frames with peaks 0.3, 0.8, 0.2. Without reset, the receiver - // must report the loudest (0.8) — that's the whole point of a peak + // must report the loudest (0.8) — that's the whole point of a peak // meter. The "latest peak" naive overwrite would lose the 0.8 if a // quieter 0.2 frame followed it. var interop = new FakeNdiInterop(); @@ -120,7 +120,7 @@ public class NdiReceiverTests { // Establish a peak, then call CaptureAudioOnce when the fake has no // queued frames. The high-water mark must NOT be reset just because - // a polling tick saw nothing — that would defeat the peak-hold + // a polling tick saw nothing — that would defeat the peak-hold // semantic between consumer reads. var interop = new FakeNdiInterop(); interop.ReceiverAudioPeaks.GetOrAdd(Source, _ => new System.Collections.Concurrent.ConcurrentQueue()) diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/NdiSenderTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/NdiSenderTests.cs similarity index 90% rename from src/tests/TeamsISO.Engine.Tests/Pipeline/NdiSenderTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Pipeline/NdiSenderTests.cs index db6aceb..9a7bd9b 100644 --- a/src/tests/TeamsISO.Engine.Tests/Pipeline/NdiSenderTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/NdiSenderTests.cs @@ -1,13 +1,13 @@ -using System.Threading.Channels; +using System.Threading.Channels; using Microsoft.Extensions.Logging.Abstractions; -using TeamsISO.Engine.Pipeline; -using TeamsISO.Engine.Tests.Fakes; +using DragonISO.Engine.Pipeline; +using DragonISO.Engine.Tests.Fakes; -namespace TeamsISO.Engine.Tests.Pipeline; +namespace DragonISO.Engine.Tests.Pipeline; public class NdiSenderTests { - private const string Output = "TEAMSISO_01"; + private const string Output = "Dragon-ISO_01"; private static ProcessedFrame MakeFrame(long ts) => new(1920, 1080, ts, new byte[1920 * 1080 * 4], PixelFormat.Bgra); diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs similarity index 91% rename from src/tests/TeamsISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs index 5976e43..689b099 100644 --- a/src/tests/TeamsISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/SolidFrameRendererTests.cs @@ -1,6 +1,6 @@ -using TeamsISO.Engine.Pipeline; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.Tests.Pipeline; +namespace DragonISO.Engine.Tests.Pipeline; public class SolidFrameRendererTests { diff --git a/src/tests/TeamsISO.Engine.Tests/Pipeline/TestPatternGeneratorTests.cs b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/TestPatternGeneratorTests.cs similarity index 91% rename from src/tests/TeamsISO.Engine.Tests/Pipeline/TestPatternGeneratorTests.cs rename to src/tests/Dragon-ISO.Engine.Tests/Pipeline/TestPatternGeneratorTests.cs index b0c7634..aef89b2 100644 --- a/src/tests/TeamsISO.Engine.Tests/Pipeline/TestPatternGeneratorTests.cs +++ b/src/tests/Dragon-ISO.Engine.Tests/Pipeline/TestPatternGeneratorTests.cs @@ -1,7 +1,7 @@ -using FluentAssertions; -using TeamsISO.Engine.Pipeline; +using FluentAssertions; +using DragonISO.Engine.Pipeline; -namespace TeamsISO.Engine.Tests.Pipeline; +namespace DragonISO.Engine.Tests.Pipeline; public class TestPatternGeneratorTests { @@ -60,8 +60,8 @@ public class TestPatternGeneratorTests var frame0 = TestPatternGenerator.Render(640, 480, frameNumber: 0, timestampTicks: 0); var frame100 = TestPatternGenerator.Render(640, 480, frameNumber: 100, timestampTicks: 0); - // frame0's sweep row is 0 (and ±4 rows). frame100's sweep row is 200. - // Compare pixel row 200 — frame100 should be brighter at that row. + // frame0's sweep row is 0 (and ±4 rows). frame100's sweep row is 200. + // Compare pixel row 200 — frame100 should be brighter at that row. var px0 = frame0.Pixels.Span; var px100 = frame100.Pixels.Span; var row = 200;