diff --git a/src/TeamsISO.App/MainWindow.xaml b/src/TeamsISO.App/MainWindow.xaml index 5ab756a..d1fc1c0 100644 --- a/src/TeamsISO.App/MainWindow.xaml +++ b/src/TeamsISO.App/MainWindow.xaml @@ -1343,6 +1343,25 @@ Margin="0,8,0,0" ToolTip="When checked, recording auto-starts the moment Teams transitions into a call (IN CALL pill goes cyan) and auto-stops when the call ends. Removes the manual Record toggle step from unattended-show workflows."/> + + + + + + + + + + + diff --git a/src/TeamsISO.App/TeamsEmbedWindow.xaml.cs b/src/TeamsISO.App/TeamsEmbedWindow.xaml.cs new file mode 100644 index 0000000..4e0d0db --- /dev/null +++ b/src/TeamsISO.App/TeamsEmbedWindow.xaml.cs @@ -0,0 +1,72 @@ +using System.Windows; +using System.Windows.Interop; +using TeamsISO.App.Services; + +namespace TeamsISO.App; + +/// +/// Phase E.4 experimental — hosts an embedded copy of the Teams main +/// window via SetParent. Operator opens this from Settings → DISPLAY → +/// 'Embed Teams window'. The host Border's HWND becomes Teams' parent on +/// Loaded; SizeChanged keeps Teams fitted; Closing always restores Teams +/// to a normal top-level window before we exit. +/// +/// Failsafes: +/// • If no Teams window is found at Loaded, show a friendly message +/// instead of leaving the host blank. +/// • Restore-on-close runs in a finally block so a crash mid-host +/// can't leave Teams orphaned with stripped window styles. +/// • TeamsLauncher.RestoreEmbed is idempotent — safe to call even if +/// embedding never succeeded. +/// +public partial class TeamsEmbedWindow : Window +{ + public TeamsEmbedWindow() + { + InitializeComponent(); + Loaded += OnWindowLoaded; + Closed += OnWindowClosed; + } + + private void OnWindowLoaded(object sender, RoutedEventArgs e) + { + var src = PresentationSource.FromVisual(EmbedHost) as HwndSource; + if (src is null || src.Handle == IntPtr.Zero) + { + MessageBox.Show( + "Couldn't obtain a host HWND for the embed window. " + + "Try closing and re-opening the embed window.", + "TeamsISO — embed", + MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + var w = (int)EmbedHost.ActualWidth; + var h = (int)EmbedHost.ActualHeight; + if (!TeamsLauncher.EmbedTeamsInto(src.Handle, w, h)) + { + MessageBox.Show( + "Couldn't find a Microsoft Teams window to embed. " + + "Launch Teams first (rail camera icon), then re-open this window.", + "TeamsISO — embed", + MessageBoxButton.OK, MessageBoxImage.Information); + } + } + + private void OnHostSizeChanged(object sender, SizeChangedEventArgs e) + { + // Keep Teams sized to match the host as the embed window resizes. + // No-op when nothing is embedded. + TeamsLauncher.ResizeEmbedded((int)e.NewSize.Width, (int)e.NewSize.Height); + } + + private void OnWindowClosed(object? sender, EventArgs e) + { + // ALWAYS restore Teams to top-level state when this window closes, + // even if the embed never succeeded. Idempotent. + try { TeamsLauncher.RestoreEmbed(); } + catch { /* defensive — restore is best-effort */ } + } + + private void OnClose(object sender, RoutedEventArgs e) => Close(); +} diff --git a/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs b/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs index d621f2d..aaf98b3 100644 --- a/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs +++ b/src/TeamsISO.App/ViewModels/GlobalSettingsViewModel.cs @@ -37,6 +37,7 @@ public sealed class GlobalSettingsViewModel : ObservableObject private bool _launchTeamsOnStartup; private bool _autoHideTeamsWindows; private bool _autoRecordOnCall; + private bool _embedTeamsWindow; public GlobalSettingsViewModel(IIsoController controller, ToastViewModel? toast = null) { @@ -65,6 +66,7 @@ public sealed class GlobalSettingsViewModel : ObservableObject _launchTeamsOnStartup = uiPrefs.LaunchTeamsOnStartup; _autoHideTeamsWindows = uiPrefs.AutoHideTeamsWindows; _autoRecordOnCall = uiPrefs.AutoRecordOnCall; + _embedTeamsWindow = uiPrefs.EmbedTeamsWindow; // Bring the auto-apply flag in from the presets store so the checkbox // reflects the user's prior choice when the settings panel opens. @@ -258,7 +260,8 @@ public sealed class GlobalSettingsViewModel : ObservableObject ControlSurfaceLanReachable: _controlSurfaceLanReachable, LaunchTeamsOnStartup: _launchTeamsOnStartup, AutoHideTeamsWindows: _autoHideTeamsWindows, - AutoRecordOnCall: _autoRecordOnCall)); + AutoRecordOnCall: _autoRecordOnCall, + EmbedTeamsWindow: _embedTeamsWindow)); /// /// Auto-launch the Microsoft Teams desktop client when TeamsISO starts. @@ -307,6 +310,23 @@ public sealed class GlobalSettingsViewModel : ObservableObject } } + /// + /// EXPERIMENTAL: SetParent-reparents Teams' main window into a TeamsISO- + /// owned host so Teams visually appears inside our window. WebView2 in + /// modern Teams may render weirdly after reparent — if so, untick and + /// fall back to the auto-hide flow. Polling logic in MainWindow.xaml.cs + /// applies / restores the embed; this property is just the persisted + /// toggle. + /// + public bool EmbedTeamsWindow + { + get => _embedTeamsWindow; + set + { + if (SetField(ref _embedTeamsWindow, value)) PersistUiPrefs(); + } + } + /// /// Record each newly-enabled ISO's normalized output to disk under /// . Already-running ISOs are not retroactively