Skip to content

Micswitch v2.0.1#4723

Open
BlackPaw21 wants to merge 4 commits into
ramensoftware:mainfrom
BlackPaw21:micswitch-v2.0.1
Open

Micswitch v2.0.1#4723
BlackPaw21 wants to merge 4 commits into
ramensoftware:mainfrom
BlackPaw21:micswitch-v2.0.1

Conversation

@BlackPaw21

Copy link
Copy Markdown
Contributor

Changelog

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

  • New: Persistent Mute toggle in dashboard settings. When checked, mute state survives device cycles (USB replug, sleep/wake, driver resets).
  • Fixed: Persistent mute now survives device cycling — mute is re-applied to the newly defaulted device after switching.
  • Fixed: Tray icon red-dot overlay updates after persistent re-muting.
  • Fixed: "Persistent Mute" checkbox text now renders correctly on the dark dashboard.

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.

BlackPaw21 and others added 4 commits July 8, 2026 17:52
- Persistent Mute toggle survives device cycles (USB replug, sleep/wake)
- Persistent mute re-applied after SetDefaultEndpoint
- Tray icon red-dot overlay updates after re-muting
- Renamed from microswap to micswitch
…ecks

- GetCurrentVolumePct/SetCurrentDeviceVolume: needsUninit pattern
- CycleAudioDevice: guard all 3 CoUninitialize call sites
- RestoreMuteExternal: guard CoInitialize before ApplyMute
- WhTool_ModInit: guard CoInitialize before ApplyMute
- Use RPC_E_CHANGED_MODE constant instead of S_FALSE
@m417z

m417z commented Jul 10, 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.

Note: I haven't verified the review. Address it or let me know if it's not correct.


The main new feature (Persistent Mute) has a state-tracking bug that leaves a microphone muted behind the user's back — please fix that before merge.

Persistent-mute re-apply never moves the mute tracking to the new device — the active mic can stay muted after the mod is disabled. Both re-apply paths mute the new default endpoint with ApplyMute(nullptr, TRUE) but never update g_mutedDeviceId (or the persisted MutedDeviceId) and never unmute the previously-muted device:

  • CycleAudioDevicemicroswap.wh.cpp:2019-2024 (the keepMute branch also skips RestoreMute(), so the old device stays muted).

  • TrayWndProc / WM_REBIND_VOLUME_CALLBACKmicroswap.wh.cpp:2675-2679 (this is the USB-replug / sleep-wake path — the headline scenario).

    Concrete failure: mute device A (default), then cycle/replug to device B. Now both A and B are muted, but g_mutedDeviceId still points at A. When the mod is disabled, RestoreMuteExternal() unmutes A and leaves B — the user's current microphone — muted, with no indication the mod did it. Because MutedDeviceId in storage is also stale, the next mod launch unmutes A again and B stays muted across restarts. During normal use, every cycled device also accumulates a stuck mute that only the current default ever gets cleared. This breaks Windhawk's reversibility principle (a mod's effects must disappear when it's disabled).

    Fix: whenever you re-apply the mute to the new default, unmute the previously-tracked device and record the new one, so exactly one device is ever muted-by-us. Roughly:

    if (keepMute) {
        WCHAR prevId[512];
        { EnterCriticalSection(&g_stateLock); lstrcpynW(prevId, g_mutedDeviceId, 512); LeaveCriticalSection(&g_stateLock); }
        if (prevId[0]) ApplyMute(prevId, FALSE);   // release the old device
    
        IMMDevice* pDef = nullptr;
        if (SUCCEEDED(pEnum->GetDefaultAudioEndpoint(eCapture, eMultimedia, &pDef))) {
            LPWSTR pId = nullptr;
            if (SUCCEEDED(pDef->GetId(&pId))) {
                ApplyMute(pId, TRUE);
                EnterCriticalSection(&g_stateLock);
                lstrcpynW(g_mutedDeviceId, pId, 512); g_isMutedByUs = true;
                LeaveCriticalSection(&g_stateLock);
                Wh_SetStringValue(L"MutedDeviceId", pId);
                CoTaskMemFree(pId);
            }
            pDef->Release();
        }
    }

    Apply the equivalent in the WM_REBIND_VOLUME_CALLBACK handler.

Optional improvements

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

  • g_initComplete reload guard looks inert in the tool-mod pattern. WhTool_ModInit / WhTool_ModUninit each run exactly once per dedicated process (Wh_ModUninit calls WhTool_ModUninit() then ExitProcess(0)), so the "prevent double-init/double-uninit on DLL reload" guard can never actually fire. Harmless, but it's dead complexity you can drop.

  • g_persistentMute / g_isMutedByUs read without the lock in WM_REBIND_VOLUME_CALLBACK (microswap.wh.cpp:2676). g_isMutedByUs is written under g_stateLock from the worker thread, so this is a technical data race — benign for a bool, but for consistency either take the lock (as CycleAudioDevice does) or make these std::atomic<bool>.

Functionality notes

Non-critical observations about the feature behavior itself.

  • Double mute is applied on a manual cycle. CycleAudioDevice calls ApplyMute(nullptr, TRUE) after SetDefaultEndpoint, and the resulting OnDefaultDeviceChangedWM_REBIND_VOLUME_CALLBACK re-applies it again. Idempotent, so no harm, but the re-mute logic effectively runs twice for the same switch. If you centralize the "move mute to new default" logic (per the main fix), consider driving it from a single path to avoid the redundant COM round-trip.

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