diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index bd9288450bc..e31cdcb723c 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -704,6 +704,41 @@ namespace winrt::TerminalApp::implementation } } + // Method Description: + // - Implements the F7 handler (per GH#638) + // Return value: + // - whether F7 was handled + bool AppLogic::OnF7Pressed() + { + if (_root) + { + // Manually bubble the OnF7Pressed event up through the focus tree. + auto xamlRoot{ _root->XamlRoot() }; + auto focusedObject{ Windows::UI::Xaml::Input::FocusManager::GetFocusedElement(xamlRoot) }; + do + { + if (auto f7Listener{ focusedObject.try_as() }) + { + if (f7Listener.OnF7Pressed()) + { + return true; + } + // otherwise, keep walking. bubble the event manually. + } + + if (auto focusedElement{ focusedObject.try_as() }) + { + focusedObject = focusedElement.Parent(); + } + else + { + break; // we hit a non-FE object, stop bubbling. + } + } while (focusedObject); + } + return false; + } + // Method Description: // - Used to tell the app that the 'X' button has been clicked and // the user wants to close the app. We kick off the close warning diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index b97b1bb160c..4b92afd370a 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -38,6 +38,7 @@ namespace winrt::TerminalApp::implementation hstring Title(); void TitlebarClicked(); + bool OnF7Pressed(); void WindowCloseButtonClicked(); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index 39bfb38e5a2..8acce21c50c 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -3,6 +3,7 @@ import "../TerminalPage.idl"; import "../ShortcutActionDispatch.idl"; +import "../IF7Listener.idl"; namespace TerminalApp { @@ -12,7 +13,7 @@ namespace TerminalApp MaximizedMode, }; - [default_interface] runtimeclass AppLogic { + [default_interface] runtimeclass AppLogic: IF7Listener { AppLogic(); // For your own sanity, it's better to do setup outside the ctor. diff --git a/src/cascadia/TerminalApp/IF7Listener.idl b/src/cascadia/TerminalApp/IF7Listener.idl new file mode 100644 index 00000000000..8529320be21 --- /dev/null +++ b/src/cascadia/TerminalApp/IF7Listener.idl @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace TerminalApp +{ + // C++/winrt makes it difficult to share this idl between two projects, + // Instead, we just pin the uuid and include it in both TermControl and App + // If you update this one, please update the one in TerminalControl\TermControl.idl + // If you change this interface, please update the guid. + // If you press F7 and get a runtime error, go make sure both copies are the same. + [uuid("339e1a87-5315-4da6-96f0-565549b6472b")] + interface IF7Listener + { + Boolean OnF7Pressed(); + } +} diff --git a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj index 0f29447982b..11b64756677 100644 --- a/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj +++ b/src/cascadia/TerminalApp/lib/TerminalAppLib.vcxproj @@ -186,6 +186,7 @@ + ../App.xaml diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index df35e490fe0..68389fbd93d 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -654,6 +654,38 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation e.Handled(handled); } + // Method Description: + // - Manually generate an F7 event into the key bindings or terminal. + // This is required as part of GH#638. + // Return value: + // - Whether F7 was handled. + bool TermControl::OnF7Pressed() + { + bool handled{ false }; + auto bindings{ _settings.KeyBindings() }; + + const auto modifiers{ _GetPressedModifierKeys() }; + + if (bindings) + { + handled = bindings.TryKeyChord({ + modifiers.IsCtrlPressed(), + modifiers.IsAltPressed(), + modifiers.IsShiftPressed(), + VK_F7, + }); + } + + if (!handled) + { + // _TrySendKeyEvent pretends it didn't handle F7 for some unknown reason. + (void)_TrySendKeyEvent(VK_F7, 0, modifiers); + handled = true; + } + + return handled; + } + void TermControl::_KeyDownHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, Input::KeyRoutedEventArgs const& e) { diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 0fcc63cde4d..11161e9d52f 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -81,6 +81,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void CreateSearchBoxControl(); + bool OnF7Pressed(); + ~TermControl(); Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer(); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 75d5895b02c..0a01a329121 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -7,6 +7,17 @@ namespace Microsoft.Terminal.TerminalControl delegate void FontSizeChangedEventArgs(Int32 width, Int32 height, Boolean isInitialChange); delegate void ScrollPositionChangedEventArgs(Int32 viewTop, Int32 viewHeight, Int32 bufferLength); + // C++/winrt makes it difficult to share this idl between two projects, + // Instead, we just pin the uuid and include it in both TermControl and App + // If you update this one, please update TerminalApp\IF7Listener.idl. + // If you change this interface, please update the guid. + // If you press F7 and get a runtime error, go make sure both copies are the same. + [uuid("339e1a87-5315-4da6-96f0-565549b6472b")] + interface IF7Listener + { + Boolean OnF7Pressed(); + } + runtimeclass CopyToClipboardEventArgs { String Text { get; }; @@ -19,7 +30,7 @@ namespace Microsoft.Terminal.TerminalControl void HandleClipboardData(String data); } - [default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl + [default_interface] runtimeclass TermControl : Windows.UI.Xaml.Controls.UserControl, IF7Listener { TermControl(); TermControl(Microsoft.Terminal.Settings.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection); diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index eae4aba9529..2959b22220e 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -63,6 +63,15 @@ AppHost::~AppHost() _app = nullptr; } +bool AppHost::OnF7Pressed() +{ + if (_logic) + { + return _logic.OnF7Pressed(); + } + return false; +} + // Method Description: // - Retrieve any commandline args passed on the commandline, and pass them to // the app logic for processing. diff --git a/src/cascadia/WindowsTerminal/AppHost.h b/src/cascadia/WindowsTerminal/AppHost.h index f65669dca5b..90b65b21c24 100644 --- a/src/cascadia/WindowsTerminal/AppHost.h +++ b/src/cascadia/WindowsTerminal/AppHost.h @@ -17,6 +17,7 @@ class AppHost void AppTitleChanged(const winrt::Windows::Foundation::IInspectable& sender, winrt::hstring newTitle); void LastTabClosed(const winrt::Windows::Foundation::IInspectable& sender, const winrt::TerminalApp::LastTabClosedEventArgs& args); void Initialize(); + bool OnF7Pressed(); private: bool _useNonClientArea; diff --git a/src/cascadia/WindowsTerminal/main.cpp b/src/cascadia/WindowsTerminal/main.cpp index f8fcb2c7cef..26ce23be95f 100644 --- a/src/cascadia/WindowsTerminal/main.cpp +++ b/src/cascadia/WindowsTerminal/main.cpp @@ -73,6 +73,11 @@ static void EnsureNativeArchitecture() } } +static bool _messageIsF7Keypress(const MSG& message) +{ + return (message.message == WM_KEYDOWN || message.message == WM_SYSKEYDOWN) && message.wParam == VK_F7; +} + int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) { TraceLoggingRegister(g_hWindowsTerminalProvider); @@ -115,6 +120,23 @@ int __stdcall wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int) while (GetMessage(&message, nullptr, 0, 0)) { + // GH#638 (Pressing F7 brings up both the history AND a caret browsing message) + // The Xaml input stack doesn't allow an application to suppress the "caret browsing" + // dialog experience triggered when you press F7. Official recommendation from the Xaml + // team is to catch F7 before we hand it off. + // AppLogic contains an ad-hoc implementation of event bubbling for a runtime classes + // implementing a custom IF7Listener interface. + // If the recipient of IF7Listener::OnF7Pressed suggests that the F7 press has, in fact, + // been handled we can discard the message before we even translate it. + if (_messageIsF7Keypress(message)) + { + if (host.OnF7Pressed()) + { + // The application consumed the F7. Don't let Xaml get it. + continue; + } + } + TranslateMessage(&message); DispatchMessage(&message); }