diff --git a/src/TeamsISO.App/App.xaml b/src/TeamsISO.App/App.xaml
index 8be975d..abef318 100644
--- a/src/TeamsISO.App/App.xaml
+++ b/src/TeamsISO.App/App.xaml
@@ -1,5 +1,11 @@
-
+
+
+
+
+
+
+
diff --git a/src/TeamsISO.App/MainWindow.xaml b/src/TeamsISO.App/MainWindow.xaml
index 461b7ff..dda478a 100644
--- a/src/TeamsISO.App/MainWindow.xaml
+++ b/src/TeamsISO.App/MainWindow.xaml
@@ -3,60 +3,134 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TeamsISO.App.ViewModels"
xmlns:conv="clr-namespace:TeamsISO.App.Converters"
- Title="TeamsISO" Height="700" Width="1100"
- Background="#202225" Foreground="#E8E8E8">
+ Title="TeamsISO"
+ Height="760" Width="1180"
+ MinHeight="600" MinWidth="960"
+ Background="{DynamicResource Stone.Canvas}"
+ UseLayoutRounding="True"
+ TextOptions.TextFormattingMode="Ideal"
+ TextOptions.TextRenderingMode="ClearType">
-
-
-
-
-
-
+
+
-
+
-
+
-
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
@@ -66,7 +140,10 @@
-
+
@@ -76,7 +153,10 @@
-
+
@@ -86,7 +166,10 @@
-
+
@@ -96,63 +179,187 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/TeamsISO.App/Themes/StoneTheme.xaml b/src/TeamsISO.App/Themes/StoneTheme.xaml
new file mode 100644
index 0000000..12eaae9
--- /dev/null
+++ b/src/TeamsISO.App/Themes/StoneTheme.xaml
@@ -0,0 +1,491 @@
+
+
+
+
+
+ 4
+ 8
+ 12
+ 16
+ 24
+ 32
+
+
+ 4
+ 6
+ 8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Segoe UI Variable Display, Segoe UI, Manrope, sans-serif
+ Cascadia Mono, Consolas, monospace
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs b/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs
index e92d320..449b9c7 100644
--- a/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs
+++ b/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs
@@ -4,7 +4,8 @@ using TeamsISO.Engine.Domain;
namespace TeamsISO.App.ViewModels;
///
-/// Bindings for the global settings panel: framerate, resolution, aspect, audio.
+/// Bindings for the global settings panel: framerate, resolution, aspect, audio,
+/// NDI groups (discovery + output), and the participant-list hide-Local toggle.
///
public sealed class GlobalSettingsViewModel : ObservableObject
{
@@ -13,6 +14,9 @@ public sealed class GlobalSettingsViewModel : ObservableObject
private TargetResolution _resolution;
private AspectMode _aspect;
private AudioMode _audio;
+ private string _discoveryGroups;
+ private string _outputGroups;
+ private bool _hideLocalSelf = true;
public GlobalSettingsViewModel(IIsoController controller)
{
@@ -22,6 +26,11 @@ public sealed class GlobalSettingsViewModel : ObservableObject
_resolution = current.Resolution;
_aspect = current.Aspect;
_audio = current.Audio;
+
+ var groups = controller.GroupSettings;
+ _discoveryGroups = groups.DiscoveryGroups ?? string.Empty;
+ _outputGroups = groups.OutputGroups ?? string.Empty;
+
ApplyCommand = new AsyncRelayCommand(ApplyAsync);
}
@@ -35,11 +44,29 @@ public sealed class GlobalSettingsViewModel : ObservableObject
public AspectMode Aspect { get => _aspect; set => SetField(ref _aspect, value); }
public AudioMode Audio { get => _audio; set => SetField(ref _audio, value); }
+ /// NDI discovery group(s) — comma-separated. Empty = default (Public).
+ public string DiscoveryGroups { get => _discoveryGroups; set => SetField(ref _discoveryGroups, value); }
+
+ /// NDI output group(s) — comma-separated. Empty = default (Public).
+ public string OutputGroups { get => _outputGroups; set => SetField(ref _outputGroups, value); }
+
+ ///
+ /// 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 when filtering the list it presents.
+ ///
+ public bool HideLocalSelf { get => _hideLocalSelf; set => SetField(ref _hideLocalSelf, value); }
+
public AsyncRelayCommand ApplyCommand { get; }
- private Task ApplyAsync()
+ private async Task ApplyAsync()
{
var settings = new FrameProcessingSettings(_framerate, _resolution, _aspect, _audio);
- return _controller.SetGlobalSettingsAsync(settings, CancellationToken.None);
+ 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);
}
}
diff --git a/src/TeamsISO.App/ViewModels/MainViewModel.cs b/src/TeamsISO.App/ViewModels/MainViewModel.cs
index b7cdfae..79ecb51 100644
--- a/src/TeamsISO.App/ViewModels/MainViewModel.cs
+++ b/src/TeamsISO.App/ViewModels/MainViewModel.cs
@@ -61,8 +61,14 @@ public sealed class MainViewModel : ObservableObject, IDisposable
private void OnParticipantsChanged(IReadOnlyList incoming)
{
var seenIds = new HashSet();
+ var hideLocal = Settings.HideLocalSelf;
foreach (var p in incoming)
{
+ // The new Teams client emits a "(Local)" pseudo-participant for the user's
+ // own preview — operators rarely want it as a routable ISO. Suppress when
+ // HideLocalSelf is on (default).
+ if (hideLocal && IsLocalSelf(p)) continue;
+
seenIds.Add(p.Id);
if (_byId.TryGetValue(p.Id, out var vm))
{
@@ -76,7 +82,7 @@ public sealed class MainViewModel : ObservableObject, IDisposable
}
}
- // Remove participants no longer present
+ // Remove participants no longer present (or now hidden by the filter).
for (var i = Participants.Count - 1; i >= 0; i--)
{
var vm = Participants[i];
@@ -88,6 +94,9 @@ public sealed class MainViewModel : ObservableObject, IDisposable
}
}
+ private static bool IsLocalSelf(Participant p) =>
+ string.Equals(p.DisplayName, "(Local)", StringComparison.Ordinal);
+
public void Dispose()
{
_participantsSub.Dispose();