2026-05-08 07:19:31 -04:00
|
|
|
using System.Windows;
|
|
|
|
|
using TeamsISO.App.Services;
|
2026-05-07 11:39:46 -04:00
|
|
|
using TeamsISO.Engine.Controller;
|
|
|
|
|
using TeamsISO.Engine.Domain;
|
|
|
|
|
|
|
|
|
|
namespace TeamsISO.App.ViewModels;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
/// Bindings for the global settings panel: framerate, resolution, aspect, audio,
|
|
|
|
|
/// NDI groups (discovery + output), and the participant-list hide-Local toggle.
|
2026-05-07 11:39:46 -04:00
|
|
|
/// </summary>
|
|
|
|
|
public sealed class GlobalSettingsViewModel : ObservableObject
|
|
|
|
|
{
|
|
|
|
|
private readonly IIsoController _controller;
|
|
|
|
|
private TargetFramerate _framerate;
|
|
|
|
|
private TargetResolution _resolution;
|
|
|
|
|
private AspectMode _aspect;
|
|
|
|
|
private AudioMode _audio;
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
private string _discoveryGroups;
|
|
|
|
|
private string _outputGroups;
|
|
|
|
|
private bool _hideLocalSelf = true;
|
2026-05-07 11:39:46 -04:00
|
|
|
|
|
|
|
|
public GlobalSettingsViewModel(IIsoController controller)
|
|
|
|
|
{
|
|
|
|
|
_controller = controller;
|
|
|
|
|
var current = controller.GlobalSettings;
|
|
|
|
|
_framerate = current.Framerate;
|
|
|
|
|
_resolution = current.Resolution;
|
|
|
|
|
_aspect = current.Aspect;
|
|
|
|
|
_audio = current.Audio;
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
|
|
|
|
|
var groups = controller.GroupSettings;
|
|
|
|
|
_discoveryGroups = groups.DiscoveryGroups ?? string.Empty;
|
|
|
|
|
_outputGroups = groups.OutputGroups ?? string.Empty;
|
|
|
|
|
|
2026-05-07 11:39:46 -04:00
|
|
|
ApplyCommand = new AsyncRelayCommand(ApplyAsync);
|
2026-05-08 07:19:31 -04:00
|
|
|
ApplyTranscoderTopologyCommand = new AsyncRelayCommand(ApplyTranscoderTopologyAsync);
|
2026-05-07 11:39:46 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IEnumerable<TargetFramerate> AvailableFramerates => Enum.GetValues<TargetFramerate>();
|
|
|
|
|
public IEnumerable<TargetResolution> AvailableResolutions => Enum.GetValues<TargetResolution>();
|
|
|
|
|
public IEnumerable<AspectMode> AvailableAspectModes => Enum.GetValues<AspectMode>();
|
|
|
|
|
public IEnumerable<AudioMode> AvailableAudioModes => Enum.GetValues<AudioMode>();
|
|
|
|
|
|
|
|
|
|
public TargetFramerate Framerate { get => _framerate; set => SetField(ref _framerate, value); }
|
|
|
|
|
public TargetResolution Resolution { get => _resolution; set => SetField(ref _resolution, value); }
|
|
|
|
|
public AspectMode Aspect { get => _aspect; set => SetField(ref _aspect, value); }
|
|
|
|
|
public AudioMode Audio { get => _audio; set => SetField(ref _audio, value); }
|
|
|
|
|
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
/// <summary>NDI discovery group(s) — comma-separated. Empty = default (Public).</summary>
|
|
|
|
|
public string DiscoveryGroups { get => _discoveryGroups; set => SetField(ref _discoveryGroups, value); }
|
|
|
|
|
|
|
|
|
|
/// <summary>NDI output group(s) — comma-separated. Empty = default (Public).</summary>
|
|
|
|
|
public string OutputGroups { get => _outputGroups; set => SetField(ref _outputGroups, value); }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Hide the user's own self-preview ("(Local)") from the participants list.
|
|
|
|
|
/// On by default — operators rarely want to ISO-route their own preview.
|
|
|
|
|
/// Read by <see cref="MainViewModel"/> when filtering the list it presents.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool HideLocalSelf { get => _hideLocalSelf; set => SetField(ref _hideLocalSelf, value); }
|
|
|
|
|
|
2026-05-07 11:39:46 -04:00
|
|
|
public AsyncRelayCommand ApplyCommand { get; }
|
|
|
|
|
|
2026-05-08 07:19:31 -04:00
|
|
|
/// <summary>
|
|
|
|
|
/// One-click "set up the transcoder topology" — writes ndi-config.v1.json so all
|
|
|
|
|
/// local senders broadcast on a private group ("teamsiso-input") while local
|
|
|
|
|
/// receivers can see both that and "public", then sets the engine's discovery and
|
|
|
|
|
/// output groups to align (engine receives from the private group, emits on Public).
|
|
|
|
|
/// User has to restart Teams for the new ndi-config.v1.json to take effect there.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public AsyncRelayCommand ApplyTranscoderTopologyCommand { get; }
|
|
|
|
|
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
private async Task ApplyAsync()
|
2026-05-07 11:39:46 -04:00
|
|
|
{
|
|
|
|
|
var settings = new FrameProcessingSettings(_framerate, _resolution, _aspect, _audio);
|
feat(ui): rebuild MainWindow with Stone-theme design system
Adopts the design language from Dammyjay93/interface-design: warm Stone neutrals, accent orange (#EA580C), borders-only depth (no shadows), 8px spacing grid, all-caps section labels, mono typography for machine names and timecodes.
Themes/StoneTheme.xaml is the single source of truth for design tokens (color brushes, typography styles, spacing) plus restyled control templates (Button, TextBox, ComboBox, ComboBoxItem, CheckBox, DataGrid + DataGridColumnHeader / DataGridRow / DataGridCell, ScrollBar). MainWindow consumes the tokens and is laid out with a header (title + status pill), section-headed Settings sidebar (Output Format / NDI Network / Display), card-wrapped participant list, and a mono status footer.
Settings sidebar surfaces the new NDI group configuration (discovery + output) and a Hide-(Local) checkbox. The latter filters the user's own self-preview from the participants list at the MainViewModel layer (HideLocalSelf=true by default) so operators don't accidentally route their own preview as an ISO. Apply Changes round-trips both FrameProcessingSettings and NdiGroupSettings through the controller in one go.
2026-05-07 23:58:02 -04:00
|
|
|
await _controller.SetGlobalSettingsAsync(settings, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
var groups = new NdiGroupSettings(
|
|
|
|
|
DiscoveryGroups: string.IsNullOrWhiteSpace(_discoveryGroups) ? null : _discoveryGroups.Trim(),
|
|
|
|
|
OutputGroups: string.IsNullOrWhiteSpace(_outputGroups) ? null : _outputGroups.Trim());
|
|
|
|
|
await _controller.SetGroupSettingsAsync(groups, CancellationToken.None);
|
2026-05-07 11:39:46 -04:00
|
|
|
}
|
2026-05-08 07:19:31 -04:00
|
|
|
|
|
|
|
|
private async Task ApplyTranscoderTopologyAsync()
|
|
|
|
|
{
|
|
|
|
|
// 1. Update the machine-wide NDI config so Teams' raw broadcasts go to the
|
|
|
|
|
// private group instead of polluting Public.
|
|
|
|
|
var result = NdiAccessManagerConfig.ApplyTranscoderTopology();
|
|
|
|
|
if (!result.Success)
|
|
|
|
|
{
|
|
|
|
|
MessageBox.Show(
|
|
|
|
|
$"Could not write NDI Access Manager config.\n\n{result.ErrorMessage}\n\nPath: {result.ConfigPath}",
|
|
|
|
|
"TeamsISO — Apply transcoder topology",
|
|
|
|
|
MessageBoxButton.OK,
|
|
|
|
|
MessageBoxImage.Warning);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 2. Update the engine: receive only from the private group, emit on Public.
|
|
|
|
|
var ourGroups = new NdiGroupSettings(
|
|
|
|
|
DiscoveryGroups: NdiAccessManagerConfig.TranscoderInputGroup,
|
|
|
|
|
OutputGroups: "public");
|
|
|
|
|
await _controller.SetGroupSettingsAsync(ourGroups, CancellationToken.None);
|
|
|
|
|
|
|
|
|
|
// 3. Reflect the new values in the bound text boxes.
|
|
|
|
|
DiscoveryGroups = NdiAccessManagerConfig.TranscoderInputGroup;
|
|
|
|
|
OutputGroups = "public";
|
|
|
|
|
|
|
|
|
|
var backupNote = result.BackupPath is null
|
|
|
|
|
? "No prior NDI config existed; a fresh one was created."
|
|
|
|
|
: $"A backup of your prior NDI config was saved to:\n{result.BackupPath}";
|
|
|
|
|
|
|
|
|
|
MessageBox.Show(
|
|
|
|
|
"Transcoder topology applied. ✓\n\n" +
|
|
|
|
|
"• Local senders (Teams, etc.) will broadcast on group 'teamsiso-input'.\n" +
|
|
|
|
|
"• Local receivers will see both 'public' and 'teamsiso-input'.\n" +
|
|
|
|
|
"• TeamsISO will discover from 'teamsiso-input' and re-emit on 'public'.\n\n" +
|
|
|
|
|
"RESTART Microsoft Teams for the new NDI config to take effect there.\n\n" +
|
|
|
|
|
backupNote,
|
|
|
|
|
"TeamsISO — Apply transcoder topology",
|
|
|
|
|
MessageBoxButton.OK,
|
|
|
|
|
MessageBoxImage.Information);
|
|
|
|
|
}
|
2026-05-07 11:39:46 -04:00
|
|
|
}
|