Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace IInputEvent with INPUT_RECORD #15673

Merged

Conversation

lhecker
Copy link
Member

@lhecker lhecker commented Jul 7, 2023

IInputEvent makes adding Unicode support to InputBuffer more
difficult than necessary as the abstract class makes downcasting
as well as copying quite verbose. I found that using INPUT_RECORDs
directly leads to a significantly simplified implementation.

In addition, this commit fixes at least one bug: The previous approach
to detect the null key via DoActiveModifierKeysMatch didn't work.
As it compared the modifier keys as a bitset with == it failed to
match whenever the numpad key was set, which it usually is.

Validation Steps Performed

  • Unit and feature tests are ✅

@lhecker lhecker added the Area-CodeHealth Issues related to code cleanliness, linting, rules, warnings, errors, static analysis, etc. label Jul 7, 2023
@lhecker lhecker changed the title Clean up InputBuffer coalescing and preprocessing Replace IInputEvent with INPUT_RECORD Jul 7, 2023
@@ -86,7 +86,6 @@ class KeyPressTests
expectedRecord.Event.KeyEvent.uChar.UnicodeChar = 0x0;
expectedRecord.Event.KeyEvent.bKeyDown = true;
expectedRecord.Event.KeyEvent.dwControlKeyState = ENHANCED_KEY;
expectedRecord.Event.KeyEvent.dwControlKeyState |= (GetKeyState(VK_NUMLOCK) & KEY_STATE_TOGGLED) ? NUMLOCK_ON : 0;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was quite puzzling. On line 78 above we call TurnOffModifierKeys(hwnd) and then assume that conhost still thinks that the numlock is set? This test should've always failed on every keyboard where numlock was on.

Comment on lines +149 to +152
if (LOBYTE(zeroKey) == Event.Event.KeyEvent.wVirtualKeyCode &&
WI_IsAnyFlagSet(Event.Event.KeyEvent.dwControlKeyState, ALT_PRESSED) == WI_IsFlagSet(zeroKey, 0x400) &&
WI_IsAnyFlagSet(Event.Event.KeyEvent.dwControlKeyState, CTRL_PRESSED) == WI_IsFlagSet(zeroKey, 0x200) &&
WI_IsAnyFlagSet(Event.Event.KeyEvent.dwControlKeyState, SHIFT_PRESSED) == WI_IsFlagSet(zeroKey, 0x100))
Copy link
Member Author

@lhecker lhecker Jul 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what replaces the std::unordered_set code. I've decided against using clever bitmasking or against converting dwControlKeyState into a Win32 compatible bitset first, because I felt like writing it this way is easier to understand (and review). The constants at the end are kind of magic, but it should be easy to see how they correspond to the ALT_PRESSED, CTRL_PRESSED, SHIFT_PRESSED constants.

Comment on lines +281 to +285
[[nodiscard]] friend constexpr small_vector_iterator operator+(const difference_type off, small_vector_iterator next) noexcept
{
next += off;
return next;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was missing. It's required for til::small_vector to fulfill the Range concept and be implicitly convertible to a std::span.

@@ -166,64 +120,35 @@ std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::Synthes
// alt + numpad
// Note:
// - will throw exception on error
std::deque<std::unique_ptr<KeyEvent>> Microsoft::Console::Interactivity::SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage)
void Microsoft::Console::Interactivity::SynthesizeNumpadEvents(const wchar_t wch, const unsigned int codepage, InputEventQueue& keyEvents)
Copy link
Member Author

@lhecker lhecker Jul 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An out-parameter allows us to directly append to an existing buffer instead of returning a buffer and then appending that buffer to another buffer. This function is only called from a single place too (the clipboard handler).

@lhecker lhecker force-pushed the dev/lhecker/8000-WriteConsoleInputA branch from 80fcebc to 42ac9b7 Compare July 12, 2023 17:21
@lhecker lhecker force-pushed the dev/lhecker/8000-IInputEvent-exorcism branch from 1caa29f to 12038ca Compare July 12, 2023 17:24
@@ -45,9 +45,27 @@
</Expand>
</Type>

