diff --git a/src/TeamsISO.App/App.xaml.cs b/src/TeamsISO.App/App.xaml.cs
index 25ca90c..fe0aa29 100644
--- a/src/TeamsISO.App/App.xaml.cs
+++ b/src/TeamsISO.App/App.xaml.cs
@@ -221,6 +221,42 @@ public partial class App : Application
await _viewModel.InitializeAsync(CancellationToken.None);
+ // Auto-launch Teams in the background if the operator has opted in.
+ // Combined with AutoHideTeamsWindows this gives the "I only see
+ // TeamsISO" experience — Teams runs but never appears on screen,
+ // and all interaction routes through the IN-CALL bar + participants
+ // DataGrid. Fire-and-forget so a slow Teams launch doesn't delay
+ // TeamsISO's window from appearing.
+ if (_viewModel.Settings.LaunchTeamsOnStartup && !Services.TeamsLauncher.IsRunning())
+ {
+ _ = Task.Run(() =>
+ {
+ try
+ {
+ if (Services.TeamsLauncher.TryLaunch(out var launchError))
+ {
+ if (_viewModel.Settings.AutoHideTeamsWindows)
+ _ = Services.TeamsLauncher.AutoHideAfterLaunchAsync();
+ }
+ else
+ {
+ logger.LogWarning("Auto-launch Teams on startup failed: {Error}", launchError);
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogWarning(ex, "Auto-launch Teams on startup threw");
+ }
+ });
+ }
+ else if (_viewModel.Settings.AutoHideTeamsWindows && Services.TeamsLauncher.IsRunning())
+ {
+ // Teams is already up from a previous session. If auto-hide is
+ // on, hide it now so the operator's "I only see TeamsISO" rule
+ // applies even when Teams was launched externally.
+ _ = Services.TeamsLauncher.AutoHideAfterLaunchAsync();
+ }
+
// Background update check, throttled to once per 24h. Fire-and-forget
// so a slow / offline update server never delays startup. Surfaces a
// banner via UpdateBanner if newer; failures just log.
diff --git a/src/TeamsISO.App/MainWindow.xaml b/src/TeamsISO.App/MainWindow.xaml
index d6792d7..52eaf20 100644
--- a/src/TeamsISO.App/MainWindow.xaml
+++ b/src/TeamsISO.App/MainWindow.xaml
@@ -1251,6 +1251,20 @@
Margin="0,12,0,0"
ToolTip="When checked, minimizing the window hides it and shows a tray icon. Useful for long unattended shows. Double-click the tray icon to restore."/>
+
+
+
+
+
+ /// 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
+ /// 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 —
+ /// failure to hide is harmless (user just sees Teams briefly).
+ ///
+ public static Task AutoHideAfterLaunchAsync(TimeSpan? timeout = null, CancellationToken ct = default)
+ {
+ var deadline = DateTime.UtcNow + (timeout ?? TimeSpan.FromSeconds(15));
+ return Task.Run(async () =>
+ {
+ try
+ {
+ var hiddenAny = false;
+ while (!ct.IsCancellationRequested && DateTime.UtcNow < deadline)
+ {
+ // Poll for visible windows. Each iteration may catch new
+ // 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.
+ var hidden = HideWindows();
+ if (hidden > 0)
+ {
+ hiddenAny = true;
+ // Settling delay: after we hide windows, wait a beat
+ // before polling again so we don't busy-loop while
+ // Teams' window manager catches up.
+ await Task.Delay(750, ct).ConfigureAwait(false);
+ }
+ else if (hiddenAny)
+ {
+ // We hid at least once; if the next poll finds
+ // nothing, Teams has settled. Bail early.
+ return;
+ }
+ else
+ {
+ // Teams hasn't materialized yet; keep waiting.
+ await Task.Delay(250, ct).ConfigureAwait(false);
+ }
+ }
+ }
+ catch (OperationCanceledException) { /* expected on cancel */ }
+ catch { /* defensive — auto-hide is best-effort, never breaks the app */ }
+ }, ct);
+ }
+
// ────────────────────────────────────────────────────────────────────
// Keyboard-shortcut forwarding (PostMessage path).
//
diff --git a/src/TeamsISO.App/Services/UIPreferences.cs b/src/TeamsISO.App/Services/UIPreferences.cs
index 0861919..55b3b66 100644
--- a/src/TeamsISO.App/Services/UIPreferences.cs
+++ b/src/TeamsISO.App/Services/UIPreferences.cs
@@ -39,7 +39,13 @@ public static class UIPreferences
bool AutoDisableOnDeparture = false,
SortMode ParticipantSort = SortMode.JoinOrder,
bool MinimizeToTray = false,
- bool ControlSurfaceLanReachable = 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
+ // 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);
public static Prefs Load()
{
diff --git a/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs b/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs
index 000d23d..9d0f7f8 100644
--- a/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs
+++ b/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs
@@ -34,6 +34,8 @@ public sealed class GlobalSettingsViewModel : ObservableObject
private UIPreferences.SortMode _participantSort = UIPreferences.SortMode.JoinOrder;
private bool _minimizeToTray;
private bool _controlSurfaceLanReachable;
+ private bool _launchTeamsOnStartup;
+ private bool _autoHideTeamsWindows;
public GlobalSettingsViewModel(IIsoController controller, ToastViewModel? toast = null)
{
@@ -59,6 +61,8 @@ public sealed class GlobalSettingsViewModel : ObservableObject
_participantSort = uiPrefs.ParticipantSort;
_minimizeToTray = uiPrefs.MinimizeToTray;
_controlSurfaceLanReachable = uiPrefs.ControlSurfaceLanReachable;
+ _launchTeamsOnStartup = uiPrefs.LaunchTeamsOnStartup;
+ _autoHideTeamsWindows = uiPrefs.AutoHideTeamsWindows;
// Bring the auto-apply flag in from the presets store so the checkbox
// reflects the user's prior choice when the settings panel opens.
@@ -222,7 +226,41 @@ public sealed class GlobalSettingsViewModel : ObservableObject
AutoDisableOnDeparture: _autoDisableOnDeparture,
ParticipantSort: _participantSort,
MinimizeToTray: _minimizeToTray,
- ControlSurfaceLanReachable: _controlSurfaceLanReachable));
+ ControlSurfaceLanReachable: _controlSurfaceLanReachable,
+ LaunchTeamsOnStartup: _launchTeamsOnStartup,
+ AutoHideTeamsWindows: _autoHideTeamsWindows));
+
+ ///
+ /// Auto-launch the Microsoft Teams desktop client when TeamsISO starts.
+ /// Paired with gives the operator a
+ /// "TeamsISO is the only window I see" experience — Teams runs in the
+ /// background, all interaction happens through the participants DataGrid
+ /// + IN-CALL bar.
+ ///
+ public bool LaunchTeamsOnStartup
+ {
+ get => _launchTeamsOnStartup;
+ set
+ {
+ if (SetField(ref _launchTeamsOnStartup, value)) PersistUiPrefs();
+ }
+ }
+
+ ///
+ /// Auto-hide Teams' top-level windows as soon as they materialize after
+ /// a launch (whether triggered via ,
+ /// the rail button, or the eye-toggle). Runs a brief background poll
+ /// that calls TeamsLauncher.HideWindows every ~250ms for up to
+ /// 15 seconds, catching splash + main + follow-up panels.
+ ///
+ public bool AutoHideTeamsWindows
+ {
+ get => _autoHideTeamsWindows;
+ set
+ {
+ if (SetField(ref _autoHideTeamsWindows, value)) PersistUiPrefs();
+ }
+ }
///
/// Record each newly-enabled ISO's normalized output to disk under