in src/interactivity/win32/windowio.cpp [150:468]
void HandleKeyEvent(const HWND hWnd,
const UINT Message,
const WPARAM wParam,
const LPARAM lParam,
_Inout_opt_ PBOOL pfUnlockConsole)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// BOGUS for WM_CHAR/WM_DEADCHAR, in which LOWORD(lParam) is a character
WORD VirtualKeyCode = LOWORD(wParam);
WORD VirtualScanCode = LOBYTE(HIWORD(lParam));
const WORD RepeatCount = LOWORD(lParam);
const ULONG ControlKeyState = GetControlKeyState(lParam);
const BOOL bKeyDown = WI_IsFlagClear(lParam, KEY_TRANSITION_UP);
if (bKeyDown)
{
// Log a telemetry flag saying the user interacted with the Console
// Only log when the key is a down press. Otherwise we're getting many calls with
// Message = WM_CHAR, VirtualKeyCode = VK_TAB, with bKeyDown = false
// when nothing is happening, or the user has merely clicked on the title bar, and
// this can incorrectly mark the session as being interactive.
Telemetry::Instance().SetUserInteractive();
}
// Make sure we retrieve the key info first, or we could chew up
// unneeded space in the key info table if we bail out early.
if (Message == WM_CHAR || Message == WM_SYSCHAR || Message == WM_DEADCHAR || Message == WM_SYSDEADCHAR)
{
// --- START LOAD BEARING CODE ---
// NOTE: We MUST match up the original data from the WM_KEYDOWN stroke (handled at some inexact moment in the
// past by TranslateMessageEx) with the WM_CHAR we are processing now to ensure we have the correct
// wVirtualScanCode to associate with the message and pass down into the console input queue for further
// processing.
// This is required because we cannot accurately re-synthesize (using MapVirtualKey/Ex)
// the original scan code just based on the information we have now and the scan code might be
// required by the underlying client application, processed input handler (inside the console),
// or other input channels to help portray certain key sequences.
// Most notably this affects Ctrl-C, Ctrl-Break, and Pause/Break among others.
//
RetrieveKeyInfo(hWnd,
&VirtualKeyCode,
&VirtualScanCode,
!gci.pInputBuffer->fInComposition);
// --- END LOAD BEARING CODE ---
}
KeyEvent keyEvent{ !!bKeyDown, RepeatCount, VirtualKeyCode, VirtualScanCode, UNICODE_NULL, 0 };
if (Message == WM_CHAR || Message == WM_SYSCHAR || Message == WM_DEADCHAR || Message == WM_SYSDEADCHAR)
{
// If this is a fake character, zero the scancode.
if (lParam & 0x02000000)
{
keyEvent.SetVirtualScanCode(0);
}
keyEvent.SetActiveModifierKeys(GetControlKeyState(lParam));
if (Message == WM_CHAR || Message == WM_SYSCHAR)
{
keyEvent.SetCharData(static_cast<wchar_t>(wParam));
}
else
{
keyEvent.SetCharData(0);
}
}
else
{
// if alt-gr, ignore
if (lParam & 0x02000000)
{
return;
}
keyEvent.SetActiveModifierKeys(ControlKeyState);
keyEvent.SetCharData(0);
}
const INPUT_KEY_INFO inputKeyInfo(VirtualKeyCode, ControlKeyState);
// Capture telemetry on Ctrl+Shift+ C or V commands
if (IsInProcessedInputMode())
{
// Capture telemetry data when a user presses ctrl+shift+c or v in processed mode
if (inputKeyInfo.IsShiftAndCtrlOnly())
{
if (VirtualKeyCode == 'V')
{
Telemetry::Instance().LogCtrlShiftVProcUsed();
}
else if (VirtualKeyCode == 'C')
{
Telemetry::Instance().LogCtrlShiftCProcUsed();
}
}
}
else
{
// Capture telemetry data when a user presses ctrl+shift+c or v in raw mode
if (inputKeyInfo.IsShiftAndCtrlOnly())
{
if (VirtualKeyCode == 'V')
{
Telemetry::Instance().LogCtrlShiftVRawUsed();
}
else if (VirtualKeyCode == 'C')
{
Telemetry::Instance().LogCtrlShiftCRawUsed();
}
}
}
// If this is a key up message, should we ignore it? We do this so that if a process reads a line from the input
// buffer, the key up event won't get put in the buffer after the read completes.
if (gci.Flags & CONSOLE_IGNORE_NEXT_KEYUP)
{
gci.Flags &= ~CONSOLE_IGNORE_NEXT_KEYUP;
if (!bKeyDown)
{
return;
}
}
Selection* pSelection = &Selection::Instance();
if (bKeyDown && gci.GetInterceptCopyPaste() && inputKeyInfo.IsShiftAndCtrlOnly())
{
// Intercept C-S-v to paste
switch (VirtualKeyCode)
{
case 'V':
// the user is attempting to paste from the clipboard
Telemetry::Instance().SetKeyboardTextEditingUsed();
Clipboard::Instance().Paste();
return;
}
}
else if (!IsInVirtualTerminalInputMode())
{
// First attempt to process simple key chords (Ctrl+Key)
if (inputKeyInfo.IsCtrlOnly() && ShouldTakeOverKeyboardShortcuts() && bKeyDown)
{
switch (VirtualKeyCode)
{
case 'A':
// Set Text Selection using keyboard to true for telemetry
Telemetry::Instance().SetKeyboardTextSelectionUsed();
// the user is asking to select all
pSelection->SelectAll();
return;
case 'F':
// the user is asking to go to the find window
DoFind();
*pfUnlockConsole = FALSE;
return;
case 'M':
// the user is asking for mark mode
Selection::Instance().InitializeMarkSelection();
return;
case 'V':
// the user is attempting to paste from the clipboard
Telemetry::Instance().SetKeyboardTextEditingUsed();
Clipboard::Instance().Paste();
return;
case VK_HOME:
case VK_END:
case VK_UP:
case VK_DOWN:
// if the user is asking for keyboard scroll, give it to them
if (Scrolling::s_HandleKeyScrollingEvent(&inputKeyInfo))
{
return;
}
break;
case VK_PRIOR:
case VK_NEXT:
Telemetry::Instance().SetCtrlPgUpPgDnUsed();
break;
}
}
// Handle F11 fullscreen toggle
if (VirtualKeyCode == VK_F11 &&
bKeyDown &&
inputKeyInfo.HasNoModifiers() &&
ShouldTakeOverKeyboardShortcuts())
{
ServiceLocator::LocateConsoleWindow<Window>()->ToggleFullscreen();
return;
}
// handle shift-ins paste
if (inputKeyInfo.IsShiftOnly() && ShouldTakeOverKeyboardShortcuts())
{
if (!bKeyDown)
{
return;
}
else if (VirtualKeyCode == VK_INSERT && !(pSelection->IsInSelectingState() && pSelection->IsKeyboardMarkSelection()))
{
Clipboard::Instance().Paste();
return;
}
}
// handle ctrl+shift+plus/minus for transparency adjustment
if (inputKeyInfo.IsShiftAndCtrlOnly() && ShouldTakeOverKeyboardShortcuts())
{
if (!bKeyDown)
{
return;
}
else
{
//This is the only place where the window opacity is changed NOT due to the props sheet.
short opacityDelta = 0;
if (VirtualKeyCode == VK_OEM_PLUS || VirtualKeyCode == VK_ADD)
{
opacityDelta = OPACITY_DELTA_INTERVAL;
}
else if (VirtualKeyCode == VK_OEM_MINUS || VirtualKeyCode == VK_SUBTRACT)
{
opacityDelta = -OPACITY_DELTA_INTERVAL;
}
if (opacityDelta != 0)
{
ServiceLocator::LocateConsoleWindow<Window>()->ChangeWindowOpacity(opacityDelta);
return;
}
}
}
}
// Then attempt to process more complicated selection/scrolling commands that require state.
// These selection and scrolling functions must go after the simple key-chord combinations
// as they have the potential to modify state in a way those functions do not expect.
if (gci.Flags & CONSOLE_SELECTING)
{
if (!bKeyDown)
{
return;
}
Selection::KeySelectionEventResult handlingResult = pSelection->HandleKeySelectionEvent(&inputKeyInfo);
if (handlingResult == Selection::KeySelectionEventResult::CopyToClipboard)
{
// If the ALT key is held, also select HTML as well as plain text.
bool const fAlsoSelectHtml = WI_IsFlagSet(GetKeyState(VK_MENU), KEY_PRESSED);
Clipboard::Instance().Copy(fAlsoSelectHtml);
return;
}
else if (handlingResult == Selection::KeySelectionEventResult::EventHandled)
{
return;
}
}
if (Scrolling::s_IsInScrollMode())
{
if (!bKeyDown || Scrolling::s_HandleKeyScrollingEvent(&inputKeyInfo))
{
return;
}
}
// we need to check if there is an active popup because otherwise they won't be able to receive shift+key events
if (pSelection->s_IsValidKeyboardLineSelection(&inputKeyInfo) && IsInProcessedInputMode() && gci.PopupCount.load() == 0)
{
if (!bKeyDown || pSelection->HandleKeyboardLineSelectionEvent(&inputKeyInfo))
{
return;
}
}
// if the user is inputting chars at an inappropriate time, beep.
if ((gci.Flags & (CONSOLE_SELECTING | CONSOLE_SCROLLING | CONSOLE_SCROLLBAR_TRACKING)) &&
bKeyDown &&
!IsSystemKey(VirtualKeyCode))
{
ServiceLocator::LocateConsoleWindow()->SendNotifyBeep();
return;
}
if (gci.pInputBuffer->fInComposition)
{
return;
}
bool generateBreak = false;
// ignore key strokes that will generate CHAR messages. this is only necessary while a dialog box is up.
if (ServiceLocator::LocateGlobals().uiDialogBoxCount != 0)
{
if (Message != WM_CHAR && Message != WM_SYSCHAR && Message != WM_DEADCHAR && Message != WM_SYSDEADCHAR)
{
WCHAR awch[MAX_CHARS_FROM_1_KEYSTROKE];
BYTE KeyState[256];
if (GetKeyboardState(KeyState))
{
int cwch = ToUnicodeEx((UINT)wParam, HIWORD(lParam), KeyState, awch, ARRAYSIZE(awch), TM_POSTCHARBREAKS, nullptr);
if (cwch != 0)
{
return;
}
}
else
{
return;
}
}
else
{
// remember to generate break
if (Message == WM_CHAR)
{
generateBreak = true;
}
}
}
HandleGenericKeyEvent(keyEvent, generateBreak);
}