<Type Name="KeyEvent">
<DisplayString Condition="_keyDown">{{↓ wch:{_charData} mod:{_activeModifierKeys} repeat:{_repeatCount} vk:{_virtualKeyCode} vsc:{_virtualScanCode}}</DisplayString>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i did think the arrows were clever... :P

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll restore the arrows before this merges. 🙂

@lhecker lhecker marked this pull request as draft July 13, 2023 18:34
Base automatically changed from dev/lhecker/8000-WriteConsoleInputA to main July 18, 2023 18:16
@lhecker lhecker force-pushed the dev/lhecker/8000-IInputEvent-exorcism branch from 12038ca to fcbbe08 Compare July 27, 2023 15:31
@lhecker
Copy link
Member Author

lhecker commented Jul 27, 2023

@DHowett @zadjii-msft The PR is now ready. Despite the size, I feel like it's actually a surprisingly light read. 🙂

@lhecker lhecker marked this pull request as ready for review July 27, 2023 15:38
Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

24/54, checkpoint

@@ -4,6 +4,7 @@
#include "precomp.h"
#include "terminalInput.hpp"

#include "../types/inc/IInputEvent.hpp"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IInputEvent lives?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sort of... It now contains helper functions to generate INPUT_RECORDs. They all start with "Synthesize".

{
return MakeUnhandled();
}

auto keyEvent = *static_cast<const KeyEvent* const>(pInEvent);
auto keyEvent = event.Event.KeyEvent;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm would we benefit from an auto&?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We modify the keyEvent unfortunately to decoalesce the repeated key events.

@lhecker lhecker added the Issue-Bug It either shouldn't be doing this or needs an investigation. label Jul 28, 2023
Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I think I'm cool with this. I don't think any of my comments are blocking

src/host/ft_host/Message_KeyPressTests.cpp Show resolved Hide resolved
const bool ansiMode,
const bool cursorApplicationMode,
const bool keypadApplicationMode) noexcept
{
if (ansiMode)
{
if (keyEvent.IsCursorKey())
if (keyEvent.wVirtualKeyCode >= VK_END && keyEvent.wVirtualKeyCode <= VK_DOWN)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably use a comment - IsCursorKey was definitely more informative

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... I felt like it was fine given that the code inside the if condition mentions s_cursorKeysApplicationMapping and s_cursorKeysNormalMapping. I've got an idea how to improve it though! 🙂

Copy link
Member Author

@lhecker lhecker Aug 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW I thought a little bit about how to construct the function "optimally" and I came up with this:

static std::span<const TermKeyMap> _getKeyMapping(const KEY_EVENT_RECORD& keyEvent, const bool ansiMode, const bool cursorApplicationMode, const bool keypadApplicationMode) noexcept
    static constexpr uint8_t stage1[2][2][2][2]{
              // ansiMode | cursorApplicationMode | keypadApplicationMode
        0, 1, // f        | f                     | f
        0, 1, // f        | f                     | true
        0, 1, // f        | true                  | f
        0, 1, // f        | true                  | true
        2, 4, // true     | f                     | f
        3, 4, // true     | f                     | true
        2, 5, // true     | true                  | f
        3, 5, // true     | true                  | true
    };
    static constexpr std::span<const TermKeyMap> stage2[]{
        s_keypadVt52Mapping,
        s_cursorKeysVt52Mapping,
        s_keypadNumericMapping,
        s_keypadApplicationMapping,
        s_cursorKeysNormalMapping,
        s_cursorKeysApplicationMapping,
    };

    // Cursor keys: VK_END, VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWN
    const auto isCursorKey = keyEvent.wVirtualKeyCode >= VK_END && keyEvent.wVirtualKeyCode <= VK_DOWN;
    const auto idx = stage1[ansiMode][cursorApplicationMode][keypadApplicationMode][isCursorKey];
    return stage2[idx];
}

Would that be too much / too confusing? I personally do like seeing that 2-3, 2-3 and 4-4, 5-5 symmetry, but that's a pretty strong matter of taste. 😅

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I TOTALLY MISSED THIS

I LOVE IT

src/terminal/input/terminalInput.cpp Outdated Show resolved Hide resolved
@lhecker lhecker requested a review from DHowett August 7, 2023 12:20
Copy link
Member

@DHowett DHowett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do this. I'm so excited to not be making individual 8-byte allocations ALL OVER THE PLACE and PASSING POINTERS AROUND.

Thank you for this necessary cleanup :D

@@ -63,9 +63,6 @@
<ClCompile Include="VtRendererTests.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<Clcompile Include="..\..\types\IInputEventStreams.cpp">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

YIKES including a cpp file right out of another project lol

}

return CodepointWidth::Narrow;
return (0x1100 <= wch && wch <= 0x115f) || // From Unicode 9.0, Hangul Choseong is wide
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cute, this reformatting is much better

@@ -16,35 +12,29 @@ static constexpr WORD leftShiftScanCode = 0x2A;
// Routine Description:
// - naively determines the width of a UCS2 encoded wchar (with caveats noted above)
#pragma warning(suppress : 4505) // this function will be deleted if numpad events are disabled
static CodepointWidth GetQuickCharWidthLegacyForNumpadEventSynthesis(const wchar_t wch) noexcept
static bool GetQuickCharWidthLegacyForNumpadEventSynthesis(const wchar_t wch) noexcept
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i might recommend renaming this since it doesn't get a width any longer

const auto shiftSet = WI_IsFlagSet(modifierState, 1);
const auto ctrlSet = WI_IsFlagSet(modifierState, 2);
const auto altSet = WI_IsFlagSet(modifierState, 4);
const auto altGrSet = WI_AreAllFlagsSet(modifierState, 4 | 2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh this doesn't care about left ctrl or right alt specifically. good to know!


// add modifier key up event
// handle yucky alt-gr keys
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha

@lhecker lhecker added the AutoMerge Marked for automatic merge by the bot when requirements are met label Aug 10, 2023
@microsoft-github-policy-service microsoft-github-policy-service bot merged commit 5b44476 into main Aug 11, 2023
16 of 17 checks passed
@microsoft-github-policy-service microsoft-github-policy-service bot deleted the dev/lhecker/8000-IInputEvent-exorcism branch August 11, 2023 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-CodeHealth Issues related to code cleanliness, linting, rules, warnings, errors, static analysis, etc. AutoMerge Marked for automatic merge by the bot when requirements are met Issue-Bug It either shouldn't be doing this or needs an investigation. zBugBash-Consider
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants