Add: Global Hotkey Mute Microphone + Floating Overlay mod - #5221
Add: Global Hotkey Mute Microphone + Floating Overlay mod#5221Eliasilyz wants to merge 8 commits into
Conversation
…zable floating overlay
|
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 See the pull request review process for the full details. |
|
@Eliasilyz Comment |
|
@Eliasilyz See the pull request review process for details. |
|
/ai-review |
Submission reviewNote: 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. The concept is nice and the GDI+ overlay rendering is clean, but the mod is injected into 1. This should be a tool mod ( The mod installs no function hooks and no symbol hooks at all — it starts its own thread, creates its own window, calls
This is exactly the case the wiki describes: Mods as tools: Running mods in a dedicated process. Change Very close prior art to model on — lock-keys-notifier.wh.cpp is architecturally the same mod (tool mod, worker thread, layered GDI+ toast overlay driven by a keyboard event). Also always-on-top.wh.cpp (tool mod + 2. Windhawk
Also, Post DWORD WINAPI ModThreadProc(LPVOID) {
MSG msg;
// Force the message queue to exist so PostThreadMessageW can't be dropped.
PeekMessageW(&msg, nullptr, WM_USER, WM_USER, PM_NOREMOVE);
...
}
void Wh_ModUninit() {
if (g_threadId) {
while (!PostThreadMessageW(g_threadId, WM_QUIT, 0, 0) &&
WaitForSingleObject(g_hThread, 10) == WAIT_TIMEOUT) {
}
}
if (g_hThread) {
WaitForSingleObject(g_hThread, INFINITE);
CloseHandle(g_hThread);
g_hThread = nullptr;
}
}References: aero-flip3d-recreation.wh.cpp#L3529-L3547 for the retry-then- Note this also matters for 3. The default capture device is resolved once and never refreshed.
Related: if Register an 4. Nothing validates the mask. With Reject a zero mask (and a zero/invalid VK), and check the if (!RegisterHotKey(hwnd, 1, mods, g_settings.hotkeyVK)) {
Wh_Log(L"RegisterHotKey failed: %u", GetLastError());
}5. The
Both should be applied in the LONG_PTR ex = GetWindowLongPtr(hwnd, GWL_EXSTYLE);
ex = g_settings.clickThrough ? (ex | WS_EX_TRANSPARENT) : (ex & ~WS_EX_TRANSPARENT);
SetWindowLongPtr(hwnd, GWL_EXSTYLE, ex);
if (g_settings.alwaysShow && !g_overlayVisible) {
ShowOverlayTemporarily(hwnd);
}6. Positioning ignores the work area, secondary monitors, and DPI.
lock-keys-notifier.wh.cpp#L1006-L1032 has ready-made 7. The window class uses
HINSTANCE GetCurrentModuleHandle() {
HINSTANCE hInst = nullptr;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
(LPCWSTR)&GetCurrentModuleHandle, &hInst);
return hInst;
}Note 8. The 30 fps render loop never stops while Every Only run the timer while something is actually animating, and stop it once the visual is static; re-render on state change instead of polling. Mute-state changes can be event-driven too via 9. README/metadata.
Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ai-review |
Submission reviewNote: 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. The tool-mod conversion, the event-driven mute/device notifications, the 1. The 30 fps timer runs forever whenever the mic is unmuted, even with the overlay hidden.
bool needTimer = !g_micMuted || (g_overlayVisible && !g_settings.alwaysShow);With the default settings ( Nothing in the hidden state uses the polled peak: bool needTimer =
g_overlayVisible && (!g_micMuted || !g_settings.alwaysShow);and 2. The hotkey settings are a raw bitmask plus a decimal virtual-key code. - hotkeyModifiers: 3
$description: Bitmask: 1=Alt, 2=Ctrl, 4=Shift, 8=Win. ...
- hotkeyVK: 77
$description: "Decimal VK code, default 77 = 'M'"To rebind this to, say, 3. Overlap with existing mods — please state the differentiation. Two mods already cover parts of this:
The floating peak-driven overlay is genuinely new, so this isn't a straight duplicate — but the maintainer's strong preference is to extend an existing mod rather than add a near-neighbour. Worth either contributing the mic-mute action upstream to Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ready-for-reviewer |
|
How about addressing the first and second notes of the review (performance, hotkey settings)? I'd also consider addressing this:
|
|
/ai-review |
Submission reviewNote: 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. Overall this is in good shape: correct tool-mod structure with the launcher boilerplate intact, proper thread join in 1. The 30 fps render loop never stops while the mic is unmuted — even with the overlay hidden. bool needTimer =
!g_micMuted || (g_overlayVisible && !g_settings.alwaysShow);
Nothing needs to animate while the window is hidden — peak level never triggers a show, only the hotkey and mute-change messages do. Gate the timer on visibility: bool needTimer =
g_overlayVisible && (!g_micMuted || !g_settings.alwaysShow);2. Changing the mic's input volume pops up the mute overlay.
STDMETHODIMP OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify) override {
if (g_hOverlay && pNotify) {
PostMessage(g_hOverlay, kMsgMuteChanged, pNotify->bMuted ? 1 : 0, 0);
}The handler then always calls case kMsgMuteChanged: {
bool muted = wParam != 0;
if (muted == g_micMuted) {
return 0; // volume-only notification, mute unchanged
}
g_micMuted = muted;
ShowOverlayTemporarily(hwnd);
...
}As a bonus this also drops the duplicate show/render that currently happens after every hotkey press (your own 3. case WM_MOVE: {
if (!g_settings.clickThrough) {
g_manualPos.x = (short)LOWORD(lParam);
...
g_manualPosition = true;
}
Capture the position only at the end of a real drag instead: case WM_EXITSIZEMOVE: {
if (!g_settings.clickThrough) {
RECT wr;
GetWindowRect(hwnd, &wr);
g_manualPos = {wr.left, wr.top};
g_manualPosition = true;
}
return 0;
}(and drop the 4. Please state how this differs from the existing mods in this area. The closest ones are mic-tray-control and mutealert (both surface default-mic mute state and let you toggle it, via the tray/taskbar rather than a hotkey), and keyboard-shortcut-actions, which is a general hotkey→action framework using the same Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ai-review |
Submission reviewNote: 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. Overall this is a well-structured tool mod: correct 1. Drag-to-reposition fights the render timer
POINT ptDst = {rc.left, rc.top};
...
UpdateLayeredWindow(hwnd, nullptr, &ptDst, &sizeWnd, g_memDC, &ptSrc, 0, &blend, ULW_ALPHA);The modal move loop entered from Fix: track the drag and don't reposition during it — case WM_ENTERSIZEMOVE:
g_dragging = true;
return 0;
...
UpdateLayeredWindow(hwnd, nullptr, g_dragging ? nullptr : &ptDst, &sizeWnd,
g_memDC, &ptSrc, 0, &blend, ULW_ALPHA);neko-cat does exactly this — an 2. BOOL muted = FALSE;
g_pEndpointVolume->GetMute(&muted);
g_pEndpointVolume->SetMute(!muted, nullptr);
g_micMuted = !muted;If BOOL muted = FALSE;
if (SUCCEEDED(g_pEndpointVolume->GetMute(&muted)) &&
SUCCEEDED(g_pEndpointVolume->SetMute(!muted, nullptr))) {
g_micMuted = !muted;
}3. Overlap with The README's comparison is accurate — Optional improvements
Minor polish — none of this affects users much, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ai-review |
Submission reviewNote: 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. Overall this is a well-structured tool mod — the dedicated-process pattern, COM object ownership (everything created and released on the overlay thread), the join-in- 1. Auto-hide fires in the middle of a drag, and leaves almost no window to start one
if (!g_settings.alwaysShow && g_overlayVisible) {
bool activeSignal = !g_micMuted && peak > 0.03f;
DWORD elapsed = GetTickCount() - g_lastActivityTick;
if (!activeSignal && elapsed > (DWORD)g_settings.overlayDurationMs) {
ShowWindow(hwnd, SW_HIDE);
g_overlayVisible = false;
}
}The move loop started from Suppress the hide while dragging and restart the countdown when the drag ends: if (!g_settings.alwaysShow && g_overlayVisible && !g_dragging) {
...
}case WM_EXITSIZEMOVE: {
g_dragging = false;
g_lastActivityTick = GetTickCount(); // don't hide right after the drop
...
}2.
Either make it explicit in the Optional improvements
Minor polish — none of this affects users, so it's your call.
Functionality notes
Non-critical observations and ideas about the feature behavior itself.
Next steps:
See the review process for details. |
|
/ai-review |
|
This pull request has already had 3 AI reviews in the last 24 hours, which is the limit, so no review was posted this time. Comment |
|
@Eliasilyz |
A customizable floating overlay and global hotkey to toggle and monitor the default microphone's mute state.
Changelog
If this pull request updates an existing mod, describe the changes below:
Mod authorship
If this pull request introduces a new mod, please complete the section below.
This mod was created by:
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.
Summary
Adds a new Windhawk mod,
mic-mute-hotkey-overlay.cpp, that binds a global hotkey to toggle mute on the system's default microphone and shows a floating, always-on-top overlay indicator while doing so.What it does
Ctrl+Alt+M, configurable) toggles mute viaIAudioEndpointVolumeon the default capture device.Testing
explorer.exe.Known limitations
IAudioMeterInformationisn't fully defined in the Windhawk compiler's SDK headers (forward-declared only), so real peak metering isn't available in this build environment.