Compare commits
No commits in common. "d8186c5eb8409c2f33d1b6448d506d031364e6ca" and "e020d1c2ac63390a604257ec1aa01486d76ed6c7" have entirely different histories.
d8186c5eb8
...
e020d1c2ac
7 changed files with 43 additions and 304 deletions
|
|
@ -221,42 +221,6 @@ public partial class App : Application
|
||||||
|
|
||||||
await _viewModel.InitializeAsync(CancellationToken.None);
|
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
|
// Background update check, throttled to once per 24h. Fire-and-forget
|
||||||
// so a slow / offline update server never delays startup. Surfaces a
|
// so a slow / offline update server never delays startup. Surfaces a
|
||||||
// banner via UpdateBanner if newer; failures just log.
|
// banner via UpdateBanner if newer; failures just log.
|
||||||
|
|
|
||||||
|
|
@ -123,18 +123,11 @@
|
||||||
</Grid>
|
</Grid>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<!-- Nav: Launch Teams. Click = launch / surface; right-click = stop.
|
<!-- Nav: Launch Teams. Subprocess-launches the MS Teams desktop client. -->
|
||||||
The previous "left-click toggles" behavior ambushed operators
|
|
||||||
who'd hidden Teams' windows via the eye toggle next door —
|
|
||||||
they'd hit Launch expecting Teams to come back and instead
|
|
||||||
get a "Close all Teams windows now?" dialog. Now click is
|
|
||||||
always idempotent-progressive (running but hidden → show;
|
|
||||||
not running → launch). Right-click for stop. -->
|
|
||||||
<Button DockPanel.Dock="Top"
|
<Button DockPanel.Dock="Top"
|
||||||
Style="{StaticResource Wd.Button.RailIcon}"
|
Style="{StaticResource Wd.Button.RailIcon}"
|
||||||
Click="OnLaunchTeamsClick"
|
Click="OnLaunchTeamsClick"
|
||||||
MouseRightButtonUp="OnLaunchTeamsRightClick"
|
ToolTip="Launch Microsoft Teams">
|
||||||
ToolTip="Launch Microsoft Teams (or surface its window). Right-click to stop Teams.">
|
|
||||||
<!-- Stylized 'video meeting' camera icon -->
|
<!-- Stylized 'video meeting' camera icon -->
|
||||||
<Path Data="M 4,8 L 16,8 L 16,16 L 4,16 Z M 16,11 L 22,8 L 22,16 L 16,13 Z"
|
<Path Data="M 4,8 L 16,8 L 16,16 L 4,16 Z M 16,11 L 22,8 L 22,16 L 16,13 Z"
|
||||||
Stroke="{DynamicResource Wd.Text.Secondary}"
|
Stroke="{DynamicResource Wd.Text.Secondary}"
|
||||||
|
|
@ -1251,20 +1244,6 @@
|
||||||
Margin="0,12,0,0"
|
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."/>
|
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."/>
|
||||||
|
|
||||||
<!-- Phase E.1/E.2 "I only see TeamsISO" pair. With both ticked,
|
|
||||||
the operator launches TeamsISO and never sees the Teams UI —
|
|
||||||
Teams runs in the background and all interaction routes
|
|
||||||
through the IN-CALL bar + participants DataGrid. -->
|
|
||||||
<CheckBox Content="Launch Microsoft Teams on TeamsISO startup"
|
|
||||||
IsChecked="{Binding Settings.LaunchTeamsOnStartup}"
|
|
||||||
Margin="0,16,0,0"
|
|
||||||
ToolTip="When checked, TeamsISO will auto-launch Microsoft Teams in the background each time it starts. Combine with 'Auto-hide Teams windows' for the 'I only see TeamsISO' experience."/>
|
|
||||||
|
|
||||||
<CheckBox Content="Auto-hide Teams windows when launched"
|
|
||||||
IsChecked="{Binding Settings.AutoHideTeamsWindows}"
|
|
||||||
Margin="0,8,0,0"
|
|
||||||
ToolTip="When checked, Teams' windows are hidden as soon as they materialize after launch. Use the eye-icon button in the rail to restore them when needed. Drives Teams via the IN-CALL bar (mute / camera / share / leave / marker) and the participants DataGrid for ISO routing."/>
|
|
||||||
|
|
||||||
<Separator Margin="0,16,0,8"/>
|
<Separator Margin="0,16,0,8"/>
|
||||||
|
|
||||||
<CheckBox Content="Record ISOs to disk"
|
<CheckBox Content="Record ISOs to disk"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
|
||||||
using System.Windows.Shapes;
|
using System.Windows.Shapes;
|
||||||
using TeamsISO.App.Services;
|
using TeamsISO.App.Services;
|
||||||
using TeamsISO.App.ViewModels;
|
using TeamsISO.App.ViewModels;
|
||||||
|
|
@ -114,89 +113,43 @@ public partial class MainWindow : Window
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Three-state click behavior matching operator intuition:
|
/// Toggle behavior: if Teams is already running, ask to stop it; otherwise
|
||||||
/// 1. Teams not running → launch it via TeamsLauncher's fallback chain.
|
/// launch via TeamsLauncher's fallback chain. First step toward the
|
||||||
/// 2. Teams running but its windows are hidden (we toggled them off, OR
|
/// Embedded-Teams roadmap (Phase E.1).
|
||||||
/// it launched into the tray) → restore the windows + foreground them.
|
|
||||||
/// This is the case the previous "ask to stop" dialog was ambushing —
|
|
||||||
/// operators don't think of a hidden Teams as "running and ready to
|
|
||||||
/// stop", they think of it as "I clicked Launch and nothing happened".
|
|
||||||
/// 3. Teams running with visible windows → bring the most recent one to
|
|
||||||
/// the foreground. (Stopping Teams is now a right-click action;
|
|
||||||
/// see OnLaunchTeamsRightClick.)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void OnLaunchTeamsClick(object sender, RoutedEventArgs e)
|
private void OnLaunchTeamsClick(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var toast = (DataContext as MainViewModel)?.Toast;
|
if (TeamsLauncher.IsRunning())
|
||||||
|
|
||||||
if (!TeamsLauncher.IsRunning())
|
|
||||||
{
|
{
|
||||||
if (!TeamsLauncher.TryLaunch(out var error))
|
var confirm = MessageBox.Show(
|
||||||
|
"Microsoft Teams is currently running.\n\nClose all Teams windows now?",
|
||||||
|
"TeamsISO — Stop Teams",
|
||||||
|
MessageBoxButton.YesNo,
|
||||||
|
MessageBoxImage.Question);
|
||||||
|
if (confirm != MessageBoxResult.Yes) return;
|
||||||
|
|
||||||
|
var asked = TeamsLauncher.StopAll();
|
||||||
|
if (TeamsLauncher.IsRunning())
|
||||||
{
|
{
|
||||||
MessageBox.Show(
|
MessageBox.Show(
|
||||||
$"Could not launch Microsoft Teams.\n\n{error}",
|
asked == 0
|
||||||
"TeamsISO — Launch Teams",
|
? "No Teams windows responded to close."
|
||||||
|
: $"Sent close to {asked} Teams window(s); some may still be exiting.",
|
||||||
|
"TeamsISO — Stop Teams",
|
||||||
MessageBoxButton.OK,
|
MessageBoxButton.OK,
|
||||||
MessageBoxImage.Warning);
|
MessageBoxImage.Information);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var autoHide = (DataContext as MainViewModel)?.Settings.AutoHideTeamsWindows ?? false;
|
|
||||||
toast?.Show(autoHide
|
|
||||||
? "Launching Microsoft Teams (will hide windows automatically)…"
|
|
||||||
: "Launching Microsoft Teams…");
|
|
||||||
if (autoHide)
|
|
||||||
{
|
|
||||||
_ = TeamsLauncher.AutoHideAfterLaunchAsync();
|
|
||||||
_teamsWindowsHidden = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_teamsWindowsHidden = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Teams is running. Always try to restore + foreground its window —
|
if (!TeamsLauncher.TryLaunch(out var error))
|
||||||
// if windows are already visible, ShowWindows is a SetForegroundWindow
|
|
||||||
// no-op besides bringing them forward; if they were hidden by our
|
|
||||||
// own toggle, this is the operator's intuitive "show me Teams" path.
|
|
||||||
var shown = TeamsLauncher.ShowWindows();
|
|
||||||
_teamsWindowsHidden = false;
|
|
||||||
toast?.Show(shown > 0
|
|
||||||
? $"Teams is already running — surfaced {shown} window(s)"
|
|
||||||
: "Teams is running but has no visible windows yet");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Right-click on the rail Launch button asks to stop Teams. Split out
|
|
||||||
/// from the left-click so a normal click is "open / surface" rather than
|
|
||||||
/// the previous "open OR ambush you with a stop dialog".
|
|
||||||
/// </summary>
|
|
||||||
private void OnLaunchTeamsRightClick(object sender, MouseButtonEventArgs e)
|
|
||||||
{
|
|
||||||
if (!TeamsLauncher.IsRunning()) return;
|
|
||||||
|
|
||||||
var confirm = MessageBox.Show(
|
|
||||||
"Microsoft Teams is currently running.\n\nClose all Teams windows now?",
|
|
||||||
"TeamsISO — Stop Teams",
|
|
||||||
MessageBoxButton.YesNo,
|
|
||||||
MessageBoxImage.Question);
|
|
||||||
if (confirm != MessageBoxResult.Yes) return;
|
|
||||||
|
|
||||||
var asked = TeamsLauncher.StopAll();
|
|
||||||
if (TeamsLauncher.IsRunning())
|
|
||||||
{
|
{
|
||||||
MessageBox.Show(
|
MessageBox.Show(
|
||||||
asked == 0
|
$"Could not launch Microsoft Teams.\n\n{error}",
|
||||||
? "No Teams windows responded to close."
|
"TeamsISO — Launch Teams",
|
||||||
: $"Sent close to {asked} Teams window(s); some may still be exiting.",
|
|
||||||
"TeamsISO — Stop Teams",
|
|
||||||
MessageBoxButton.OK,
|
MessageBoxButton.OK,
|
||||||
MessageBoxImage.Information);
|
MessageBoxImage.Warning);
|
||||||
}
|
}
|
||||||
e.Handled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -44,39 +44,21 @@ public static class TeamsLauncher
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Launches Teams. Returns true if a launch was started successfully (the
|
/// Launches Teams. Returns true if a launch was started successfully (the
|
||||||
/// process may take a few seconds to actually appear). False if every
|
/// process may take a few seconds to actually appear). False if every
|
||||||
/// fallback path failed; <paramref name="errorMessage"/> includes the
|
/// fallback path failed.
|
||||||
/// reasons each attempt was rejected so the operator can see why.
|
|
||||||
///
|
|
||||||
/// Path order matters:
|
|
||||||
/// 1. <c>ms-teams:</c> URI — new Teams (MSTeams AppX) registers this
|
|
||||||
/// handler at install. Activates through the AppX shell so the
|
|
||||||
/// stub <c>ms-teams.exe</c> in WindowsApps gets the right context.
|
|
||||||
/// 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.
|
|
||||||
/// We deliberately DON'T try the bare <c>ms-teams.exe</c> WindowsApps
|
|
||||||
/// path: it's a 0-byte AppX placeholder that fails silently when invoked
|
|
||||||
/// without AppX activation context. Looked plausible, never worked.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static bool TryLaunch(out string? errorMessage)
|
public static bool TryLaunch(out string? errorMessage)
|
||||||
{
|
{
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
var attempts = new List<string>();
|
|
||||||
|
|
||||||
// Path 1: URI scheme. The shell handler picks the registered Teams
|
// Path 1: URI scheme. The shell handler picks whichever Teams client
|
||||||
// (new MSTeams takes priority on modern Windows). UseShellExecute=true
|
// is registered (new MSTeams.exe takes priority on modern Windows).
|
||||||
// is required — Win32 Process creation can't open URIs directly.
|
if (TryStart("ms-teams:", useShell: true)) return true;
|
||||||
if (TryStart("ms-teams:", useShell: true, out var err1)) return true;
|
|
||||||
attempts.Add($"ms-teams: URI → {err1}");
|
|
||||||
|
|
||||||
// Path 2: AppX activation via the explorer.exe shell. Modern Teams
|
// Path 2: new Teams' WindowsApps shim.
|
||||||
// ships as MSTeams_8wekyb3d8bbwe; if other code on the box has
|
var newTeams = Path.Combine(
|
||||||
// clobbered the URI registration, this still works because it goes
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
||||||
// through the AppsFolder verb the OS itself uses for Start menu launches.
|
"Microsoft", "WindowsApps", "ms-teams.exe");
|
||||||
if (TryStart("explorer.exe", false, out var err2,
|
if (File.Exists(newTeams) && TryStart(newTeams, useShell: false)) return true;
|
||||||
arguments: "shell:appsFolder\\MSTeams_8wekyb3d8bbwe!MSTeams"))
|
|
||||||
return true;
|
|
||||||
attempts.Add($"AppsFolder shell → {err2}");
|
|
||||||
|
|
||||||
// Path 3: classic Teams Update.exe with --processStart hands off to
|
// Path 3: classic Teams Update.exe with --processStart hands off to
|
||||||
// the actual Teams.exe via Squirrel.
|
// the actual Teams.exe via Squirrel.
|
||||||
|
|
@ -98,17 +80,11 @@ public static class TeamsLauncher
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
attempts.Add($"classic Update.exe → {ex.Message}");
|
errorMessage = ex.Message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
attempts.Add($"classic Update.exe → not found at {classicUpdater}");
|
|
||||||
}
|
|
||||||
|
|
||||||
errorMessage = "No Microsoft Teams installation could be launched. " +
|
errorMessage ??= "No Microsoft Teams installation was found. Install Teams from https://www.microsoft.com/microsoft-teams and try again.";
|
||||||
"Install Teams from https://www.microsoft.com/microsoft-teams and try again.\n\n" +
|
|
||||||
"Attempts:\n • " + string.Join("\n • ", attempts);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,9 +123,8 @@ public static class TeamsLauncher
|
||||||
return asked;
|
return asked;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryStart(string target, bool useShell, out string error, string? arguments = null)
|
private static bool TryStart(string target, bool useShell)
|
||||||
{
|
{
|
||||||
error = string.Empty;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var info = new ProcessStartInfo
|
var info = new ProcessStartInfo
|
||||||
|
|
@ -158,13 +133,11 @@ public static class TeamsLauncher
|
||||||
UseShellExecute = useShell,
|
UseShellExecute = useShell,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
};
|
};
|
||||||
if (arguments is not null) info.Arguments = arguments;
|
|
||||||
Process.Start(info);
|
Process.Start(info);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch
|
||||||
{
|
{
|
||||||
error = ex.Message;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -251,60 +224,6 @@ public static class TeamsLauncher
|
||||||
return windows.Count;
|
return windows.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fire-and-forget background watcher that polls every 250ms for up to
|
|
||||||
/// <paramref name="timeout"/> 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).
|
|
||||||
/// </summary>
|
|
||||||
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).
|
// Keyboard-shortcut forwarding (PostMessage path).
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -39,13 +39,7 @@ public static class UIPreferences
|
||||||
bool AutoDisableOnDeparture = false,
|
bool AutoDisableOnDeparture = false,
|
||||||
SortMode ParticipantSort = SortMode.JoinOrder,
|
SortMode ParticipantSort = SortMode.JoinOrder,
|
||||||
bool MinimizeToTray = false,
|
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()
|
public static Prefs Load()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -538,25 +538,11 @@
|
||||||
<Setter Property="Foreground" Value="{StaticResource Wd.Text.Primary}"/>
|
<Setter Property="Foreground" Value="{StaticResource Wd.Text.Primary}"/>
|
||||||
<Setter Property="Cursor" Value="Hand"/>
|
<Setter Property="Cursor" Value="Hand"/>
|
||||||
<Setter Property="Padding" Value="10,0,0,0"/>
|
<Setter Property="Padding" Value="10,0,0,0"/>
|
||||||
<!--
|
|
||||||
Stretch + a Grid template lets long Content strings wrap rather
|
|
||||||
than get clipped by the 380px settings panel. Without this, the
|
|
||||||
previous StackPanel template let the ContentPresenter grow
|
|
||||||
unbounded horizontally and the parent's clip-to-bounds chopped
|
|
||||||
off the right side.
|
|
||||||
-->
|
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Left"/>
|
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<Setter.Value>
|
<Setter.Value>
|
||||||
<ControlTemplate TargetType="CheckBox">
|
<ControlTemplate TargetType="CheckBox">
|
||||||
<Grid HorizontalAlignment="Stretch">
|
<StackPanel Orientation="Horizontal">
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="Auto"/>
|
|
||||||
<ColumnDefinition Width="*"/>
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Border x:Name="Box"
|
<Border x:Name="Box"
|
||||||
Grid.Column="0"
|
|
||||||
Width="18" Height="18"
|
Width="18" Height="18"
|
||||||
BorderBrush="{StaticResource Wd.BorderStrong}"
|
BorderBrush="{StaticResource Wd.BorderStrong}"
|
||||||
BorderThickness="1"
|
BorderThickness="1"
|
||||||
|
|
@ -570,27 +556,9 @@
|
||||||
StrokeEndLineCap="Round"
|
StrokeEndLineCap="Round"
|
||||||
Visibility="Collapsed"/>
|
Visibility="Collapsed"/>
|
||||||
</Border>
|
</Border>
|
||||||
<!--
|
<ContentPresenter Margin="{TemplateBinding Padding}"
|
||||||
ContentPresenter resources inject a TextBlock
|
VerticalAlignment="Center"/>
|
||||||
style with TextWrapping=Wrap. When CheckBox's
|
</StackPanel>
|
||||||
Content is a plain string (the common case here),
|
|
||||||
WPF wraps it in an auto-generated TextBlock; the
|
|
||||||
resource lookup applies our wrapping default so
|
|
||||||
long labels flow onto multiple lines instead of
|
|
||||||
being clipped at the column edge.
|
|
||||||
-->
|
|
||||||
<ContentPresenter Grid.Column="1"
|
|
||||||
Margin="{TemplateBinding Padding}"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
RecognizesAccessKey="True">
|
|
||||||
<ContentPresenter.Resources>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Setter Property="TextWrapping" Value="Wrap"/>
|
|
||||||
</Style>
|
|
||||||
</ContentPresenter.Resources>
|
|
||||||
</ContentPresenter>
|
|
||||||
</Grid>
|
|
||||||
<ControlTemplate.Triggers>
|
<ControlTemplate.Triggers>
|
||||||
<Trigger Property="IsChecked" Value="True">
|
<Trigger Property="IsChecked" Value="True">
|
||||||
<Setter TargetName="Box" Property="Background" Value="{StaticResource Wd.Accent.Cyan}"/>
|
<Setter TargetName="Box" Property="Background" Value="{StaticResource Wd.Accent.Cyan}"/>
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,6 @@ public sealed class GlobalSettingsViewModel : ObservableObject
|
||||||
private UIPreferences.SortMode _participantSort = UIPreferences.SortMode.JoinOrder;
|
private UIPreferences.SortMode _participantSort = UIPreferences.SortMode.JoinOrder;
|
||||||
private bool _minimizeToTray;
|
private bool _minimizeToTray;
|
||||||
private bool _controlSurfaceLanReachable;
|
private bool _controlSurfaceLanReachable;
|
||||||
private bool _launchTeamsOnStartup;
|
|
||||||
private bool _autoHideTeamsWindows;
|
|
||||||
|
|
||||||
public GlobalSettingsViewModel(IIsoController controller, ToastViewModel? toast = null)
|
public GlobalSettingsViewModel(IIsoController controller, ToastViewModel? toast = null)
|
||||||
{
|
{
|
||||||
|
|
@ -61,8 +59,6 @@ public sealed class GlobalSettingsViewModel : ObservableObject
|
||||||
_participantSort = uiPrefs.ParticipantSort;
|
_participantSort = uiPrefs.ParticipantSort;
|
||||||
_minimizeToTray = uiPrefs.MinimizeToTray;
|
_minimizeToTray = uiPrefs.MinimizeToTray;
|
||||||
_controlSurfaceLanReachable = uiPrefs.ControlSurfaceLanReachable;
|
_controlSurfaceLanReachable = uiPrefs.ControlSurfaceLanReachable;
|
||||||
_launchTeamsOnStartup = uiPrefs.LaunchTeamsOnStartup;
|
|
||||||
_autoHideTeamsWindows = uiPrefs.AutoHideTeamsWindows;
|
|
||||||
|
|
||||||
// Bring the auto-apply flag in from the presets store so the checkbox
|
// Bring the auto-apply flag in from the presets store so the checkbox
|
||||||
// reflects the user's prior choice when the settings panel opens.
|
// reflects the user's prior choice when the settings panel opens.
|
||||||
|
|
@ -226,41 +222,7 @@ public sealed class GlobalSettingsViewModel : ObservableObject
|
||||||
AutoDisableOnDeparture: _autoDisableOnDeparture,
|
AutoDisableOnDeparture: _autoDisableOnDeparture,
|
||||||
ParticipantSort: _participantSort,
|
ParticipantSort: _participantSort,
|
||||||
MinimizeToTray: _minimizeToTray,
|
MinimizeToTray: _minimizeToTray,
|
||||||
ControlSurfaceLanReachable: _controlSurfaceLanReachable,
|
ControlSurfaceLanReachable: _controlSurfaceLanReachable));
|
||||||
LaunchTeamsOnStartup: _launchTeamsOnStartup,
|
|
||||||
AutoHideTeamsWindows: _autoHideTeamsWindows));
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Auto-launch the Microsoft Teams desktop client when TeamsISO starts.
|
|
||||||
/// Paired with <see cref="AutoHideTeamsWindows"/> 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.
|
|
||||||
/// </summary>
|
|
||||||
public bool LaunchTeamsOnStartup
|
|
||||||
{
|
|
||||||
get => _launchTeamsOnStartup;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetField(ref _launchTeamsOnStartup, value)) PersistUiPrefs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Auto-hide Teams' top-level windows as soon as they materialize after
|
|
||||||
/// a launch (whether triggered via <see cref="LaunchTeamsOnStartup"/>,
|
|
||||||
/// the rail button, or the eye-toggle). Runs a brief background poll
|
|
||||||
/// that calls <c>TeamsLauncher.HideWindows</c> every ~250ms for up to
|
|
||||||
/// 15 seconds, catching splash + main + follow-up panels.
|
|
||||||
/// </summary>
|
|
||||||
public bool AutoHideTeamsWindows
|
|
||||||
{
|
|
||||||
get => _autoHideTeamsWindows;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (SetField(ref _autoHideTeamsWindows, value)) PersistUiPrefs();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Record each newly-enabled ISO's normalized output to disk under
|
/// Record each newly-enabled ISO's normalized output to disk under
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue