Skip to content

!Net - #7372

Open
k-ashix wants to merge 27 commits into
ramensoftware:mainfrom
k-ashix:main
Open

!Net#7372
k-ashix wants to merge 27 commits into
ramensoftware:mainfrom
k-ashix:main

Conversation

@k-ashix

@k-ashix k-ashix commented Nov 13, 2026 •

Copy link
Copy Markdown
Contributor

Submitting !Net — Per-App Kill Switch (not-net), a Windhawk Tool Mod (@include windhawk.exe) for instant per-application internet control using Windows Filtering Platform dynamic sessions (FWPM_SESSION_DYNAMIC), foreground-app hotkeys (Ctrl+Shift+1), and a focus-aware Cycle Mode scheduler with an edge/corner pill and companion clock widget.

If this pull request updates an existing mod, describe the changes inside the changelog markers:

  • Initial release of !Net — Per-App Kill Switch (1.2026.1310).
  • Per-app and child-process network blocking (Offline, Outbound, Inbound) via WFP dynamic sessions (FWPM_SESSION_DYNAMIC), plus read-only detection of existing Windows Defender Firewall blocks (Blocked · FW).
  • Focus-aware Cycle Mode scheduler (Blocked → Allowed) with a proximity-revealed vector clock widget beside the edge/corner pill (scheduler_only or navigate_pill), free drag snapping across edges and safe corners (TopLeft, BottomLeft, BottomRight), and optional Follow cursor monitor.
  • Safety locks for critical Windows processes, VPN/RDP warnings, fullscreen/capture hiding, and global hotkeys (Ctrl+Shift+1, Ctrl+Alt+Shift+M, Ctrl+Alt+Shift+R).

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.

k-ashix added 22 commits May 8, 2026 13:54
Add Taskbar Quick Pin Dock v2.0.0

- Initial submission of Taskbar Quick Pin Dock mod (v2.0.0)
- Persistent mini icon dock anchored left of Start button
- Drag to pin, drag to reorder, drag rope snap to unpin
- Dust vanish animation effect on unpin
- Triple-tap L lock/unlock protection
- Scroll-wheel navigation for >10 pinned apps
- Dynamic dock sizing, rounded corners, and frosted glass overlay
- Multi-monitor mirrored read-only dock support (Beta)
###What's new in v2.5.0:
- Code hygiene & stability
- Implement drag intent pipeline
- Delay expensive resolver work until dock intent
- Reduce unnecessary processing during normal clicks
- General stability improvements
- Startup ownership race fixed 
- Unsupported layout fixed
- Both dock windows hidden on unsupported layout.
- Drag processing stops when layout is unsupported
- TOPMOST refresh no longer forces hidden windows visible.   
- Rename-dialog shutdown hang fixed
- Rainbow mode removed  
- Glass setting removed   -  Will be added later
- Fix deferred startup ownership probing when Shell_TrayWnd is unavailable
- Fix unsupported-layout detection and suspend dock interaction
- Keep overlay/input windows hidden until valid ownership and geometry exist
- Prevent TOPMOST maintenance from re-showing hidden windows
- Remove non-functional settings and dead feature paths
- Fix rename-dialog WM_QUIT handling during unload/reload
- Add regression coverage for drag, pin, unpin, and dock lifecycle paths
- Tool-mod migration skipped 

Tool-mod migration was investigated but remains out of scope for this PR. Multiple prototypes compiled, but the dedicated-process UI did not initialize and produced no useful runtime diagnostics, so the existing explorer-hosted architecture was retained.
🪶 Leaner tool-mod runtime. — General optimisation of the tool-mod process.
✨ Drag-to-pin feedback layer. — A glow bloom shows GREEN ("there's room") or RED ("dock full") while you drag an app toward the dock.
🖥️ Cleaner fullscreen & Snipping Tool handling. — The dock hides smoothly under fullscreen apps and the Windows native Snipping Tool overlay, with anti-flicker so it reappears seamlessly.
🎯 Refined feedback behaviour. — Dragging or deleting an already-pinned icon near the dock now stays calm in BOTH pin-limit states (dock full or with room) — no false "dock full" flash for an app that is already pinned.
🧱 New "Hide dock outline" toggle (hideDockBorder). — Turn off the thin grey Windows 11 outline drawn around the dock. Purely cosmetic; the dock, icons and rounded corners are unchanged.
🎬 Two lock/unlock flash animations, with a toggle (enableLockAnimation). — OFF: a brief highlight on the dock's real edge. ON: a loading-style glow sweeps left→right across the whole dock. Colours unchanged (gold when locking, green when unlocking).
🔧 Some more stability & polish improvements.
@windhawk-reviewer windhawk-reviewer Bot added the waiting-for-author The author's turn: request an AI review, or respond to one that was posted. label Nov 13, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Thanks for the pull request! This repository uses a two-stage review: an AI review that you run yourself, followed by a human review.

To get started, comment /ai-review. Once you're happy with the result, comment /ready-for-reviewer to hand it over to a human reviewer.

See the pull request review process for the full details.

@k-ashix

k-ashix commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

/ai-review Submitting !Net (not-Net).

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Nov 13, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

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

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

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


This is a large tool mod (6.5k lines, AI-assisted) with real stability problems: a guaranteed UI hang, data races between threads, and a persistent firewall write. It doesn't duplicate an existing mod. The closest one, Net-Toggle, is a kill switch for the whole machine, while this one blocks per app through WFP.

1. The UI thread deadlocks whenever the VPN / Remote Desktop warning appears. ApplyBlock shows MessageBoxW while it holds g_settingsMtx (L1474–1486). The message box runs a modal loop on the UI thread, and that loop keeps dispatching WM_TIMER and WM_PAINT to g_hWnd. Within a second, TIMER_FULLSCREEN (L6070) or PaintFloatingWindow (L2961, L3180) tries to lock g_settingsMtx again on the same thread. std::mutex isn't recursive, so the thread hangs for good. The panel, the hotkeys and the message box all freeze, and only disabling the mod recovers. It happens with the default settings (warnOnVpnOrRemote: true) as soon as someone toggles, hotkeys or starts a cycle on any app in kRemote (AnyDesk, TeamViewer, mstsc, Tailscale, …). Fix: copy the two flags under the lock, release it, then ask:

bool lockCrit, warnVpn;
{
    std::lock_guard<std::mutex> lk(g_settingsMtx);
    lockCrit = g_settings.lockSystemProcesses;
    warnVpn = g_settings.warnOnVpnOrRemote;
}
if (lockCrit && IsCriticalSystemExe(low)) { ... }
if (warnVpn && askUser && IsVpnOrRemoteExe(low)) { /* MessageBoxW, no lock held */ }

Also add the askUser flag so that automatic cycle transitions never prompt. The user already confirmed when the cycle started, and today the scheduler thread would pop the dialog again on every Blocked phase (see the next item).

2. The cycle scheduler and pipe threads modify state that only the UI thread should touch. CycleSchedulerThreadProc (L2044–2057) and the pipe thread (through HandleCyclePipeCommand → StartCycleForApp / StopCycleForApp) call ApplyBlock / ApplyAllow directly. Those functions:

  • write the g_statusBanner std::wstring (L1471, L1477, L1521, L1523, L1539, L1550, L1560) while the UI thread reads it during paint (L3116–3129). A reallocation during that read is a use-after-free. With Cycle Mode phases as short as 100 ms, this race runs constantly.
  • read g_excludedExes (L1470, L1712) while the UI thread inserts into and erases from it (L3513, L3517). That's undefined behavior, and a rehash crashes it.
  • write g_needsElevation and g_pillPulse, and call SetTimer(g_hWnd, …) (L1345). SetTimer fails on another thread's window, so the pulse never animates from those threads.
  • show the VPN MessageBoxW from a worker thread, owned by the UI thread's window.

Fix: keep ApplyBlock / ApplyAllow on the UI thread. The scheduler can queue its PendingActions under g_cycleMtx and PostMessageW(g_hWnd, WM_APP_CYCLE_APPLY, 0, 0), and the UI handler applies them. Alternatively, split out a WFP-only helper that touches nothing but g_blocks / g_wfpEngine under g_engineMtx, and let the UI thread update the banner and pulse.

3. Persistent change: "Persistent Rules" are written to Windows Defender Firewall and are never removed. For each persistentRules[] entry, AddPersistentFirewallRule (L1159–1213) adds the rules ExclaimNet_<exe>_<hash>_OUT and _IN through INetFwPolicy2. The firewall service stores them under HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules. SyncPersistentRules (L2242–2269) deletes every ExclaimNet_* rule and re-adds them at tool start (L6383) and on every settings change (L6282). WhTool_ModUninit (L6408–6435) doesn't touch them. So after the mod is disabled or removed, or Windhawk is uninstalled, the apps stay blocked for good. An entry removed from the settings while !Net isn't running also stays in the firewall. It's opt-in (the default entry is empty) and the README mentions the sync, but it is still a persistent change.

  • Suggested fix: apply the persistentRules entries as the same FWPM_SESSION_DYNAMIC filters at tool start. They're already stored in the mod's settings, so they come back every time !Net starts and disappear when it stops, which is how every other block in the mod already works.
  • If you keep the firewall write, please justify it in the PR, remove the ExclaimNet_* rules in WhTool_ModUninit, and state in the README that the rules stay behind whenever !Net doesn't shut down cleanly or is removed while it isn't running. The maintainer will decide whether that's acceptable.

4. The "Run as Admin" relaunch works around the tool-mod launcher. RelaunchElevated (L673–697) uses ShellExecuteExW("runas") to start windhawk.exe -tool-mod "not-net" itself:

  • The original process doesn't exit. WM_CLOSE only ends the UI thread, and the scheduler and pipe threads keep a windowless windhawk.exe alive until the mod is disabled. That process also holds the single-instance pipe (nMaxInstances = 1), so the elevated copy's pipe thread loops on CreateNamedPipeW / Sleep(200) forever (L2169–2176).
  • ReleaseMutex(g_toolModProcessMutex) (L680) runs on the UI thread, but the mutex is owned by the main thread that created it in Wh_ModInit, so the call fails. The relaunch only works because CloseHandle drops the last handle. This also changes the state of the launcher snippet.
  • Every sign-in starts the non-elevated instance again, so each session needs a click and a UAC prompt before any blocking works.
  • On Windhawk 2.0, elevation is a supported host: @include windhawk-mod-elevated.exe, with windhawk-mod.exe as the fallback (see Host processes and privilege levels). There, GetModuleFileNameW(nullptr) returns windhawk-mod.exe, so this relaunch isn't how an elevated host gets started.

Please use the elevated host and drop the self-relaunch. How 1.7.3 users should get elevation is a question for the maintainer. If the relaunch stays for 1.7.3, the original process at least needs to exit once the elevated one has started.

5. Remove the \\.\pipe\ExclaimNetCycle server (L2089–2224). Nothing in the mod is a client of it (the only connection is the shutdown wake-up at L2212), and the README doesn't document it. Yet it's a local control interface that runs in the elevated process for its whole lifetime: startCycle and stopCycle block and unblock arbitrary apps. Its commands also run on the pipe thread, which causes the races in item 2. If an external API is a goal, document it and secure it. Otherwise, delete it along with the wake-up hack in ShutdownCycleEngine.

6. The mod keeps its own log file outside Windhawk storage. LogAudit (L750–777) creates %AppData%\ExclaimNet\exclaimnet.log and appends a line on every block, allow, exclude and cycle start/stop. That includes every cycle phase change, which with 100 ms phases is up to about 20 lines per second, each one opening and closing the file. The file is never rotated and stays after the mod is removed. Wh_Log already covers diagnostics, so drop the file. If an audit trail is a real feature, put it under Wh_GetModStoragePath, make it an opt-in setting, and cap its size.

7. "Include child processes" (on by default) blocks shared executables everywhere on the system. CollectChildExePaths (L874–912) collects the executable paths of all descendant processes, and ApplyBlock (L1488–1515) adds an ALE_APP_ID filter for each one. The WFP app ID matches an executable path, not a process, so the block covers every instance of each collected path:

  • Blocking Windows Terminal or VS Code also blocks every powershell.exe, cmd.exe, git.exe and node.exe on the system.
  • Blocking an app that hosts WebView2 blocks msedgewebview2.exe for every app that uses it.

Only names in kCritical are skipped. The list shows those shared executables as Allowed, because g_blocks only holds the root (L2345–2361), and they can't be unblocked separately. Suggested fix: limit children to executables under the root's install folder, skipping %WINDIR% and shared runtimes. Show the extra executables in the row or its tooltip and mark them blocked. Consider turning the default off.

8. The tool-mod launcher isn't the wiki snippet verbatim (L6441–6554). Wh_ModInit is missing the session-0 check at the top. Wh_ModAfterInit uses CreateProcessW instead of CreateProcessInternalW with STARTF_FORCEOFFFEEDBACK, and a log message was reworded. Please replace the block with an unmodified copy of the snippet from Mods as tools.

9. The tray icon disappears after Explorer restarts. AddTrayIcon (L3357–3374) adds the icon once, and nothing handles RegisterWindowMessage(L"TaskbarCreated"). Re-add the icon when that message arrives. The README tells users to run !Net elevated, and UIPI drops the medium-integrity TaskbarCreated broadcast to an elevated window, so also allow it with ChangeWindowMessageFilterEx. Check whether the icon's WM_APP_TRAY_CALLBACK clicks reach the elevated instance too, and allow that message the same way if they don't. Reference: background-dimmer, TaskbarCreated handler and the filter call.

10. The excluded-apps list is lost once it grows past 4,095 characters. LoadExcludedApps reads excludedApps into a 4,096-character buffer (L701–702), but SaveExcludedApps (L713–720) writes the joined list with no limit. When the stored value is longer than the buffer, Wh_GetStringValue returns nothing rather than a partial value. That happens after roughly 40–60 full paths. All exclusions then disappear at the next start, and the next Exclude or Restore click overwrites the stored list with the shorter set in memory. Fix: store one value per item with numbered names and a count, as deviceCount / Device%dId do in audioswap, or cap the writer at what the reader accepts.

11. Several settings do nothing.

  • General.autoRestoreOnExit: FWPM_SESSION_DYNAMIC filters are deleted when the engine handle closes (L1001) or the process exits, so turning this off changes nothing (L6422–6429). Remove it. The README already says that blocks disappear on exit.
  • Pill.collapseDelayMs: it's read (L2621), but TIMER_COLLAPSE is never started, only killed (L5759, L5940). Either wire it up (for example, start the timer on WM_MOUSELEAVE) or remove it.
  • Behavior.fullscreenBehavior: pause ("Pause animations"): only Hide is ever checked (L6075), so Pause behaves like Allow.
  • persistentRules[].includeChildProcesses: it's saved into BlockState (L2264), but AddPersistentFirewallRule only adds rules for the executable itself.

12. Constant polling and unthrottled refreshes.

  • With Cycle Mode on, TIMER_WIDGET_PROXIMITY runs every 75 ms for as long as the pill is collapsed (L5703, L5999–6023). Each tick:

    • calls ComputeTargetRect twice, which reads posX / posY from Windhawk storage (L2839–2842) and copies the whole ModSettings struct;
    • copies the whole external-firewall map through IsExeEligibleForCycle (L5213).

    Keep the position in memory (load it once and write it when a drag ends), and look up the map under its lock instead of copying it.

  • RenderCycleWidgetLayered reads AppsUseLightTheme from the registry on every frame, including at 10 ms during animations (L5461–5466). Read it once and refresh it on WM_SETTINGCHANGE.

  • While the panel is open, every window created or destroyed anywhere on the desktop (L6297–6302) triggers a full RefreshAppList (L6233–6239). Each refresh takes a process snapshot, opens every process and queries its path, reads the TCP and UDP tables, and calls GetFileAttributes for each executable. Opening a browser fires dozens of these back to back. Debounce instead: restart a one-shot timer of about 300 ms on each event, and refresh when it fires.

Optional improvements

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

  • Winsock include: the #define WIN32_LEAN_AND_MEAN at L170–172 has no effect, because Windhawk includes <windows.h> before the mod's code. The #undef _WINSOCKAPI_ at L180 is a workaround for the resulting warning. Instead, add -DWIN32_LEAN_AND_MEAN to @compilerOptions (as net-toggle does) and drop both lines.

  • Unused dependencies: nothing uses -lws2_32, -luxtheme, -lpsapi or -lshlwapi, nor <uxtheme.h>, <psapi.h> or <shlwapi.h>.

  • AI leftovers:

    • the // CYCLE_MODE_*_PATCH_NN markers (L49, L228, L231, L304, …);
    • the unused CaptureBehavior enum (L305);
    • g_running, which is written but never read (L499);
    • (void)borderAccentPen; (L4286);
    • ApplyCaptureAffinity and the other SetWindowDisplayAffinity(…, WDA_NONE) calls, which do nothing;
    • the persistent branch of ApplyBlock (L1508–1509), which no caller ever takes.
  • Log prefix: Windhawk already adds the mod name to log lines, so the [!Net] prefix in Wh_Log calls isn't needed.

  • StringSetting: use WindhawkUtils::StringSetting instead of the raw Wh_GetStringSetting / Wh_FreeStringSetting pairs in LoadSettings. Wh_GetStringSetting never returns NULL, so the if (sEdge) / if (app) checks never fail.

  • Settings key case: the YAML groups are General, Pill, Behavior, Safety and Hotkeys, but the code reads general.…, pill.… and so on. This works only because settings lookups are currently case-insensitive. Match the case.

  • DPI awareness call: EnablePerMonitorDpiAwareness (L6309–6316) has no effect, because the tool process's manifest already sets PerMonitorV2.

  • Fixed-size buffers: swprintf_s into fixed buffers ends the process when the text doesn't fit:

    • MakePersistentRuleName (buf[256]) with an exe file name longer than about 230 characters;
    • GetAppIconCached (keyBuf[512]) with paths over about 505 characters (QueryProcessExePath accepts up to 520);
    • the pipe's line[512].

    Build these strings as std::wstring.

  • Setting description: Pill.panelEdgeOffsetPx says 0–12 px, but the code clamps to 0–16 (L2620).

  • README screenshot: the mod is mostly UI (pill, panel, clock widget, Cycle popup), so a screenshot or GIF would help users see what they're installing.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • Default hotkey: RegisterHotKey takes the default Ctrl+Shift+1 for the whole system. File Explorer uses that shortcut for the "Extra large icons" view, and Excel uses it for number formatting. A less common default, or none, would avoid breaking those.
  • Hide during capture: IsCaptureAppActive passes ScreenClippingHost and Microsoft.SnippingTool to FindWindowW as window class names. Those look like process and package names, so the check probably never matches. WDA_EXCLUDEFROMCAPTURE is defined but never used; SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE) would keep the windows out of captures directly.
  • Firewall rule paths in 32-bit Windhawk: in the 32-bit windhawk.exe (1.7.3), ExpandEnvironmentStringsW expands %ProgramFiles% to Program Files (x86) (L1299). A firewall rule written as %ProgramFiles%\App\app.exe for a 64-bit app therefore doesn't match, and the app isn't shown as "Blocked · FW". Under WOW64, substitute %ProgramW6432%.
  • Blocked · FW direction: only outbound rules count (dirMask & 1), so an app blocked only for inbound traffic by an existing rule isn't flagged.
  • IPv6 connections: GetPidsWithLiveNetwork reads only the IPv4 tables (AF_INET), so apps whose only connections are IPv6 don't count as having network activity in the Running filter.
  • Child processes started later: descendants are collected once, when the block is applied. Helpers with other executable names that start afterwards aren't blocked until the app is blocked again.
  • Settings button: OpenWindhawkSettingsUI launches GetModuleFileNameW(nullptr). In the elevated instance that opens the Windhawk UI elevated, and on 2.0 it's windhawk-mod.exe, which isn't the Windhawk UI.

