2026-05-07 11:39:46 -04:00
|
|
|
|
using TeamsISO.Engine.Controller;
|
|
|
|
|
|
using TeamsISO.Engine.Domain;
|
2026-05-08 00:52:44 -04:00
|
|
|
|
using IsoHealthStats = TeamsISO.Engine.Domain.IsoHealthStats;
|
2026-05-07 11:39:46 -04:00
|
|
|
|
|
|
|
|
|
|
namespace TeamsISO.App.ViewModels;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Per-row view model for a participant in the participant list.
|
|
|
|
|
|
/// Wraps a domain <see cref="Participant"/> and exposes ISO toggle and naming commands.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public sealed class ParticipantViewModel : ObservableObject
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IIsoController _controller;
|
|
|
|
|
|
private Participant _participant;
|
|
|
|
|
|
private bool _isEnabled;
|
|
|
|
|
|
private bool _isProcessing;
|
|
|
|
|
|
private string _customName;
|
|
|
|
|
|
|
|
|
|
|
|
public ParticipantViewModel(IIsoController controller, Participant participant)
|
|
|
|
|
|
{
|
|
|
|
|
|
_controller = controller;
|
|
|
|
|
|
_participant = participant;
|
|
|
|
|
|
_customName = string.Empty;
|
|
|
|
|
|
ToggleIsoCommand = new AsyncRelayCommand(ToggleIsoAsync, () => !_isProcessing);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Guid Id => _participant.Id;
|
|
|
|
|
|
public string DisplayName => _participant.DisplayName;
|
|
|
|
|
|
public string SourceMachine => _participant.CurrentSource?.MachineName ?? "(disconnected)";
|
|
|
|
|
|
public string SourceFullName => _participant.CurrentSource?.FullName ?? "(disconnected)";
|
|
|
|
|
|
public bool IsOnline => _participant.CurrentSource is not null;
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsEnabled
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _isEnabled;
|
|
|
|
|
|
set => SetField(ref _isEnabled, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 00:52:44 -04:00
|
|
|
|
private long _framesIn;
|
|
|
|
|
|
private long _framesOut;
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
|
private long _framesDropped;
|
2026-05-08 00:52:44 -04:00
|
|
|
|
private string _incomingResolution = "—";
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
|
private string _incomingFps = "—";
|
2026-05-08 00:52:44 -04:00
|
|
|
|
|
|
|
|
|
|
/// <summary>Number of frames the receiver has captured so far.</summary>
|
2026-05-08 00:55:57 -04:00
|
|
|
|
public long FramesIn { get => _framesIn; set => SetField(ref _framesIn, value); }
|
2026-05-08 00:52:44 -04:00
|
|
|
|
|
|
|
|
|
|
/// <summary>Number of frames the sender has emitted so far.</summary>
|
2026-05-08 00:55:57 -04:00
|
|
|
|
public long FramesOut { get => _framesOut; set => SetField(ref _framesOut, value); }
|
2026-05-08 00:52:44 -04:00
|
|
|
|
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
|
/// <summary>Frames dropped by the closest-frame strategy when the receiver outpaces the processor.</summary>
|
|
|
|
|
|
public long FramesDropped { get => _framesDropped; set => SetField(ref _framesDropped, value); }
|
|
|
|
|
|
|
2026-05-08 00:52:44 -04:00
|
|
|
|
/// <summary>Source resolution as "WxH", or em-dash when no frames have been seen yet.</summary>
|
2026-05-08 00:55:57 -04:00
|
|
|
|
public string IncomingResolution { get => _incomingResolution; set => SetField(ref _incomingResolution, value); }
|
2026-05-08 00:52:44 -04:00
|
|
|
|
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Live incoming framerate as "59.94 fps", or em-dash when fewer than 2 frames
|
|
|
|
|
|
/// have been observed since the pipeline started. Computed in the engine via a
|
|
|
|
|
|
/// 30-frame moving window.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public string IncomingFps { get => _incomingFps; set => SetField(ref _incomingFps, value); }
|
|
|
|
|
|
|
2026-05-08 00:52:44 -04:00
|
|
|
|
/// <summary>Updates the live stats display from a controller-side snapshot.</summary>
|
|
|
|
|
|
public void UpdateStats(IsoHealthStats stats)
|
|
|
|
|
|
{
|
|
|
|
|
|
FramesIn = stats.FramesIn;
|
|
|
|
|
|
FramesOut = stats.FramesOut;
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
|
FramesDropped = stats.FramesDropped;
|
2026-05-08 00:52:44 -04:00
|
|
|
|
IncomingResolution = stats.IncomingWidth > 0 && stats.IncomingHeight > 0
|
|
|
|
|
|
? $"{stats.IncomingWidth}×{stats.IncomingHeight}"
|
|
|
|
|
|
: "—";
|
feat: app icon, FPS, drops counter, --version, About dialog, Stop Teams toggle
Six related polish items, all building on tonight's groundwork.
1. App icon: teamsiso.ico generated from dragon-mark.png at 7 sizes (16-256), wired as ApplicationIcon in the WPF csproj, MainWindow.Icon, AboutWindow.Icon, and ARPPRODUCTICON in the WiX MSI. Taskbar / window / Add-Remove-Programs all show the dragon mark now.
2. Running incoming FPS: ring buffer of last 30 frame timestamps in IsoPipeline; ComputeFps() returns moving-average rate. Surfaced on IsoHealthStats.IncomingFps and shown in the Source column of the participants DataGrid as 'WxH · 59.94 fps'. Resets cleanly on every supervisor restart.
3. Drops counter: FrameProcessor.Stats already aggregated FramesDropped (closest-frame strategy when the receiver outpaces the processor) and FramesDuplicated; just plumbed _liveProcessor through IsoPipeline so GetStats() can read them. Exposed in the Live column under the in/out counters as a coral-tinted 'drop N'.
4. Console --version flag: prints engine version (with embedded git SHA), .NET version, OS, NDI runtime banner, expected prefix, exit-code legend, plus a wilddragon.net link. Useful for support tickets.
5. About dialog: chromeless modal with the dragon mark + version / .NET / OS / NDI runtime fields and a link to wilddragon.net. Triggered by clicking the rail logo.
6. Teams launcher Stop toggle: TeamsLauncher gains IsRunning() and StopAll(). The rail's Teams button now toggles — if Teams is up, ask to close all Teams windows via WM_CLOSE; otherwise launch as before. Confirms before stopping so we don't kill the user's call mid-transition.
Tests: 74/74 unit + 9/9 NDI integration green throughout. MSI builds clean and now embeds the dragon icon for ARP.
2026-05-08 13:50:19 -04:00
|
|
|
|
IncomingFps = stats.IncomingFps > 0
|
|
|
|
|
|
? $"{stats.IncomingFps:0.0} fps"
|
|
|
|
|
|
: "—";
|
2026-05-08 00:52:44 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 11:39:46 -04:00
|
|
|
|
public bool IsProcessing
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _isProcessing;
|
|
|
|
|
|
private set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (SetField(ref _isProcessing, value))
|
|
|
|
|
|
ToggleIsoCommand.RaiseCanExecuteChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string CustomName
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _customName;
|
|
|
|
|
|
set => SetField(ref _customName, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public AsyncRelayCommand ToggleIsoCommand { get; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Refreshes the underlying participant data (called when the controller emits an updated list).</summary>
|
|
|
|
|
|
public void Update(Participant updated)
|
|
|
|
|
|
{
|
|
|
|
|
|
_participant = updated;
|
|
|
|
|
|
OnPropertyChanged(nameof(DisplayName));
|
|
|
|
|
|
OnPropertyChanged(nameof(SourceMachine));
|
|
|
|
|
|
OnPropertyChanged(nameof(SourceFullName));
|
|
|
|
|
|
OnPropertyChanged(nameof(IsOnline));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task ToggleIsoAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
IsProcessing = true;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsEnabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _controller.DisableIsoAsync(Id, CancellationToken.None);
|
|
|
|
|
|
IsEnabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
await _controller.EnableIsoAsync(
|
|
|
|
|
|
Id,
|
|
|
|
|
|
string.IsNullOrWhiteSpace(_customName) ? null : _customName,
|
|
|
|
|
|
CancellationToken.None);
|
|
|
|
|
|
IsEnabled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
IsProcessing = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|