From a46db6cc1ef5df0aa594b1bfb804240f1d989a2f Mon Sep 17 00:00:00 2001 From: Dylan Deverill Date: Sun, 6 Sep 2026 21:12:56 -0700 Subject: [PATCH] Log the machine's wheel-lines setting alongside injection (#95) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scroll is injected as wheel events, 120 units to a notch, and Windows multiplies each notch by the user's lines-per-notch before anything moves. Identical injection therefore scrolls wildly different amounts on two machines, and a "scroll is far too fast" report gives no way to tell that apart from a bug in our own arithmetic. The default is 3 lines. WHEEL_PAGESCROLL makes a notch a whole page, which turns the gentlest flick into a page jump — "I barely touch the pad and it scrolls all the way down a page" describes that setting exactly, and it is not something a sensitivity control could tune away, since with a page as the smallest unit the granularity is the problem rather than the scale. Recorded in the injection environment line, which already fires at game mode takeover whenever a pad is driving the desktop, so it costs nothing and arrives attached to every future report of this kind. The reporter's log and ours are otherwise identical — both id=0x42 len=54 — so the report layout is ruled out. Co-Authored-By: Claude Opus 5 --- src/app/InputInjection.cpp | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/app/InputInjection.cpp b/src/app/InputInjection.cpp index cd72127..3734893 100644 --- a/src/app/InputInjection.cpp +++ b/src/app/InputInjection.cpp @@ -282,24 +282,43 @@ std::wstring DescribeCursor() { namespace InputInjection { +// How far one wheel notch scrolls on this machine, which is not ours to +// choose. Scroll is injected as wheel events — 120 units to a notch — and +// Windows multiplies each notch by this before anything moves, so identical +// injection scrolls wildly different amounts on two machines. The default is +// 3 lines; WHEEL_PAGESCROLL means a notch is a whole page, which turns the +// gentlest flick of a pad into a page jump and is not something a sensitivity +// setting can tune away, the granularity being the problem rather than the +// scale. Worth a line in the log, since a scroll-feels-wrong report otherwise +// gives no way to tell this apart from a bug in our own arithmetic. +static std::wstring DescribeWheelLines() { + UINT lines = 3; + if (!SystemParametersInfoW(SPI_GETWHEELSCROLLLINES, 0, &lines, 0)) + return L"unknown"; + if (lines == WHEEL_PAGESCROLL) return L"one page per notch (!)"; + return std::to_wstring(lines) + L" lines per notch" + + (lines == 3 ? L"" : L" (not the default 3)"); +} + void LogEnvironment(const char* reason) { DWORD fgIntegrity = kIntegrityUnknown; - const std::wstring fg = DescribeForeground(fgIntegrity); - const DWORD own = OwnIntegrity(); - const std::wstring clip = DescribeClip(); - const std::wstring cur = DescribeCursor(); + const std::wstring fg = DescribeForeground(fgIntegrity); + const DWORD own = OwnIntegrity(); + const std::wstring clip = DescribeClip(); + const std::wstring cur = DescribeCursor(); + const std::wstring wheel = DescribeWheelLines(); const bool outranked = fgIntegrity != kIntegrityUnknown && own != kIntegrityUnknown && fgIntegrity > own; EventLog::Write("INJECT: %s — foreground=%ls, us=integrity=%ls%s, " - "cursorClip=%ls, cursor=%ls", + "cursorClip=%ls, cursor=%ls, wheel=%ls", reason, fg.c_str(), IntegrityName(own), outranked ? " — FOREGROUND OUTRANKS US, injected input is " "blocked (UIPI) until focus moves or it closes" : "", - clip.c_str(), cur.c_str()); + clip.c_str(), cur.c_str(), wheel.c_str()); } void SetAlertCallback(AlertFn fn) {