using MBN_STOCK_WEBVIEW.Core.Playout; using Microsoft.UI.Windowing; using WinRT.Interop; namespace MBN_STOCK_WEBVIEW; public sealed partial class MainWindow { private AppWindow? _closeConfirmationAppWindow; private int _closeConfirmationPending; private int _closeConfirmed; private void EnsureCloseConfirmationAttached() { if (_closeConfirmationAppWindow is not null) { return; } try { var windowHandle = WindowNative.GetWindowHandle(this); var windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(windowHandle); var appWindow = AppWindow.GetFromWindowId(windowId); appWindow.Closing += OnAppWindowClosing; _closeConfirmationAppWindow = appWindow; } catch { // Root.Loaded retries the attachment after the native window has been created. } } private async void OnAppWindowClosing(AppWindow sender, AppWindowClosingEventArgs args) { if (Volatile.Read(ref _closeConfirmed) != 0) { return; } // AppWindow.Closing cannot be deferred. Cancel this close synchronously and issue a // second, explicitly confirmed close only after the modal result is known. args.Cancel = true; if (Interlocked.CompareExchange(ref _closeConfirmationPending, 1, 0) != 0) { return; } try { if (Root.XamlRoot is null) { return; } var dialog = new ContentDialog { XamlRoot = Root.XamlRoot, Title = "프로그램 종료", Content = BuildCloseConfirmationMessage(), PrimaryButtonText = "종료", CloseButtonText = "취소", DefaultButton = ContentDialogButton.Close }; var result = await dialog.ShowAsync(); if (result != ContentDialogResult.Primary) { return; } Interlocked.Exchange(ref _closeConfirmed, 1); Close(); } catch { // If the confirmation surface cannot be shown, fail closed and keep the app open. } finally { Interlocked.Exchange(ref _closeConfirmationPending, 0); } } private string BuildCloseConfirmationMessage() { var status = _playoutEngine?.Status; var hasPreparedScene = !string.IsNullOrWhiteSpace(status?.PreparedSceneName); var hasOnAirScene = !string.IsNullOrWhiteSpace(status?.OnAirSceneName); var commandPending = Volatile.Read(ref _playoutCommandInFlight) != 0 || status?.IsPlayCompletionPending == true; var outcomeUnknown = Volatile.Read(ref _browserCorrelationQuarantined) != 0 || status?.State == PlayoutConnectionState.OutcomeUnknown; if (outcomeUnknown) { return "송출 결과를 확정할 수 없는 상태입니다. PGM과 Network Monitoring을 확인한 뒤 종료하세요. " + "종료 과정에서 TAKE OUT 또는 다른 송출 명령을 자동으로 반복하지 않습니다. 그래도 종료하시겠습니까?"; } if (commandPending) { return "송출 명령 또는 callback 처리가 진행 중입니다. 결과를 확인한 뒤 종료하는 것이 안전합니다. " + "그래도 종료하시겠습니까?"; } if (hasOnAirScene || hasPreparedScene) { return "PREPARED 또는 PROGRAM 장면이 남아 있습니다. 필요한 경우 먼저 TAKE OUT을 실행하고 " + "PGM을 확인하세요. 종료 과정에서는 TAKE OUT을 자동 실행하지 않습니다. 그래도 종료하시겠습니까?"; } return "프로그램을 종료하시겠습니까?"; } private void DetachCloseConfirmation() { var appWindow = Interlocked.Exchange(ref _closeConfirmationAppWindow, null); if (appWindow is not null) { appWindow.Closing -= OnAppWindowClosing; } } }