Skip to content

Create cursor-motion-blur#4707

Merged
m417z merged 5 commits into
ramensoftware:mainfrom
chrisc44890:chrisc44890-cursor-motion-blur
Jul 8, 2026
Merged

Create cursor-motion-blur#4707
m417z merged 5 commits into
ramensoftware:mainfrom
chrisc44890:chrisc44890-cursor-motion-blur

Conversation

@chrisc44890

@chrisc44890 chrisc44890 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Adds Cursor Motion Blur:

A mod that adds cartoon "smear frame" motion blur to your mouse movements.

Changelog

If this pull request updates an existing mod, describe the changes below:

  • Added file extension.

Mod authorship

If this pull request introduces a new mod, please complete the section below.

This mod was created by:

    • The submitter, without AI assistance
    • [ X] The submitter, with AI assistance
    • Claude
    • ChatGPT
    • [X ] Gemini
    • Another AI (please specify):
    • Other (please specify):

Please select the options that best apply. Your selection does not affect the acceptance criteria, but it helps reviewers understand the context of the code and provide relevant feedback.

Adds Cursor Motion Blur:

A mod that adds cartoon "smear frame" motion blur to your mouse movements.
Fixed name by adding file extension... Oops
@m417z

m417z commented Jul 7, 2026

Copy link
Copy Markdown
Member

Submission review

Note: This review was done by Claude, and then refined manually. Due to the amount of submissions, doing a fully manual review for each pull request is no longer feasible. Thank you for understanding.

Please address the following issues. The items in the collapsed sections are optional, so it's your call whether to address them.


This should be a "tool mod", not injected into explorer.exe. The mod installs no function/symbol hooks — it only does global desktop work (an overlay window, a WM_TIMER, GetCursorPos, SetSystemCursor), none of which needs to run inside explorer.exe. It ignores explorer's own state and operates on the desktop as a whole, which is the textbook signal for the mods-as-tools pattern. Two concrete downsides of the current approach: a crash in the GDI+/overlay code takes down the shell, and if more than one explorer.exe is running (separate-process folder windows, or a relaunch during the mod's lifetime) each instance spins up its own competing timer/overlay and they fight over SetSystemCursor. Please convert it: set @include windhawk.exe, rename Wh_ModInit/Wh_ModSettingsChanged/Wh_ModUninitWhTool_*, and paste the launcher boilerplate verbatim from the wiki. See mods/explorer-folder-hover-menu.wh.cpp (bottom of file) for the exact snippet.

SetSystemCursor is a system-wide change that isn't reversed if the process dies. ToggleCursor(false) replaces the OS arrow/IBeam/hand cursors for every process on the desktop, and the only thing that restores them is ToggleCursor(true) running in Wh_ModUninit. If explorer (or just the overlay thread) crashes while a smear is in progress, the cursor stays invisible everywhere until the user manually resets it — and a normal explorer restart won't fix it either, because the mod reloads with ToggleCursor's isHidden static reset to false, so the restore branch never runs. This breaks Windhawk's reversibility principle (effects must fully disappear when the mod is gone) and affects processes other than the target. The cleanest fix is to stop hiding the real OS cursor at all: draw the smear trail behind/around the live cursor and delete the whole SetSystemCursor/CreateCursor/SPI_SETCURSORS mechanism. If a hidden head is essential to the look, the hiding needs to be made crash-safe (which is hard with SetSystemCursor), which is another reason to avoid it.

timeBeginPeriod(1) raises the global system timer resolution for the mod's entire lifetime. It's called once in OverlayThreadProc and only released in Wh_ModUninit, so the whole system runs at 1 ms timer granularity — and its associated higher power draw — the entire time the mod is loaded, including when the mouse is idle (contradicting the "Idles at 0% CPU" claim). It also buys almost nothing here: SetTimer clamps uElapse to USER_TIMER_MINIMUM (10 ms), so the "1 ms" timer actually fires at ~10 ms regardless. Recommend dropping timeBeginPeriod/timeEndPeriod (and the -lwinmm link) entirely, or at most raising the period only while actively smearing.

Add a GIF/screenshot to the README. This is a purely visual effect, so a short GIF of the smear trail in the README would help users a lot (only i.imgur.com and raw.githubusercontent.com are allowed as image hosts).

Optional improvements

Minor polish — none of this affects users, so it's your call.

  • The overlay window/class is leaked on unload. Wh_ModUninit posts WM_QUIT but never calls DestroyWindow(g_overlayHwnd), and the class registered via RegisterClass(&wc) is never UnregisterClassed. The window happens to be torn down when the thread exits, and the leaked class is harmless only because its lpfnWndProc is user32's DefWindowProc (not a pointer into the mod image), so the next load falls through the ERROR_CLASS_ALREADY_EXISTS from RegisterClass (whose return value is unchecked). It's still a leak that relies on that coincidence — cleaner to DestroyWindow and UnregisterClass(CLASS_NAME, hInstance) on teardown.
  • Teardown ordering race on g_cursorBitmap. Wh_ModUninit does delete g_cursorBitmap before it posts WM_QUIT/joins the overlay thread, so the still-running SmearTimerProc can DrawImage/re-cache g_cursorBitmap concurrently with the delete. Stop the thread first (PostMessage(WM_QUIT) + WaitForSingleObject), then free the bitmap. Same applies to ToggleCursor(true) being called from the arbitrary uninit thread while the overlay thread may also touch the unsynchronized isHidden static.
  • The full-virtual-screen bitmap is recreated every frame while smearing. CreateCompatibleDC + CreateCompatibleBitmap(hdcScreen, vW, vH) allocate a bitmap the size of the entire virtual desktop on every tick. You could create the DC/bitmap once and reuse it (or restrict the layered update to the trail's bounding rect) to avoid the per-frame allocation churn.

Functionality notes

Non-critical observations about the feature behavior itself.

  • The drawn "head" cursor likely goes invisible after the first smear frame. CacheRealCursor() runs every tick and caches GetCursorInfo().hCursor. Once ToggleCursor(false) has replaced the system cursor with the blank one, GetCursorInfo returns that blank cursor, whose handle differs from s_lastSeen, so g_cursorBitmap gets re-cached to the blank image and the fake cursor drawn at g_history.front() renders as nothing. Worth verifying; the fix is to skip CacheRealCursor() (or the re-cache) while the cursor is hidden, so only the genuine cursor is ever cached.
  • Only OCR_NORMAL (32512), OCR_HAND (32649), and OCR_IBEAM (32513) are blanked, so if the pointer is any other shape (resize arrows, wait, cross, etc.) when a smear starts, the real OS cursor still shows through alongside the trail.
  • IsGameRunning's fullscreen heuristic can false-positive on a maximized borderless non-game window (e.g. a maximized browser/video player), silently disabling the effect, and it calls FindWindowW(Progman/WorkerW) on every invocation.
  • The mod isn't DPI-aware, so cursor-bitmap size and trail positions may be off on mixed-DPI multi-monitor setups.
  • The overlay is a full-screen WS_EX_TOPMOST layered window; even transparent, a permanent topmost surface over the whole desktop can interfere with other topmost windows and with apps trying to present exclusively.

@chrisc44890

Copy link
Copy Markdown
Contributor Author

Changelog
If this pull request updates an existing mod, describe the changes below:

Converted the mod to a standalone Tool Mod running in windhawk.exe to prevent explorer.exe crashes.

Completely replaced the CPU-heavy GDI+ rendering engine with hardware-accelerated Direct2D for zero-CPU GPU drawing.

Rewrote the rendering engine to generate a solid procedurally-generated 2D polygon mesh, entirely eliminating alpha-blending artifacts at trail segment joints.

Replaced the broken gradient shader with unified GeometryGroup mesh welding and D2D1_FILL_MODE_WINDING to ensure self-intersecting loops seamlessly merge like liquid instead of hollowing out or stacking dark opacity spots.

Removed the invasive SetSystemCursor global hook so it no longer risks permanently hiding the OS cursor on a crash.

Dropped the timeBeginPeriod timer resolution hijack to fix unnecessary system usage.

Implemented per-monitor DPI awareness to fix scaling issues on mixed-monitor setups.

Optimized the rendering loop to allocate the screen-sized bitmap only on resolution changes rather than every single frame.

Added logic to physically hide the overlay window when idle so it doesn't block exclusive fullscreen presentation.

Added proper COM and memory cleanup on unload.

Stripped WS_EX_TOPMOST and added WS_EX_NOACTIVATE to the overlay window to prevent the auto-hide taskbar from glitching out when the smear triggers.

Comment thread mods/cursor-motion-blur.wh.cpp Outdated
// @name Cursor Smear Frames
// @description Adds high-speed cartoon motion blur to your mouse pointer.
// @version 2.0
// @author TheatriCHris

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Typo? It was TheatriChris before.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oop yeah I missed a few things here, I'll give it one more go for the day.

Fixed header typos and missed edits
@m417z m417z merged commit ccbe825 into ramensoftware:main Jul 8, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants