Skip to content

fix(computer): emit one full WHEEL_DELTA per libnut scroll tick#2575

Open
quanru wants to merge 3 commits into
mainfrom
fix/computer-libnut-detents
Open

fix(computer): emit one full WHEEL_DELTA per libnut scroll tick#2575
quanru wants to merge 3 commits into
mainfrom
fix/computer-libnut-detents

Conversation

@quanru

@quanru quanru commented Jun 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Move the libnut-fallback per-detent loop into ComputerInputDriver.emitScrollDetents so scroll dispatch lives next to the rest of the input primitives.
  • Emit exactly one platform-correct detent per libnut.scrollMouse call (120 on Windows = one WHEEL_DELTA, 1 elsewhere) and pace calls with LIBNUT_FALLBACK_TICK_DELAY_MS so blink doesn't coalesce them into a single tick.
  • Add input-driver-scroll.test.ts covering per-call magnitude, pacing, horizontal pass-through, and destroy-mid-scroll rejection.

Why

libnut.scrollMouse(x, y) is not a portable pixel API:

Platform y semantics
macOS CG pixel scroll delta
Linux XButton4/5 press-release pairs (1 = one wheel notch)
Windows Forwarded directly to MOUSEEVENTF_WHEEL's mouseData, where the unit is WHEEL_DELTA (= 120) per detent

The old fallback computed ticks = ceil(distance / 100) and made a single scrollMouse(0, ticks) call. On Windows that sends mouseData < 120 for any normal request — well below one detent — and Chromium's WheelEventQueue silently accumulates and drops it, which is exactly what we saw in Lark / Feishu Electron clients reporting "scroll did nothing". macOS still works because the phased-scroll helper takes the path; Linux happens to work because 1 detent ≈ 1 notch already.

Edge scrolls (scrollToTop etc.) had the same problem in the libnut path: the unit vector was hard-coded as [0, 10] etc. — fine on Linux as 10 notches, near-useless on Windows as mouseData = 10.

After this change, both the directional fallback and edge fallback go through emitScrollDetents, which fires LIBNUT_FALLBACK_DETENT_AMOUNT per call — 120 on Windows, 1 everywhere else — and the destroy-mid-scroll rejection wired into the pending-input-delay set keeps shutdown clean.

Test plan

  • pnpm run lint
  • npx nx test computer — 53/53 passing, including 4 new tests in input-driver-scroll.test.ts
  • npx nx build computer
  • Windows manual verification in Lark / Feishu Electron client (single scroll request now visibly moves the page)

Related

  • Pairs with fix(computer): self-heal phased-scroll +x bit lost in npm tarball #2574 (chmod self-heal). Both are scroll-reliability fixes in the libnut/phased-scroll path; this one is the Windows half.
  • Supersedes branch fix/computer-scroll-win-fallback (commits 905124883, d020e1de4) — those predated the ComputerInputDriver refactor on main; this PR is the rewrite.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ec4694cbb1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/computer/src/device.ts Outdated
await this.inputDriver.emitScrollDetents(
ux * LIBNUT_FALLBACK_DETENT_AMOUNT,
uy * LIBNUT_FALLBACK_DETENT_AMOUNT,
SCROLL_REPEAT_COUNT,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve enough detents for edge-scroll fallback

When phased scrolling is unavailable and the libnut fallback is used (Windows/Linux, or macOS with the libnut keyboard driver), scrollToTop/scrollToBottom now emits only SCROLL_REPEAT_COUNT one-detent ticks. With the new platform-correct detent amount, that is roughly 10 wheel notches (about 1,000px by this file's LIBNUT_FALLBACK_PIXELS_PER_DETENT), whereas edge scrolls are documented and implemented in the phased path as a 50,000px boundary scroll; common long pages will stop far short of the top/bottom. The old Linux fallback effectively sent 100 notches, and the Windows fix should scale the number of detents rather than reducing the edge action to a single-screen-ish scroll.

Useful? React with 👍 / 👎.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 1, 2026

Copy link
Copy Markdown

Deploying midscene with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9a63ffb
Status: ✅  Deploy successful!
Preview URL: https://9de9d4ac.midscene.pages.dev
Branch Preview URL: https://fix-computer-libnut-detents.midscene.pages.dev

View logs

@quanru quanru force-pushed the fix/computer-libnut-detents branch 2 times, most recently from 7244220 to 7e976ab Compare June 8, 2026 09:48
@quanru quanru force-pushed the fix/computer-libnut-detents branch from 7e976ab to 4045343 Compare June 8, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant