void HandleKeyEvent()

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);
}