DarthVlader
DarthVlader

Reputation: 373

How to handle VK_MENU (alt) keypresses properly using WinAPI?

I am writing a windows application using the WinAPI (win32) on C++, but can't figure out what's happening with the ALT key. I am handling both WM_KEYDOWN and WM_SYSKEYDOWN, so all keypresses should be coming through. However, while every ALT keyup is detected, every second ALT keydown is being skipped.

Do I have to generate my own keydown event whenever I get a second WM_SYSKEYUP in a row, or am I doing something wrong with my message handling?

Details

Tap these keys: ALT ALT ALT ALT ALT (alt five times)
Expected message sequence: DUDUDUDUDU (down then up, repeated five times)
Observed message sequence: DUUDUUDU (every second down is missing)

Tap these keys: ALT C ALT C ALT C ALT C ALT C (add other keypresses in between - release ALT before tapping C)
Expected VK_MENU message sequence: DUDUDUDUDU (down then up, repeated five times)
Observed VK_MENU message sequence: DUDUDUDUDU (as expected!)

It seems that adding other keypresses (or even mouse events) cancels whatever Windows was doing and forces it to send the app all the ALT keypresses.

Example code (Visual c++17):

Creates a window which adds 'D' to the window title when ALT is pressed and 'U' when ALT is released

#ifndef UNICODE
#define UNICODE
#endif
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#define STRICT
#include <Windows.h>
#include <optional>
#include <malloc.h>

WCHAR* title;
int titleLen = 0;
const int maxTitleLen = 64;

void updateWindowTitle(WCHAR c, HWND hwnd)
{
    if (titleLen >= maxTitleLen) return;
    title[titleLen++] = c;
    title[titleLen] = 0;
    SetWindowTextW(hwnd, title);
}

LRESULT WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CLOSE:
        PostQuitMessage(0);
        return 0;
    case WM_SYSKEYDOWN: //needed for keys like alt (VK_MENU)
    case WM_KEYDOWN:
        //process_keydown_event(wParam);
        if (!(lParam & 0x40000000)) { //ignore autorepeat
            if (wParam == VK_MENU) updateWindowTitle(L'D', hwnd);
        }
        break;
    case WM_SYSKEYUP: //needed for keys like alt (VK_MENU)
    case WM_KEYUP:
        //process_keyup_event(wParam);
        if (wParam == VK_MENU) updateWindowTitle(L'U', hwnd);
        break;
    //etc...
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

// *********** STANDARD BORING SETUP BELOW ************

std::optional<int> processMessages() noexcept
{
    MSG msg;
    while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
    {
        if (msg.message == WM_QUIT)
        {
            // Return argument to PostQuitMessage as we are quitting
            return msg.wParam;
        }
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return {}; //return empty optional when not quitting
}

int CALLBACK WinMain(
    HINSTANCE hInst,
    HINSTANCE hPrevInst,
    LPSTR     lpCmdLine,
    int       nShowCmd)
{
    // Make global title point to valid memory
    title = static_cast<WCHAR*>(malloc(sizeof(WCHAR) * maxTitleLen));

    // Register window class
    WNDCLASSEX wc = { 0 };
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_OWNDC;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.hInstance = hInst;
    wc.hIcon = nullptr;
    wc.hIconSm = nullptr;
    wc.hCursor = nullptr;
    wc.hbrBackground = nullptr;
    wc.lpszMenuName = nullptr;
    wc.lpszClassName = L"my window class name";
    RegisterClassEx(&wc);

    // Create window 800x500
    HWND hwnd = CreateWindowW(
        wc.lpszClassName, L"",
        WS_VISIBLE | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU,
        CW_USEDEFAULT, CW_USEDEFAULT, 800, 500,
        nullptr, nullptr, hInst, nullptr
    );
    // (check HWND is not NULL)

    //main loop
    while (true)
    {
        if (const auto errorcode = processMessages())
        {
            return errorcode.value();
        }
    }
}

Upvotes: 1

Views: 2316

Answers (1)

Zeus
Zeus

Reputation: 3890

I can reproduce the problem through code, and then I capture the message of the window through spy++.

We can see that when the ALT key is pressed for the first time, the WM_SYSKEYDOWN and WM_SETTEXT messages are successfully triggered:

enter image description here

When the ALT key is pressed the second time, it actually triggers the WM_SYSKEYDOWN message, but after a series of message sending(WM_CAPTURECHANGED,WM_MENUSELECT...).This resulted in the message not being processed in the main window, but sent to hmenu:00000000, so the main window did not process the WM_SYSKEYDOWN message.

enter image description here

You can handle it by processing the WM_SYSCOMMAND message:

case WM_SYSCOMMAND:
    if (wParam == SC_KEYMENU)
    {
        return 0;
    }
    else
        return DefWindowProc(hwnd, msg, wParam, lParam);
    break;

It works for me.

Upvotes: 5

Related Questions