@@ -1458,61 +1458,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
1458
1458
// - Whether the key was handled.
1459
1459
bool TermControl::OnDirectKeyEvent (const uint32_t vkey, const uint8_t scanCode, const bool down)
1460
1460
{
1461
- // Short-circuit isReadOnly check to avoid warning dialog
1462
- if (_core.IsInReadOnlyMode ())
1463
- {
1464
- return false ;
1465
- }
1466
-
1467
1461
const auto modifiers{ _GetPressedModifierKeys () };
1468
- auto handled = false ;
1469
-
1470
- if (vkey == VK_MENU && !down)
1471
- {
1472
- // Manually generate an Alt KeyUp event into the key bindings or terminal.
1473
- // This is required as part of GH#6421.
1474
- (void )_TrySendKeyEvent (VK_MENU, scanCode, modifiers, false );
1475
- handled = true ;
1476
- }
1477
- else if ((vkey == VK_F7 || vkey == VK_SPACE) && down)
1478
- {
1479
- // Manually generate an F7 event into the key bindings or terminal.
1480
- // This is required as part of GH#638.
1481
- // Or do so for alt+space; only send to terminal when explicitly unbound
1482
- // That is part of #GH7125
1483
- auto bindings{ _core.Settings ().KeyBindings () };
1484
- auto isUnbound = false ;
1485
- const KeyChord kc = {
1486
- modifiers.IsCtrlPressed (),
1487
- modifiers.IsAltPressed (),
1488
- modifiers.IsShiftPressed (),
1489
- modifiers.IsWinPressed (),
1490
- gsl::narrow_cast<WORD>(vkey),
1491
- 0
1492
- };
1493
-
1494
- if (bindings)
1495
- {
1496
- handled = bindings.TryKeyChord (kc);
1497
-
1498
- if (!handled)
1499
- {
1500
- isUnbound = bindings.IsKeyChordExplicitlyUnbound (kc);
1501
- }
1502
- }
1503
-
1504
- const auto sendToTerminal = vkey == VK_F7 || (vkey == VK_SPACE && isUnbound);
1505
-
1506
- if (!handled && sendToTerminal)
1507
- {
1508
- // _TrySendKeyEvent pretends it didn't handle F7 for some unknown reason.
1509
- (void )_TrySendKeyEvent (gsl::narrow_cast<WORD>(vkey), scanCode, modifiers, true );
1510
- // GH#6438: Note that we're _not_ sending the key up here - that'll
1511
- // get passed through XAML to our KeyUp handler normally.
1512
- handled = true ;
1513
- }
1514
- }
1515
- return handled;
1462
+ return _KeyHandler (gsl::narrow_cast<WORD>(vkey), gsl::narrow_cast<WORD>(scanCode), modifiers, down);
1516
1463
}
1517
1464
1518
1465
void TermControl::_KeyDownHandler (const winrt::Windows::Foundation::IInspectable& /* sender*/ ,
@@ -1529,13 +1476,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
1529
1476
1530
1477
void TermControl::_KeyHandler (const Input::KeyRoutedEventArgs& e, const bool keyDown)
1531
1478
{
1532
- // If the current focused element is a child element of searchbox,
1533
- // we do not send this event up to terminal
1534
- if (_searchBox && _searchBox->ContainsFocus ())
1535
- {
1536
- return ;
1537
- }
1538
-
1539
1479
const auto keyStatus = e.KeyStatus ();
1540
1480
const auto vkey = gsl::narrow_cast<WORD>(e.OriginalKey ());
1541
1481
const auto scanCode = gsl::narrow_cast<WORD>(keyStatus.ScanCode );
@@ -1546,6 +1486,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
1546
1486
modifiers |= ControlKeyStates::EnhancedKey;
1547
1487
}
1548
1488
1489
+ e.Handled (_KeyHandler (vkey, scanCode, modifiers, keyDown));
1490
+ }
1491
+
1492
+ bool TermControl::_KeyHandler (WORD vkey, WORD scanCode, ControlKeyStates modifiers, bool keyDown)
1493
+ {
1494
+ // If the current focused element is a child element of searchbox,
1495
+ // we do not send this event up to terminal
1496
+ if (_searchBox && _searchBox->ContainsFocus ())
1497
+ {
1498
+ return false ;
1499
+ }
1500
+
1549
1501
// GH#11076:
1550
1502
// For some weird reason we sometimes receive a WM_KEYDOWN
1551
1503
// message without vkey or scanCode if a user drags a tab.
@@ -1554,8 +1506,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
1554
1506
// accidental insertion of invalid KeyChords into classes like ActionMap.
1555
1507
if (!vkey && !scanCode)
1556
1508
{
1557
- e.Handled (true );
1558
- return ;
1509
+ return true ;
1559
1510
}
1560
1511
1561
1512
// Mark the event as handled and do nothing if we're closing, or the key
@@ -1568,26 +1519,151 @@ namespace winrt::Microsoft::Terminal::Control::implementation
1568
1519
// about.
1569
1520
if (_IsClosing () || vkey == VK_LWIN || vkey == VK_RWIN)
1570
1521
{
1571
- e.Handled (true );
1572
- return ;
1522
+ return true ;
1573
1523
}
1574
1524
1575
1525
// Short-circuit isReadOnly check to avoid warning dialog
1576
1526
if (_core.IsInReadOnlyMode ())
1577
1527
{
1578
- e.Handled (!keyDown || _TryHandleKeyBinding (vkey, scanCode, modifiers));
1579
- return ;
1528
+ return !keyDown || _TryHandleKeyBinding (vkey, scanCode, modifiers);
1580
1529
}
1581
1530
1582
- // Alt-Numpad# input will send us a character once the user releases
1583
- // Alt, so we should be ignoring the individual keydowns. The character
1584
- // will be sent through the TSFInputControl. See GH#1401 for more
1585
- // details
1586
- if (modifiers.IsAltPressed () && !modifiers.IsCtrlPressed () &&
1587
- (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9))
1531
+ // Our custom TSF input control doesn't receive Alt+Numpad inputs,
1532
+ // and we don't receive any via WM_CHAR as a xaml island app either.
1533
+ // So, we simply implement our own Alt-Numpad handling here.
1534
+ //
1535
+ // This handles the case where the Alt key is released.
1536
+ // We'll flush any ongoing composition in that case.
1537
+ if (vkey == VK_MENU && !keyDown && _altNumpadState.active )
1588
1538
{
1589
- e.Handled (true );
1590
- return ;
1539
+ auto & s = _altNumpadState;
1540
+ auto encoding = s.encoding ;
1541
+ wchar_t buf[4 ]{};
1542
+ size_t buf_len = 0 ;
1543
+
1544
+ if (encoding == AltNumpadEncoding::Unicode)
1545
+ {
1546
+ // UTF-32 -> UTF-16
1547
+ if (s.accumulator <= 0xffff )
1548
+ {
1549
+ buf[buf_len++] = static_cast <uint16_t >(s.accumulator );
1550
+ }
1551
+ else
1552
+ {
1553
+ buf[buf_len++] = static_cast <uint16_t >((s.accumulator >> 10 ) + 0xd7c0 );
1554
+ buf[buf_len++] = static_cast <uint16_t >((s.accumulator & 0x3ff ) | 0xdc00 );
1555
+ }
1556
+ }
1557
+ else
1558
+ {
1559
+ const auto ansi = encoding == AltNumpadEncoding::ANSI;
1560
+ const auto acp = GetACP ();
1561
+ auto codepage = ansi ? acp : CP_OEMCP;
1562
+
1563
+ // Alt+Numpad inputs are always a single codepoint, be it UTF-32 or ANSI.
1564
+ // Since DBCS code pages by definition are >1 codepoint, we can't encode those.
1565
+ // Traditionally, the OS uses the Latin1 or IBM code page instead.
1566
+ if (acp == CP_JAPANESE ||
1567
+ acp == CP_CHINESE_SIMPLIFIED ||
1568
+ acp == CP_KOREAN ||
1569
+ acp == CP_CHINESE_TRADITIONAL ||
1570
+ acp == CP_UTF8)
1571
+ {
1572
+ codepage = ansi ? 1252 : 437 ;
1573
+ }
1574
+
1575
+ // The OS code seemed to also simply cut off the last byte in the accumulator.
1576
+ const auto ch = gsl::narrow_cast<char >(s.accumulator & 0xff );
1577
+ const auto len = MultiByteToWideChar (codepage, 0 , &ch, 1 , &buf[0 ], 2 );
1578
+ buf_len = gsl::narrow_cast<size_t >(std::max (0 , len));
1579
+ }
1580
+
1581
+ if (buf_len != 0 )
1582
+ {
1583
+ // WinRT always needs null-terminated strings, because HSTRING is dumb.
1584
+ // If it encounters a string that isn't, cppwinrt will abort().
1585
+ // It should already be null-terminated, but let's make sure to not crash.
1586
+ buf[buf_len] = L' \0 ' ;
1587
+ _core.SendInput (std::wstring_view{ &buf[0 ], buf_len });
1588
+ }
1589
+
1590
+ s = {};
1591
+ return true ;
1592
+ }
1593
+ // As a continuation of the above, this handles the key-down case.
1594
+ if (modifiers.IsAltPressed ())
1595
+ {
1596
+ // The OS code seems to reset the composition if shift is pressed, but I couldn't
1597
+ // figure out how exactly it worked. We'll simply ignore any such inputs.
1598
+ static constexpr DWORD permittedModifiers =
1599
+ RIGHT_ALT_PRESSED |
1600
+ LEFT_ALT_PRESSED |
1601
+ NUMLOCK_ON |
1602
+ SCROLLLOCK_ON |
1603
+ CAPSLOCK_ON;
1604
+
1605
+ if (keyDown && (modifiers.Value () & ~permittedModifiers) == 0 )
1606
+ {
1607
+ auto & s = _altNumpadState;
1608
+
1609
+ if (vkey == VK_ADD)
1610
+ {
1611
+ // Alt '+' <number> is used to input Unicode code points.
1612
+ // Every time you press + it resets the entire state
1613
+ // in the original OS implementation as well.
1614
+ s.encoding = AltNumpadEncoding::Unicode;
1615
+ s.accumulator = 0 ;
1616
+ s.active = true ;
1617
+ }
1618
+ else if (vkey == VK_NUMPAD0 && s.encoding == AltNumpadEncoding::OEM && s.accumulator == 0 )
1619
+ {
1620
+ // Alt '0' <number> is used to input ANSI code points.
1621
+ // Otherwise, they're OEM codepoints.
1622
+ s.encoding = AltNumpadEncoding::ANSI;
1623
+ s.active = true ;
1624
+ }
1625
+ else
1626
+ {
1627
+ // Otherwise, append the pressed key to the accumulator.
1628
+ const uint32_t base = s.encoding == AltNumpadEncoding::Unicode ? 16 : 10 ;
1629
+ uint32_t add = 0xffffff ;
1630
+
1631
+ if (vkey >= VK_NUMPAD0 && vkey <= VK_NUMPAD9)
1632
+ {
1633
+ add = vkey - VK_NUMPAD0;
1634
+ }
1635
+ else if (vkey >= ' A' && vkey <= ' F' )
1636
+ {
1637
+ add = vkey - ' A' + 10 ;
1638
+ }
1639
+
1640
+ // Pressing Alt + <not a number> should not activate the Alt+Numpad input, however.
1641
+ if (add < base)
1642
+ {
1643
+ s.accumulator = std::min (s.accumulator * base + add, 0x10FFFFu );
1644
+ s.active = true ;
1645
+ }
1646
+ }
1647
+
1648
+ // If someone pressed Alt + <not a number>, we'll skip the early
1649
+ // return and send the Alt key combination as per usual.
1650
+ if (s.active )
1651
+ {
1652
+ return true ;
1653
+ }
1654
+
1655
+ // Unless I didn't code the above correctly, active == false should imply
1656
+ // that _altNumpadState is in the (default constructed) base state.
1657
+ assert (s.encoding == AltNumpadEncoding::OEM);
1658
+ assert (s.accumulator == 0 );
1659
+ }
1660
+ }
1661
+ else if (_altNumpadState.active )
1662
+ {
1663
+ // If the user Alt+Tabbed in the middle of an Alt+Numpad sequence, we'll not receive a key-up event for
1664
+ // the Alt key. There are several ways to detect this. Here, we simply check if the user typed another
1665
+ // character, it's not an alt-up event, and we still have an ongoing composition.
1666
+ _altNumpadState = {};
1591
1667
}
1592
1668
1593
1669
// GH#2235: Terminal::Settings hasn't been modified to differentiate
@@ -1603,20 +1679,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation
1603
1679
keyDown &&
1604
1680
_TryHandleKeyBinding (vkey, scanCode, modifiers))
1605
1681
{
1606
- e.Handled (true );
1607
- return ;
1682
+ return true ;
1608
1683
}
1609
1684
1610
1685
if (_TrySendKeyEvent (vkey, scanCode, modifiers, keyDown))
1611
1686
{
1612
- e.Handled (true );
1613
- return ;
1687
+ return true ;
1614
1688
}
1615
1689
1616
1690
// Manually prevent keyboard navigation with tab. We want to send tab to
1617
1691
// the terminal, and we don't want to be able to escape focus of the
1618
1692
// control with tab.
1619
- e. Handled ( vkey == VK_TAB) ;
1693
+ return vkey == VK_TAB;
1620
1694
}
1621
1695
1622
1696
// Method Description:
0 commit comments