Code overview

For the maintainer: a map of the mod and what it touches outside its own process. Descriptive only — anything that needs a change is listed above. Line numbers refer to the mod source at commit 6f76141. This mod is new, so everything below is new.

Code regions

  • Line 1–12: metadata — a tool mod (@include windhawk.exe) with WFP and firewall COM libraries.
  • Line 14–48: README.
  • Line 51–168: settings, in the groups General, Pill, Behavior, Safety, Hotkeys and the persistentRules array.
  • Line 170–260: includes, with a winsock workaround, plus WFP GUID constants missing from MinGW and DWM compat defines.
  • Line 262–482: constants (window classes, pipe name, messages, timers, hotkey IDs), enums, settings and data models, and animation helpers (MotionClock, PulseState).
  • Line 483–631: global state and forward declarations, including the Cycle Mode models.
  • Line 633–777: utilities:
    • string helpers;
    • FileExistsW, which turns off WOW64 file-system redirection;
    • IsProcessElevated;
    • RelaunchElevated (UAC relaunch of windhawk.exe -tool-mod);
    • excluded-apps storage;
    • LogAudit, which writes the log file in %AppData%.
  • Line 779–830: safety lists: critical system executables, system paths, VPN and remote-desktop executables.
  • Line 832–956: process and network discovery: process snapshot and exe paths, descendant-exe collection, TCP/UDP owner tables, PIDs with visible windows.
  • Line 958–1075: WFP engine. Opens an FWPM_SESSION_DYNAMIC session with a custom sublayer, adds ALE_APP_ID block filters at the ALE connect and recv-accept layers (v4/v6) for each exe, and removes them.
  • Line 1077–1336: Windows Defender Firewall COM. Adds, removes and clears all ExclaimNet_* rules, and scans existing block rules every 15 s for "Blocked · FW".
  • Line 1338–1563: high-level operations: status pulse, foreground sync (resolves UWP apps through ApplicationFrameHost), ApplyBlock (VPN warning, children, WFP or firewall), ApplyAllow.
  • Line 1566–2069: Cycle Mode scheduler. Holds the runtime state, handles start/stop/pause, snapshots, focus and resume, and runs the scheduler thread (Blocked/Allowed transitions, process-exit check every 500 ms).
  • Line 2071–2224: named-pipe IPC: command parser (startCycle, stopCycle, cycleStatus), pipe server thread, start and shutdown of the cycle engine.
  • Line 2226–2278: RestoreAllSessionBlocks, SyncPersistentRules (clears and re-adds the firewall rules), blocked count.
  • Line 2280–2449: RefreshAppList. Merges processes, windows, network use, blocks, external firewall rules and exclusions, then filters and sorts the list.
  • Line 2451–2526: icon cache, capture-tool and fullscreen detection.
  • Line 2528–2719: hotkey parsing and registration, opening the Windhawk UI, LoadSettings (including the dockEdgeOverride storage value).
  • Line 2721–2916: window geometry: rounded paths, DWM frame mode, target monitor, pill and panel rect (reads posX / posY from storage), UpdateWindowGeometry.
  • Line 2918–3351: PaintFloatingWindow, which draws the pill and the panel with GDI+ (header, filter chips, banner, app rows, hit rects).
  • Line 3353–3453: tray icon and its context menu, tooltip.
  • Line 3455–3569: hit testing and click actions: toggle, direction, exclude, filter chips, elevate.
  • Line 3572–5183: Cycle Mode popup: duration and clock field model, layout, painting, the custom run-mode dropdown window, keyboard input, OpenCyclePopupForApp.
  • Line 5186–5725: clock widget beside the pill: eligibility, navigation to the app, layered rendering, window procedure, reveal and sync logic.
  • Line 5727–6276: FloatingWndProc:
    • hover expand, drag and snap (saves the dock edge and position to storage), wheel scrolling;
    • timers: collapse, animation, hover sensor, widget proximity, outside click, fullscreen;
    • keyboard navigation, WM_HOTKEY, tray callback;
    • foreground, refresh and cycle messages, power resume, teardown.
  • Line 6278–6303: ControlWndProc, which reloads settings on the UI thread, and the WinEvent callback, which posts refreshes on foreground changes and window creation or destruction.
  • Line 6305–6378: UI thread: DPI awareness, COM STA, GDI+, window classes, the pill window and a message-only window, two SetWinEventHooks, the message loop, teardown.
  • Line 6380–6435: WhTool_ModInit, WhTool_ModSettingsChanged and WhTool_ModUninit. Uninit stops the cycle engine, closes the UI thread, restores session blocks and closes the WFP engine.
  • Line 6437–6554: tool-mod launcher boilerplate (modified from the wiki snippet).

Side effects of interest

  • Line 750–777: external file write: appends to %AppData%\ExclaimNet\exclaimnet.log, creating the folder. Runs on every block, allow, exclude and cycle event, and at init and uninit. The file stays after the mod is disabled or removed.

  • Line 2455–2468: external file read: extracts icons from the listed apps' executables with PrivateExtractIconsW while painting rows.

  • Line 1093–1213, 2242–2269: registry write (through the firewall API): adds or removes Windows Defender Firewall rules ExclaimNet_<exe>_<hash>_OUT/IN, which are stored under HKLM\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules. All ExclaimNet_* rules are deleted and re-added at tool start and on every settings change. They're not removed on unload and persist after the mod is disabled.

  • Line 1228–1336: registry read (through the firewall API): enumerates all firewall rules to find existing blocks; cached for 15 s, scanned on list refresh and hotkey use.

  • Line 5461–5466: registry read: HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme, on every widget render.

  • Line 962–1075: IPC: RPC to the Base Filtering Engine. Opens a dynamic WFP session, adds a sublayer, and adds and deletes block filters for each blocked exe and its descendant exes. The filters affect those executables system-wide and are removed when the session handle closes or the process exits. Runs on user toggle, hotkey, cycle transitions and pipe commands.

  • Line 2167–2195: IPC: named-pipe server \\.\pipe\ExclaimNetCycle (default security, PIPE_REJECT_REMOTE_CLIENTS). Accepts startCycle, stopCycle and cycleStatus from any local client that can open it, for the whole process lifetime.

  • Line 673–697: other reach outside the process: ShellExecuteExW with runas starts windhawk.exe -tool-mod "not-net" elevated when the user clicks Run as Admin.

  • Line 2572–2583: other reach outside the process: ShellExecuteW starts the current host exe to open the Windhawk UI, then FindWindowW(L"Windhawk") + SetForegroundWindow.

  • Line 836–956: other reach outside the process (read-only):

    • OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) and QueryFullProcessImageNameW on every process;
    • GetExtendedTcpTable / GetExtendedUdpTable;
    • EnumWindows across all processes (no PID filter, by design).

    Runs on every list refresh and when blocking with children; the per-cycle 500 ms check only snapshots processes.

  • Line 2554–2570: other reach outside the process: three global hotkeys through RegisterHotKey (defaults Ctrl+Shift+1, Ctrl+Alt+Shift+M, Ctrl+Alt+Shift+R).

  • Line 3357–3385: other reach outside the process: notification-area icon through Shell_NotifyIconW.

  • Line 6354–6357: other reach outside the process: two out-of-context SetWinEventHooks for the whole desktop (foreground through minimize-end, and object create/destroy).


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

Important: Each reply that was generated by AI must be disclosed by including the following text in the reply: "Generated by AI. Model: (ai-model-name)".

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Nov 13, 2026
@k-ashix

k-ashix commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

/ai-review !Net (not-Net).

README screenshot: the mod is mostly UI (pill, panel, clock widget, Cycle popup), so a screenshot or GIF would help users see what they're installing. - Handled - After Finishing Development - Til Skipped

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Nov 13, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

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

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

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


Thanks for the thorough update. Almost everything from the last round is fixed:

  • Threading: the VPN prompt no longer runs under g_settingsMtx, cycle transitions are queued to the UI thread, and the pipe server is gone.
  • Persistent change: the firewall rules are replaced with session WFP filters.
  • Elevation: the 2.0 elevated host is used, and the 1.7.3 relaunch now exits the original process. Whether that relaunch is acceptable on 1.7.3 is the maintainer's call.
  • Storage: the %AppData% log is gone, and exclusions are now stored as numbered values.
  • Child executables: they're now limited to the app's own folder, without %WINDIR% and shared runtimes.
  • Launcher and tray: the launcher matches the wiki snippet (formatting aside), and TaskbarCreated is handled.
  • Settings and performance: the dead settings are removed, and the refresh and polling costs are fixed.

One crash remains, in code that was already there last round and that I missed then:

