Strange coordinates in `WM_MOUSEMOVE`

Viewed 144

MSDN describes that lParam of WM_MOUSEMOVE is 2 shorts due to it needing to be compatible with virtual coordinates because it acts as a redirected event if capture is set, which is clear. However, negative coordinates are still received under normal circumstances when moving the mouse slightly outside of the window, to be exact a bonus 5 pixels in all directions except up (which is where the caption is, and it'd go to WM_NCMOUSEMOVE instead).

I initially suspected this was to do with the drop shadow technically being part of the window (like with the output of AdjustWindowRectEx's rectangle including it) since you can receive events in some of the shadow, but the values of that don't match up, and clamping the values to the client area's size doesn't feel intended. Where do the bonus 5 pixels (on my system, Windows 10 Education 2004) come from, especially considering that shouldn't even be a part of the client area to my knowledge, and is there a clean/intended way to dodge unwanted values?

I've seen some discussion about the area you can grab to resize the window potentially being related, but my window isn't resizable (doesn't have a thick frame).

Visualization

Edit: After tinkering, it seems this is related to the window style. Here's a reproducible sample (hopefully on other machines, too):

#include <Windows.h>
#include <stdio.h>

// Remove `^ WS_THICKFRAME` and the bug vanishes!
#define WINDOW_STYLE ((WS_OVERLAPPEDWINDOW | WS_VISIBLE) ^ WS_THICKFRAME)

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    if (message == WM_MOUSEMOVE) {
        POINTS mouse = MAKEPOINTS(lParam);
        printf("WM_MOUSEMOVE @ x=%hd, y=%hd\n", mouse.x, mouse.y);
    } else if (message == WM_DESTROY) {
        PostQuitMessage(0);
    }
    return DefWindowProcW(hWnd, message, wParam, lParam);
}

int main(void) {
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = GetModuleHandle(NULL);
    wcex.hIcon = NULL;
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = L"SO_Example";
    wcex.hIconSm = NULL;

    RECT windowRect = { 0, 0, 600, 600 };
    AdjustWindowRectEx(&windowRect, WINDOW_STYLE, FALSE, 0);

    ATOM windowClass = RegisterClassExW(&wcex);
    HWND hWnd = CreateWindowExW(
        0,
        (LPCWSTR)windowClass,
        L"hello stackoverflow!",
        WINDOW_STYLE,
        CW_USEDEFAULT, CW_USEDEFAULT,
        windowRect.right - windowRect.left,
        windowRect.bottom - windowRect.top,
        NULL,
        NULL,
        GetModuleHandle(NULL),
        NULL
    );

    MSG msg;
    while (GetMessageW(&msg, NULL, 0, 0) != 0) {
        TranslateMessage(&msg);
        DispatchMessageW(&msg);
    }

    return 0;
}

Here's that sample in action: https://i.imgur.com/LfCe9od.mp4

1 Answers

It appears 5 is the border size (1) and the border padding (4) added up (aka the area the user can use to resize the window), so it's safe to discard values outside of the client rectangle, even if it's a little strange that Win32 is reporting events in that area.

Related