Skip to content

Commit

Permalink
During Alt+Numpad composition, stash keys in case we bail out (#17774)
Browse files Browse the repository at this point in the history
We were erroneously eating Alt followed by VK_ADD. This change makes
sure we cache key presses and releases that happen once a numpad
composition is active so that we can send them when you release Alt.

Right now, we only send them when you release Alt after composing Alt
and VK_ADD (entering hex mode) and only if you haven't inserted an
actual hex numpad code. This does mean that `Alt VK_ADD 0 0 H I` will
result in an input of "+hi". That... seems like a small price to pay for
Alt VK_ADD working again.

Closes #17762
  • Loading branch information
DHowett committed Aug 23, 2024
1 parent 6dd9c46 commit e006f75
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 33 deletions.
84 changes: 51 additions & 33 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1547,11 +1547,24 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto encoding = s.encoding;
wchar_t buf[4]{};
size_t buf_len = 0;
bool handled = true;

if (encoding == AltNumpadEncoding::Unicode)
{
// UTF-32 -> UTF-16
if (s.accumulator <= 0xffff)
if (s.accumulator == 0)
{
// If the user pressed Alt + VK_ADD, then released Alt, they probably didn't intend to insert a numpad character at all.
// Send any accumulated key events instead.
for (auto&& e : _altNumpadState.cachedKeyEvents)
{
handled = handled && _TrySendKeyEvent(e.vkey, e.scanCode, e.modifiers, e.keyDown);
}
// Send the alt keyup we are currently processing
handled = handled && _TrySendKeyEvent(vkey, scanCode, modifiers, keyDown);
// do not accumulate into the buffer
}
else if (s.accumulator <= 0xffff)
{
buf[buf_len++] = static_cast<uint16_t>(s.accumulator);
}
Expand Down Expand Up @@ -1595,7 +1608,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}

s = {};
return true;
return handled;
}
// As a continuation of the above, this handles the key-down case.
if (modifiers.IsAltPressed())
Expand All @@ -1609,53 +1622,58 @@ namespace winrt::Microsoft::Terminal::Control::implementation
SCROLLLOCK_ON |
CAPSLOCK_ON;

if (keyDown && (modifiers.Value() & ~permittedModifiers) == 0)
if ((modifiers.Value() & ~permittedModifiers) == 0)
{
auto& s = _altNumpadState;

if (vkey == VK_ADD)
{
// Alt '+' <number> is used to input Unicode code points.
// Every time you press + it resets the entire state
// in the original OS implementation as well.
s.encoding = AltNumpadEncoding::Unicode;
s.accumulator = 0;
s.active = true;
}
else if (vkey == VK_NUMPAD0 && s.encoding == AltNumpadEncoding::OEM && s.accumulator == 0)
{
// Alt '0' <number> is used to input ANSI code points.
// Otherwise, they're OEM codepoints.
s.encoding = AltNumpadEncoding::ANSI;
s.active = true;
}
else
if (keyDown)
{
// Otherwise, append the pressed key to the accumulator.
const uint32_t base = s.encoding == AltNumpadEncoding::Unicode ? 16 : 10;
uint32_t add = 0xffffff;

if (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9)
if (vkey == VK_ADD)
{
add = vkey - VK_NUMPAD0;
// Alt '+' <number> is used to input Unicode code points.
// Every time you press + it resets the entire state
// in the original OS implementation as well.
s.encoding = AltNumpadEncoding::Unicode;
s.accumulator = 0;
s.active = true;
}
else if (vkey >= 'A' && vkey <= 'F')
else if (vkey == VK_NUMPAD0 && s.encoding == AltNumpadEncoding::OEM && s.accumulator == 0)
{
add = vkey - 'A' + 10;
// Alt '0' <number> is used to input ANSI code points.
// Otherwise, they're OEM codepoints.
s.encoding = AltNumpadEncoding::ANSI;
s.active = true;
}

// Pressing Alt + <not a number> should not activate the Alt+Numpad input, however.
if (add < base)
else
{
s.accumulator = std::min(s.accumulator * base + add, 0x10FFFFu);
s.active = true;
// Otherwise, append the pressed key to the accumulator.
const uint32_t base = s.encoding == AltNumpadEncoding::Unicode ? 16 : 10;
uint32_t add = 0xffffff;

if (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9)
{
add = vkey - VK_NUMPAD0;
}
else if (vkey >= 'A' && vkey <= 'F')
{
add = vkey - 'A' + 10;
}

// Pressing Alt + <not a number> should not activate the Alt+Numpad input, however.
if (add < base)
{
s.accumulator = std::min(s.accumulator * base + add, 0x10FFFFu);
s.active = true;
}
}
}

// If someone pressed Alt + <not a number>, we'll skip the early
// return and send the Alt key combination as per usual.
if (s.active)
{
// Cache it in case we have to emit it after alt is released
_altNumpadState.cachedKeyEvents.emplace_back(vkey, scanCode, modifiers, keyDown);
return true;
}

Expand Down
8 changes: 8 additions & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,20 @@ namespace winrt::Microsoft::Terminal::Control::implementation
};
struct AltNumpadState
{
struct CachedKey
{
WORD vkey;
WORD scanCode;
::Microsoft::Terminal::Core::ControlKeyStates modifiers;
bool keyDown;
};
AltNumpadEncoding encoding = AltNumpadEncoding::OEM;
uint32_t accumulator = 0;
// Checking for accumulator != 0 to see if we have an ongoing Alt+Numpad composition is insufficient.
// The state can be active, while the accumulator is 0, if the user pressed Alt+Numpad0 which enabled
// the OEM encoding mode (= active), and then pressed Numpad0 again (= accumulator is still 0).
bool active = false;
til::small_vector<CachedKey, 4> cachedKeyEvents;
};
AltNumpadState _altNumpadState;

Expand Down

0 comments on commit e006f75

Please sign in to comment.