1. Row clicks keep references into g_appList across a modal dialog, which can read freed memory. In HandleClickAction, the RowToggle branch holds const AppEntry& app = g_appList[hr.rowIndex] (L3245) while MessageBoxW asks "Stop the cycle and toggle manually?" (L3253). After that it reads app.displayPath and app.block.blocked (L3257–3270). It also passes app.displayPath by reference into ApplyBlock, which can show the VPN MessageBoxW (L1352) and then reads exePath again (L1388, L1409). RowDirection does the same at L3275–3285.

  • A message box runs a modal loop, and that loop keeps dispatching the panel's posted messages.
  • If the panel stays open (it was opened by hover and not yet clicked, or it's pinned), these handlers rebuild the list while the dialog is up:
    • WM_APP_CYCLE_APPLY (L5991);
    • the debounced refresh (L5746);
    • WM_APP_FOREGROUND_CHG (L5946).
  • The rebuild assigns g_appList = std::move(filtered) (L2168), which frees the vector that app points into.

The "Stop the cycle?" dialog is shown only for an app that's cycling, so a phase change during the dialog is the normal case. With the default 10 s / 5 s cycle, it takes only a few seconds before answering. The result is a toggle in the wrong direction, or a crash of the tool process, which drops every block at once. Fix: copy what you need before any modal UI, and re-read the live block state afterwards:

AppEntry app = g_appList[hr.rowIndex];  // copy, not a reference
...
bool blockedNow;
{
    std::lock_guard<std::mutex> lk(g_engineMtx);
    auto it = g_blocks.find(app.exePath);
    blockedNow = it != g_blocks.end() && it->second.blocked;
}

Also take exePath by value in ApplyBlock (or copy it before the MessageBoxW), so that no other caller can hand it a reference into the list.

Optional improvements

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

  • Uninit still calls ApplyAllow from the Windhawk thread: ShutdownCycleEngine calls StopAllCycles(true) (L1951), which runs ApplyAllow on the Windhawk engine thread while the UI thread is still running. That writes g_statusBanner and the pulse state from the wrong thread. RestoreAllSessionBlocks already runs after the UI thread is joined (L6170) and removes every filter, so StopAllCycles(false) is enough there.
  • Leftover tray icon after the 1.7.3 relaunch: RelaunchElevated calls ExitProcess(0) without removing the tray icon (L6197). The old icon stays in the notification area until the mouse passes over it. Call RemoveTrayIcon() first.
  • Leftovers from the refactor:
    • if (true) { (L1292, L3209);
    • the unused borderAccentPen (L3895);
    • BlockState::persistent, which is now always false, so the · Locked label (L2940), the !app.block.persistent check (L3284) and the persistent branch in RestoreAllSessionBlocks (L1965) are dead;
    • ApplyAllow's unused bool parameter;
    • the "Pipe IPC" section title (L1459);
    • the stray indentation at L318, L325, L540 and L3348;
    • the fallback to the legacy excludedApps value (L677–686), which no released version ever wrote.
  • Setting name: the key is still persistentRules, though the entries are now "Auto-Blocked Apps" applied per session. Since the mod isn't released yet, renaming it (for example to autoBlockedApps) costs nothing.
  • Text that no longer matches the defaults:
    • Pill.followCursorMonitor now defaults to true, but its description still says "(off by default)";
    • Cycle Mode is now on by default, but the README still says "(enable in Settings)" and "after enabling Turn on Cycle Mode scheduler".
  • README screenshot: you mentioned you'll add one once development is finished, which is fine.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • Default hotkey: the default Ctrl+Shift+1 is still registered for the whole system. It takes over File Explorer's "Extra large icons" shortcut and Excel's number-format shortcut.
  • Auto-blocked apps after settings changes: SyncPersistentRules runs on every settings change (L6040), even an unrelated one such as toggling tooltips. It blocks every listed app again, including one the user just allowed from the panel. An entry removed from the list also stays blocked until !Net restarts or the user clicks Allow. Applying only the entries that changed, and unblocking the removed ones, would match what users expect.
  • Rows for child executables: child executables are now shown in the parent's tooltip, but their own rows still say "Allowed", because g_blocks only holds the root. Clicking Allow on such a row does nothing, since ApplyAllow finds no entry for it.
  • Children started later: SyncLateChildBlocks runs only from TIMER_DEBOUNCE_REFRESH (L5746), which is set only while the panel is open (L5958). Helpers started while the panel is collapsed aren't blocked until it's opened.
  • Stale cycle action: a SwitchBlock that the scheduler queued just before the user stops that cycle is still applied in WM_APP_CYCLE_APPLY, which re-blocks the app with no cycle running. The window for this is very small. Skipping SwitchBlock / SwitchAllow actions whose exe is no longer in g_cycles closes it.

Code overview

For the maintainer: a map of the mod and what it touches outside its own process. Descriptive only — anything that needs a change is listed above. Line numbers refer to the mod source at commit 8775efc. This mod is new, so everything below is new.

Code regions

  • Line 1–14: metadata. A tool mod for the windhawk-mod-elevated.exe, windhawk-mod.exe and windhawk.exe hosts, with -DWIN32_LEAN_AND_MEAN and the WFP and firewall COM libraries.
  • Line 16–50: README.
  • Line 52–164: settings, in the groups General, Pill, Behavior, Safety, Hotkeys and the persistentRules array (shown as "Auto-Blocked Apps").
  • Line 166–242: includes, WFP GUID constants missing from MinGW, DWM compat defines.
  • Line 243–460: constants, enums, settings and data models (BlockState also tracks the blocked child executables), animation helpers.
  • Line 461–620: global state, including the cached pill position and the light-theme flag (RefreshLightThemeCache), plus forward declarations and the Cycle Mode models.
  • Line 621–727: utilities:
    • string helpers;
    • FileExistsW, which turns off WOW64 file-system redirection;
    • IsProcessElevated;
    • excluded-apps storage (numbered excluded_N values with excludedCount, falling back to the old excludedApps value);
    • LogAudit, which now only calls Wh_Log.
  • Line 729–780: safety lists: critical system executables, system paths, VPN and remote-desktop executables.
  • Line 782–951: process and network discovery:
    • process snapshot and exe paths;
    • CollectChildExePaths, limited to the app's folder and skipping %WINDIR% and shared runtimes;
    • TCP/UDP owner tables for IPv4 and IPv6;
    • PIDs with visible windows.
  • Line 953–1070: WFP engine. Opens an FWPM_SESSION_DYNAMIC session with a custom sublayer, adds ALE_APP_ID block filters at the ALE connect and recv-accept layers (v4/v6) for each exe, and removes them.
  • Line 1072–1202: read-only scan of existing Windows Firewall block rules (cached 15 s, with the WOW64 %ProgramFiles% fix).
  • Line 1204–1456: high-level operations:
    • status pulse and foreground sync;
    • ApplyBlock (VPN prompt outside any lock, askUser flag, child executables);
    • SyncLateChildBlocks;
    • ApplyAllow.
  • Line 1458–1941: Cycle Mode scheduler: runtime state, the pending-action queue, start/stop/pause, snapshots, focus and resume handling. The scheduler thread queues Blocked/Allowed transitions and posts WM_APP_CYCLE_APPLY to the UI thread.
  • Line 1943–2000: cycle engine start and shutdown; RestoreAllSessionBlocks; SyncPersistentRules, which applies the auto-block entries as session WFP filters; blocked count.
  • Line 2002–2171: RefreshAppList. Merges processes, windows, network use, blocks, external firewall rules and exclusions, then filters and sorts the list.
  • Line 2173–2250: icon cache, capture exclusion (WDA_EXCLUDEFROMCAPTURE on the mod's windows), fullscreen detection.
  • Line 2252–2429: hotkey parsing and registration; OpenWindhawkSettingsUI, which focuses the Windhawk window or starts windhawk.exe; LoadSettings.
  • Line 2431–2633: window geometry: rounded paths, DWM frame mode, target monitor, pill and panel rect (uses the cached posX / posY), UpdateWindowGeometry.
  • Line 2635–3072: PaintFloatingWindow, which draws the pill and the panel with GDI+. The toggle tooltip lists the blocked child executables.
  • Line 3074–3174: tray icon and its context menu, tooltip.
  • Line 3176–3290: hit testing and click actions: toggle, direction, exclude, filter chips, elevate.
  • Line 3293–4898: Cycle Mode popup: duration and clock field model, layout, painting, the run-mode dropdown window, keyboard input, OpenCyclePopupForApp.
  • Line 4900–5435: clock widget beside the pill: eligibility, navigation to the app, layered rendering, window procedure, reveal and sync logic.
  • Line 5437–6034: FloatingWndProc:
    • re-adds the tray icon on TaskbarCreated, and its UIPI filters let that message and the tray callback through;
    • hover expand, drag and snap, scrolling;
    • timers, including the debounced refresh that also blocks late children;
    • WM_HOTKEY, WM_SETTINGCHANGE, WM_APP_CYCLE_APPLY, power resume, teardown.
  • Line 6036–6061: ControlWndProc, which reloads settings and re-applies the auto-block entries, and the WinEvent callback, which posts refreshes.
  • Line 6063–6128: UI thread: COM STA, GDI+, window classes, the pill window and a message-only window, two SetWinEventHooks, the message loop, teardown.
  • Line 6130–6176: WhTool_ModInit, WhTool_ModSettingsChanged and WhTool_ModUninit.
  • Line 6178–6204: RelaunchElevated, which relaunches the current host through UAC and calls ExitProcess on success.
  • Line 6206–6367: tool-mod launcher snippet (matches the wiki apart from formatting).

Side effects of interest

  • Line 2177–2199: external file read: extracts icons from the listed apps' executables with PrivateExtractIconsW while painting rows.

  • Line 473–479: registry read: HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme, at window creation and on WM_SETTINGCHANGE.

  • Line 1085–1202: registry read (through the firewall API): enumerates all Windows Firewall rules through INetFwPolicy2 to find existing blocks. Read-only, cached 15 s.

  • Line 957–1070: IPC: RPC to the Base Filtering Engine. Opens a dynamic WFP session, adds a sublayer, and adds and deletes block filters for each blocked exe and its child executables in the same folder.

    • The filters affect those executables system-wide.
    • They're removed when the session handle closes or the process exits.
    • Runs on user toggle, hotkey, cycle transitions, the auto-block entries (at start and on every settings change), and the late-child sync during debounced refreshes.
  • Line 6182–6204: other reach outside the process: ShellExecuteExW with runas starts the current host exe with -tool-mod "not-net" elevated when the user clicks Run as Admin, then the original process exits.

  • Line 2296–2318: other reach outside the process: FindWindowW(nullptr, L"Windhawk") + SetForegroundWindow, or ShellExecuteW of windhawk.exe, from the settings button.

  • Line 786–951: other reach outside the process (read-only):

    • OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) and QueryFullProcessImageNameW on every process;
    • GetExtendedTcpTable / GetExtendedUdpTable;
    • EnumWindows across all processes (no PID filter, by design).

    Runs on list refreshes, when blocking with children, and every 500 ms for each active cycle (process snapshot only).

  • Line 2278–2294: other reach outside the process: three global hotkeys through RegisterHotKey (defaults Ctrl+Shift+1, Ctrl+Alt+Shift+M, Ctrl+Alt+Shift+R).

  • Line 3078–3106, 5446–5447: other reach outside the process: notification-area icon through Shell_NotifyIconW. ChangeWindowMessageFilterEx allows TaskbarCreated and the tray callback message from lower-integrity processes.

  • Line 6104–6107: other reach outside the process: two out-of-context SetWinEventHooks for the whole desktop (foreground through minimize-end, and object create/destroy).


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

Important: Each reply that was generated by AI must be disclosed by including the following text in the reply: "Generated by AI. Model: (ai-model-name)".

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Nov 13, 2026
@k-ashix

k-ashix commented Nov 13, 2026

Copy link
Copy Markdown
Contributor Author

/ai-review !Net (not-Net).

README screenshot: the mod is mostly UI (pill, panel, clock widget, Cycle popup), so a screenshot or GIF would help users see what they're installing. - Handled - After Finishing Development - Til Skipped

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-ai-review An AI review was requested and is being prepared. and removed waiting-for-author The author's turn: request an AI review, or respond to one that was posted. labels Nov 13, 2026
@windhawk-reviewer

Copy link
Copy Markdown

Submission review

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

Remember: The AI reviewer can be wrong - it may misread code, flag correct code as broken, or suggest changes that make things worse. Treat its findings as suggestions to verify, not instructions to follow blindly. You're responsible for the code you submit, so if a finding doesn't hold up, say so instead of changing working code to satisfy it.

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


Thanks, this round fixes everything from the last review:

  • Row clicks: they now copy the AppEntry, re-read the live block state after the "Stop the cycle?" prompt, and ApplyBlock / ApplyAllow take the path by value.
  • Child executables: child rows now show as blocked and can be allowed, and late children are also picked up while the panel is collapsed.
  • Auto-blocked apps: only entries that changed are re-applied, and removed entries are unblocked.
  • Cycle actions: a queued action for a stopped cycle is now skipped.
  • Shutdown and relaunch: uninit no longer calls ApplyAllow from the Windhawk thread, and the relaunch removes the tray icon before exiting.
  • Defaults and text: the default hotkey is now Ctrl+Alt+Shift+1, the setting is renamed to autoBlockedApps, the refactor leftovers are removed, and the descriptions and README match the defaults.

One more instance of last round's crash is left, in the right-click menu:

1. The row context menu keeps a reference into g_appList across TrackPopupMenu. In WM_RBUTTONUP, const AppEntry& app = g_appList[hr->rowIndex] (L5613) is held while TrackPopupMenu (L5622) runs its modal loop. Afterwards the code reads app in OpenCyclePopupForApp(app) and StopCycleForApp(app.displayPath, true) (L5624–5626). The menu loop keeps dispatching the panel's posted messages, and the panel stays open, because SetForegroundWindow(hwnd) runs just before the menu (L5621). So any of these can rebuild the list, through g_appList = std::move(filtered) (L2191), while the menu is open:

  • WM_APP_CYCLE_APPLY (L6039);
  • the debounced refresh (L5782–5789);
  • WM_APP_FOREGROUND_CHG (L5982).

"Stop Cycle" appears only for an app that's cycling, so a phase change while the menu is open is the normal case. Picking an item then reads freed memory, which can stop the wrong cycle or crash the tool process. It's the same one-line fix as in HandleClickAction:

AppEntry app = g_appList[hr->rowIndex];  // copy, not a reference
Optional improvements

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

  • Leftovers:
    • the unused borderAccentPen (L3931);
    • the bare { … } blocks left where if (true) was removed (L1278, L3231);
    • the stray indentation at L318, L325, L539 and L3384;
    • PersistentRuleCfg, which could now be named after the autoBlockedApps setting.
  • One snapshot per refresh: SyncLateChildBlocks (L1400–1426) calls CollectChildExePaths once for each blocked app. Each call takes its own full process snapshot and opens every process. With several apps blocked, building one snapshot and walking it for all of them would cost a fraction of that. It now runs after window events even while the panel is collapsed.
  • README screenshot: still planned for after development, as you said, which is fine.

Functionality notes

Non-critical observations and ideas about the feature behavior itself.

  • Allowing a child doesn't stick: ApplyAllow removes the child from the parent's blockedChildPaths (L1440). The next debounced refresh then runs SyncLateChildBlocks, which blocks every descendant that isn't in that set (L1418–1424). That happens after any window is created or destroyed anywhere on the desktop, so the child is blocked again within seconds. Keeping a per-parent set of children the user allowed, and skipping them in SyncLateChildBlocks, would make the click stick.
  • Helpers without windows: late children are detected only after window create/destroy events. A helper process that never opens a window isn't blocked until some unrelated window event happens. A slow periodic check while any block includes children would catch those.
  • Changing a child's direction: the direction button on a child row calls ApplyBlock for the child, which creates a separate entry with its own filters. The parent's filters for that child stay in place, so a narrower direction has no effect. Allowing the child afterwards takes two clicks: the first removes only the child's own entry.

Code overview

For the maintainer: a map of the mod and what it touches outside its own process. Descriptive only — anything that needs a change is listed above. Line numbers refer to the mod source at commit 45642a5. This mod is new, so everything below is new.

Code regions

  • Line 1–14: metadata. A tool mod for the windhawk-mod-elevated.exe, windhawk-mod.exe and windhawk.exe hosts, with -DWIN32_LEAN_AND_MEAN and the WFP and firewall COM libraries.
  • Line 16–50: README.
  • Line 52–164: settings, in the groups General, Pill, Behavior, Safety, Hotkeys and the autoBlockedApps array.
  • Line 166–242: includes, WFP GUID constants missing from MinGW, DWM compat defines.
  • Line 243–459: constants, enums, settings and data models (BlockState tracks the blocked child executables), animation helpers.
  • Line 460–618: global state, including the cached pill position and the light-theme flag (RefreshLightThemeCache), plus forward declarations and the Cycle Mode models.
  • Line 619–714: utilities:
    • string helpers;
    • FileExistsW, which turns off WOW64 file-system redirection;
    • IsProcessElevated;
    • excluded-apps storage (numbered excluded_N values with excludedCount);
    • LogAudit, which only calls Wh_Log.
  • Line 715–767: safety lists: critical system executables, system paths, VPN and remote-desktop executables.
  • Line 768–938: process and network discovery:
    • process snapshot and exe paths;
    • CollectChildExePaths, limited to the app's folder and skipping %WINDIR% and shared runtimes;
    • TCP/UDP owner tables for IPv4 and IPv6;
    • PIDs with visible windows.
  • Line 939–1056: WFP engine. Opens an FWPM_SESSION_DYNAMIC session with a custom sublayer, adds ALE_APP_ID block filters at the ALE connect and recv-accept layers (v4/v6) for each exe, and removes them.
  • Line 1058–1189: read-only scan of existing Windows Firewall block rules (cached 15 s, with the WOW64 %ProgramFiles% fix).
  • Line 1190–1462: high-level operations:
    • status pulse and foreground sync;
    • ApplyBlock (VPN prompt, askUser flag, child executables);
    • SyncLateChildBlocks;
    • ApplyAllow, which can also remove a single child executable from its parent's block.
  • Line 1463–1947: Cycle Mode scheduler: runtime state, the pending-action queue, start/stop/pause, snapshots, focus and resume handling. The scheduler thread queues Blocked/Allowed transitions and posts WM_APP_CYCLE_APPLY to the UI thread.
  • Line 1948–2017: cycle engine start and shutdown; RestoreAllSessionBlocks; SyncAutoBlockedApps, which diffs the autoBlockedApps entries against the last applied set and blocks or unblocks them as session WFP filters; blocked count.
  • Line 2018–2195: RefreshAppList. Merges processes, windows, network use, blocks (including the parents' blocked children), external firewall rules and exclusions, then filters and sorts the list.
  • Line 2196–2274: icon cache, capture exclusion (WDA_EXCLUDEFROMCAPTURE on the mod's windows), fullscreen detection.
  • Line 2275–2453: hotkey parsing and registration; OpenWindhawkSettingsUI, which focuses the Windhawk window or starts windhawk.exe; LoadSettings.
  • Line 2454–2656: window geometry: rounded paths, DWM frame mode, target monitor, pill and panel rect (uses the cached posX / posY), UpdateWindowGeometry.
  • Line 2658–3095: PaintFloatingWindow, which draws the pill and the panel with GDI+. The toggle tooltip lists the blocked child executables.
  • Line 3096–3197: tray icon and its context menu, tooltip.
  • Line 3198–3328: hit testing and click actions: toggle (copies the entry and re-reads the block state), direction, exclude, filter chips, elevate.
  • Line 3329–4935: Cycle Mode popup: duration and clock field model, layout, painting, the run-mode dropdown window, keyboard input, OpenCyclePopupForApp.
  • Line 4936–5472: clock widget beside the pill: eligibility, navigation to the app, layered rendering, window procedure, reveal and sync logic.
  • Line 5473–6082: FloatingWndProc:
    • re-adds the tray icon on TaskbarCreated, and its UIPI filters let that message and the tray callback through;
    • hover expand, drag and snap, scrolling;
    • the row context menu;
    • timers, including the debounced refresh that also blocks late children;
    • WM_HOTKEY, WM_SETTINGCHANGE, WM_APP_CYCLE_APPLY, power resume, teardown.
  • Line 6084–6110: ControlWndProc, which reloads settings and syncs the auto-blocked apps, and the WinEvent callback, which posts refreshes.
  • Line 6111–6176: UI thread: COM STA, GDI+, window classes, the pill window and a message-only window, two SetWinEventHooks, the message loop, teardown.
  • Line 6178–6225: WhTool_ModInit, WhTool_ModSettingsChanged and WhTool_ModUninit.
  • Line 6226–6253: RelaunchElevated, which relaunches the current host through UAC, removes the tray icon and calls ExitProcess on success.
  • Line 6255–6416: tool-mod launcher snippet (matches the wiki apart from formatting).

Side effects of interest

  • Line 2200–2222: external file read: extracts icons from the listed apps' executables with PrivateExtractIconsW while painting rows.

  • Line 472–478: registry read: HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize\AppsUseLightTheme, at window creation and on WM_SETTINGCHANGE.

  • Line 1071–1189: registry read (through the firewall API): enumerates all Windows Firewall rules through INetFwPolicy2 to find existing blocks. Read-only, cached 15 s.

  • Line 943–1056: IPC: RPC to the Base Filtering Engine. Opens a dynamic WFP session, adds a sublayer, and adds and deletes block filters for each blocked exe and its child executables in the same folder.

    • The filters affect those executables system-wide.
    • They're removed when the session handle closes or the process exits.
    • Runs on user toggle, hotkey, cycle transitions, changed autoBlockedApps entries (at start and on settings change), and the late-child sync after window events.
  • Line 6230–6253: other reach outside the process: ShellExecuteExW with runas starts the current host exe with -tool-mod "not-net" elevated when the user clicks Run as Admin, then the original process exits.

  • Line 2319–2341: other reach outside the process: FindWindowW(nullptr, L"Windhawk") + SetForegroundWindow, or ShellExecuteW of windhawk.exe, from the settings button.

  • Line 772–938: other reach outside the process (read-only):

    • OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION) and QueryFullProcessImageNameW on every process;
    • GetExtendedTcpTable / GetExtendedUdpTable;
    • EnumWindows across all processes (no PID filter, by design).

    Runs on list refreshes, when blocking with children, and on every debounced window event while any block includes children. Each active cycle also takes a process snapshot every 500 ms.

  • Line 2301–2317: other reach outside the process: three global hotkeys through RegisterHotKey (defaults Ctrl+Alt+Shift+1, Ctrl+Alt+Shift+M, Ctrl+Alt+Shift+R).

  • Line 3100–3128, 5482–5483: other reach outside the process: notification-area icon through Shell_NotifyIconW. ChangeWindowMessageFilterEx allows TaskbarCreated and the tray callback message from lower-integrity processes.

  • Line 6152–6155: other reach outside the process: two out-of-context SetWinEventHooks for the whole desktop (foreground through minimize-end, and object create/destroy).


Next steps:

  • /ai-review - after pushing fixes, to get a review of the updated code. You can repeat this as many times as you need, but each review is thorough and usually there's no need for more than 2-3 iterations.
  • /ready-for-reviewer - once you're satisfied with the state of the pull request, to hand it over to a human reviewer. If some findings above are left unaddressed, add a short note explaining why.

See the review process for details.

Important: Each reply that was generated by AI must be disclosed by including the following text in the reply: "Generated by AI. Model: (ai-model-name)".

@windhawk-reviewer windhawk-reviewer Bot added waiting-for-author The author's turn: request an AI review, or respond to one that was posted. and removed waiting-for-ai-review An AI review was requested and is being prepared. labels Nov 13, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

waiting-for-author The author's turn: request an AI review, or respond to one that was posted.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant