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