From 0c4881bc9cd2685c31026bf5d7eee9165b2da9b5 Mon Sep 17 00:00:00 2001 From: Phil Scott Date: Tue, 23 Jun 2026 13:27:17 -0400 Subject: [PATCH] Fixes xbox controller crashing the Terminal (#20234) ## Summary of the Pull Request Fixes a denial of service by a toddler with an Xbox controller wanting to play Forza. They can impatiently press up and down and close out the terminal window. Issue is upstream in WinUI, but I think this gathers things into a state where Terminal can at least recover by adding a `GettingFocus` handler on the `TabViewItem` that cancels that gamepad focus move before it reaches the framework handler. ## References and Relevant Issues Issue #19671 and #20089 ## Validation Steps Performed Handed a controller to my toddler while working in terminal. I also banged on the keys myself. ## PR Checklist Closes #19671 Closes #20089 --- .github/actions/spelling/allow/microsoft.txt | 1 + src/cascadia/TerminalApp/Tab.cpp | 34 ++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt index f43408388b..782754a012 100644 --- a/.github/actions/spelling/allow/microsoft.txt +++ b/.github/actions/spelling/allow/microsoft.txt @@ -78,4 +78,5 @@ wtl wtt wttlog Xamarin +XBOX xfgcheck diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index da4d0896ae..2ca1bb1d77 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -162,6 +162,40 @@ namespace winrt::TerminalApp::implementation } }); + // BODGY: Work around a fail-fast crash in WinUI 2's TabView. When a + // game controller (e.g. an XBOX controller's d-pad/stick) moves focus + // Up or Down between two TabViewItems, MUX's TabView::OnListViewGettingFocus + // runs a GameController-only code path that calls + // DispatcherQueue.TryEnqueue, which fails with E_INVALIDARG. That + // unhandled error inside a GettingFocus callback makes XAML fail-fast, + // and the whole window vanishes. See + // microsoft-ui-xaml/blob/840c6cd4ac9d16d9aa99b24f0bd43997fe21bef8/dev/TabView/TabView.cpp#L246 + // + // This GettingFocus handler is on the TabViewItem, which is closer to + // the focus source than the TabViewListView that MUX's handler lives + // on, so it runs first. Marking the event Handled - and cancelling the + // focus move, which is exactly what MUX itself does for the keyboard + // case - preempts MUX's broken handler. Keyboard navigation is left + // untouched; MUX handles that path safely. + TabViewItem().GettingFocus([](auto&&, const winrt::WUX::Input::GettingFocusEventArgs& args) { + const auto direction{ args.Direction() }; + if (direction != winrt::WUX::Input::FocusNavigationDirection::Up && + direction != winrt::WUX::Input::FocusNavigationDirection::Down) + { + return; + } + if (args.InputDevice() != winrt::WUX::Input::FocusInputDeviceKind::GameController) + { + return; + } + if (args.OldFocusedElement().try_as() && + args.NewFocusedElement().try_as()) + { + args.Cancel(true); + args.Handled(true); + } + }); + UpdateTitle(); _RecalculateAndApplyTabColor(); }