Tunnel F7 keypresses directly into special handlers in TermControl (#4807)
The Xaml input stack doesn't allow an application to suppress the "caret browsing" dialog experience triggered when you press F7. The official recommendation from the Xaml team is to catch F7 before we hand it off. This commit introduces a special F7 handler and an ad-hoc implementation of event bubbling. Runtime classes implementing a custom IF7Listener interface are considered during a modified focus parent walk to determine who can handle F7 specifically. If the recipient control handles F7, we suppress the message completely. This event bubbler has some minor issues -- the search box will not be able to receive F7 because its parent control implements the handler. Since search is already mostly a text box, it doesn't _need_ special caret browsing functionality as far as I can tell. TermControl implements its OnF7Pressed handler by synthesizing a keybindings event and an event to feed into Terminal Core directly. It's not possible to create a synthetic KeyPressRoutedEvent; if it were, I would have just popped one into the traditional input queue. :) Fixes #638.
This commit is contained in:
parent
a32956d620
commit
a3d68d2b21
|
@ -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<IF7Listener>() })
|
||||
{
|
||||
if (f7Listener.OnF7Pressed())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// otherwise, keep walking. bubble the event manually.
|
||||
}
|
||||
|
||||
if (auto focusedElement{ focusedObject.try_as<Windows::UI::Xaml::FrameworkElement>() })
|
||||
{
|
||||
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
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
hstring Title();
|
||||
void TitlebarClicked();
|
||||
bool OnF7Pressed();
|
||||
|
||||
void WindowCloseButtonClicked();
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
16
src/cascadia/TerminalApp/IF7Listener.idl
Normal file
16
src/cascadia/TerminalApp/IF7Listener.idl
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -186,6 +186,7 @@
|
|||
<ItemGroup>
|
||||
<!-- If you add idl files here, make sure to include their implementation's
|
||||
header in TerminalApp.vcxproj (as well as in this file) -->
|
||||
<Midl Include="../IF7Listener.idl" />
|
||||
<Midl Include="../App.idl">
|
||||
<DependentUpon>../App.xaml</DependentUpon>
|
||||
</Midl>
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -81,6 +81,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
|||
|
||||
void CreateSearchBoxControl();
|
||||
|
||||
bool OnF7Pressed();
|
||||
|
||||
~TermControl();
|
||||
|
||||
Windows::UI::Xaml::Automation::Peers::AutomationPeer OnCreateAutomationPeer();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -17,6 +17,7 @@ public:
|
|||
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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue