Skip to content

Add Mini Wallpaper mod#4423

Open
Mirochill wants to merge 2 commits into
ramensoftware:mainfrom
Mirochill:add-mini-wallpaper
Open

Add Mini Wallpaper mod#4423
Mirochill wants to merge 2 commits into
ramensoftware:mainfrom
Mirochill:add-mini-wallpaper

Conversation

@Mirochill

@Mirochill Mirochill commented Jun 14, 2026

Copy link
Copy Markdown

Adds the Mini Wallpaper Windhawk mod as a dedicated tool-process mod.

Validation performed locally:

  • python .github\pr_validation.py mods\mini-wallpaper.wh.cpp Mirochill
  • python scripts\compile_mod.py -w "C:\Program Files\Windhawk" -f mods\mini-wallpaper.wh.cpp -o32 %TEMP%\pr-mini-wallpaper_32.dll -o64 %TEMP%\pr-mini-wallpaper_64.dll -oarm64 %TEMP%\pr-mini-wallpaper_arm64.dll

Notes:

  • Hosts a wallpaper window behind desktop icons via WorkerW.
  • Plays videos through DirectShow, GIF/images through GDI+, and can cache optimized MP4 files with FFmpeg.
  • The helper process is launched automatically by Windhawk using the tool-mod pattern.

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
    • The submitter, with AI assistance
    • Claude
    • ChatGPT
    • 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.

@Mirochill

Mirochill commented Jun 14, 2026

Copy link
Copy Markdown
Author

Local validation is complete on my side.

Latest update: video playback now applies the same stretchMode (cover/contain) logic as images and GIFs by using DirectShow's IBasicVideo::GetVideoSize and sizing the child video window accordingly.

Validation:

  • python .github\pr_validation.py mods\mini-wallpaper.wh.cpp Mirochill
  • Compiles with installed Windhawk 1.7.3:
    python scripts\compile_mod.py -w "C:\Program Files\Windhawk" -f mods\mini-wallpaper.wh.cpp -o32 %TEMP%\pr-mini-wallpaper_32.dll -o64 %TEMP%\pr-mini-wallpaper_64.dll -oarm64 %TEMP%\pr-mini-wallpaper_arm64.dll
  • Also compiles with portable Windhawk 1.6.1:
    python scripts\compile_mod.py -w %TEMP%\windhawk-ci-compat\1.6.1 -f mods\mini-wallpaper.wh.cpp -o32 %TEMP%\compat-161-mini-wallpaper_32.dll -o64 %TEMP%\compat-161-mini-wallpaper_64.dll -oarm64 %TEMP%\compat-161-mini-wallpaper_arm64.dll

The GitHub Actions runs currently show action_required; I don't have repository admin rights to approve/rerun them from the fork PR.

@Mirochill Mirochill force-pushed the add-mini-wallpaper branch from 0c887f2 to bfae357 Compare June 14, 2026 22:03
@m417z

m417z commented Jun 15, 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.


Nice mod — a local video/GIF/image wallpaper behind the desktop icons is a genuinely useful thing to have. The main thing to resolve is the process model; there are also two real stability bugs in the in-Explorer hosting and a couple of dead settings.

1. Run this in a dedicated process, not inside explorer.exe. The PR description says this is "a dedicated tool-process mod," but the code doesn't actually do that: @include is explorer.exe, the wiki launcher (LaunchToolModProcess/EntryPoint_Hook/CreateProcessInternalW/the -tool-mod/-service arg parsing) is present but disconnected and dead, and Wh_ModInit instead runs everything in-process behind a home-grown shell-process detector (ShouldRunInThisProcessIsShellExplorerProcessCurrentProcessOwnsWindow/CommandLineHasNoArguments).

This mod installs no function hooks in explorer.exe — it only creates a window, SetParents it under WorkerW, and drives DirectShow/VMR9/D3D9/GDI+ and an external ffmpeg.exe. That is exactly the "mods as tools" profile, and it's the maintainer's single most-repeated request: heavy, crash-prone work like a DirectShow graph or an FFmpeg transcode should not live in the shell, because a crash or hang there restarts all of Explorer. The WorkerW SetParent trick works perfectly well cross-process (it's how Wallpaper Engine / Lively run as separate processes), so injecting into the shell buys nothing here.

Recommended fix — finish the tool-mod conversion you already started:

  • @include windhawk.exe.
  • Wire Wh_ModInit/Wh_ModAfterInit/Wh_ModSettingsChanged/Wh_ModUninit to the verbatim wiki launcher (keep it byte-for-byte identical so review is easy) and route to your WhTool_ModInit/WhTool_ModSettingsChanged/WhTool_ModUninit. See the launcher block at the bottom of explorer-folder-hover-menu.wh.cpp — it's a direct copy of the wiki snippet.
  • Delete the now-redundant ShouldRunInThisProcess, IsShellExplorerProcess, CurrentProcessOwnsWindow, CommandLineHasNoArguments, and GetToolProcessKind — the framework's launcher already handles single-instance and process selection (no need to hand-roll the mutex/argument logic). The Wh_ModUninitExitProcess(0) that the wiki pattern uses also makes the two teardown bugs below disappear for free.

(For contrast: the maintainer's own behind-icons mod desktop-live-overlay.wh.cpp does use @include explorer.exe — but it justifies in-process residence by hooking CreateWindowExW to track WorkerW recreation. There's no equivalent justification here.)

2. The window class is never unregistered, and ERROR_CLASS_ALREADY_EXISTS is treated as success. RegisterWallpaperWindowClass registers MiniWallpaperWindhawkWindow with hInstance = GetModuleHandleW(nullptr) (the host EXE) and a lpfnWndProc that points into the mod DLL, and nothing ever calls UnregisterClass. When the mod unloads, the window is destroyed but the class registration lingers in the host process with a lpfnWndProc pointing into the now-unmapped mod image. On the next load (toggle/update/reload), RegisterClassExW fails with ERROR_CLASS_ALREADY_EXISTS, the function returns true anyway, and CreateWindowExW then builds a window on the stale class whose WndProc is dangling → crash / arbitrary-code execution on the first message. This is the #4000 anti-pattern. Fix: register against the mod's own module handle, UnregisterClass(kWallpaperWindowClass, hInstance) in teardown, and don't swallow ERROR_CLASS_ALREADY_EXISTS. (Converting to a tool mod that ExitProcess(0)s on uninit, per #1, also resolves this, since the registration dies with the process.)

3. FFmpeg transcode blocks the message-loop thread (INFINITE) and crashes on unload mid-transcode. PrepareWallpaper runs on the worker/UI thread and RunHiddenProcess does WaitForSingleObject(processInfo.hProcess, INFINITE). While FFmpeg transcodes a large video (potentially minutes), the wallpaper window's message loop is frozen — no painting, no tray menu, no settings reload. Worse, if the user disables the mod during a transcode, the worker thread can't process the WM_QUIT, so WhTool_ModUninit's WaitForSingleObject(g_workerThread, 10000) times out, CloseHandle is called on a still-running thread, the mod DLL is unloaded, and the worker keeps executing mod code once FFmpeg returns → crash. Do the transcode off the UI thread (or wait on the FFmpeg process with a timeout/cancel that's observed by uninit), and make teardown actually join the work it started. The tool-mod ExitProcess(0) from #1 removes the crash, but the frozen-UI-during-transcode part is worth fixing regardless.

Optional improvements

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

  • Dead scaffolding (tie-in with I LOVE IT #1). LaunchToolModProcess, EntryPoint_Hook, g_isToolModProcessLauncher, and g_toolModProcessMutex are all unused — note that g_toolModProcessMutex is never created (no CreateMutex), so its CloseHandle in WhTool_ModUninit is a no-op — and the ToolProcessKind::CurrentToolMod/OtherToolMod/Excluded branches are unreachable given the current @include. Either wire this up as the real launcher (I LOVE IT #1) or delete it.

  • crf setting has no effect. It's read and clamped but only feeds the cache fingerprint (OptimizedFileName); the actual encode is a fixed -c:v wmv2 -b:v 6M. The $description itself says "Legacy quality value... WMV cache output uses a fixed bitrate." A knob that changes nothing but cache identity is confusing — the maintainer routinely asks to drop no-effect settings. Remove it (and have the fingerprint reflect only inputs that actually change the output).

  • Unused libraries / includes. There are no Dwm* or Direct3D*/Direct3DCreate9 calls, so -ldwmapi and -ld3d9 (and the #include <dwmapi.h>) appear unnecessary; -loleaut32 also looks unused. Trim the @compilerOptions and includes to what's actually referenced (<d3d9.h> may still be needed for the vmr9.h types — verify).

  • GetStringSettingValue null check. Wh_GetStringSetting never returns NULL (it returns L""), so the value ? value : L"" guard is dead. You could also replace the manual get/Wh_FreeStringSetting pairs with the RAII WindhawkUtils::StringSetting.

  • README has no visual. This is an inherently visual mod (it shows a wallpaper). A short screenshot/GIF in the README goes a long way for a wallpaper mod — recommend adding one (Imgur or raw.githubusercontent.com only).

Functionality notes

Non-critical observations about the feature behavior itself.

  • External FFmpeg dependency. The optimization path shells out to ffmpeg.exe (configured path or SearchPathW/PATH) to produce a WMV cache, with optimizeMedia: true by default. Windhawk mods are normally self-contained, so depending on a user-installed external executable is unusual and the maintainer may push back on it — it's worth justifying. It degrades gracefully (falls back to the source file when FFmpeg is absent), so it's not a correctness problem, but consider whether the transcode is needed at all: DirectShow can usually play MP4/MKV directly given the user's installed codecs. (Minor: SearchPathW(nullptr, L"ffmpeg.exe", …) also searches the current directory, so it can pick up an unexpected ffmpeg.exe — prefer an absolute/validated path.)

  • autoOpenPicker: true default is intrusive. With no configured/stored/default wallpaper, the mod pops a modal "Choose wallpaper" dialog automatically on the desktop at startup — which would recur on every Explorer start (or every dedicated-process start) until a file is set. Consider defaulting this off and letting the user pick from the tray, or only opening it once.

  • Mixed-DPI / multi-monitor. Sizing/scaling is driven off the virtual-screen metrics and one window stretched across the whole virtual desktop. That's the conventional approach for wallpaper hosts, but on mixed-DPI multi-monitor setups the cover/contain math can be slightly off per monitor. No clean alternative without per-monitor surfaces — just an FYI.

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.

3 participants