From 2a4a09d0a9968458269fb920575990c416bd0de9 Mon Sep 17 00:00:00 2001 From: lx-s Date: Sat, 7 Mar 2026 16:49:00 +0100 Subject: [PATCH] Made endpoint handling more robust --- WinMute/MuteControl.cpp | 253 ++++++++++++++++++---------- WinMute/MuteControl.h | 2 + WinMute/VistaAudio.cpp | 46 ++--- WinMute/VistaAudioSessionEvents.cpp | 13 +- WinMute/WinAudio.h | 4 +- WinMute/WinMute.cpp | 12 ++ WinMute/WinMute.h | 1 + WinMute/common.h | 1 + 8 files changed, 205 insertions(+), 127 deletions(-) diff --git a/WinMute/MuteControl.cpp b/WinMute/MuteControl.cpp index 19cff60..573c472 100644 --- a/WinMute/MuteControl.cpp +++ b/WinMute/MuteControl.cpp @@ -38,9 +38,10 @@ extern HINSTANCE hglobInstance; static const int BLUETOOTH_RECONNECT_UNMUTE_DELAY = 5000; // Milliseconds static const int MUTE_DELAY_MAGIC_VALUE = 0x198604; -static const wchar_t* MUTECONTROL_CLASS_NAME = L"WinMuteMuteControl"; +static const wchar_t *MUTECONTROL_CLASS_NAME = L"WinMuteMuteControl"; -enum MuteType { +enum MuteType +{ // With restore MuteTypeWorkstationLock = 0, MuteTypeRemoteSession, @@ -56,20 +57,32 @@ enum MuteType { void DelayedMuteTimerProc(HWND hWnd, UINT, UINT_PTR, DWORD) { - MuteControl *muteCtrl = reinterpret_cast(GetWindowLongPtrW(hWnd, GWLP_USERDATA)); - if (muteCtrl != nullptr) { + MuteControl *muteCtrl = reinterpret_cast(GetWindowLongPtrW(hWnd, GWLP_USERDATA)); + if (muteCtrl != nullptr) + { muteCtrl->MuteDelayed(MUTE_DELAY_MAGIC_VALUE); } } +static void CALLBACK BluetoothUnmuteTimerProc(HWND hWnd, UINT, UINT_PTR id, DWORD) +{ + KillTimer(hWnd, id); + MuteControl *muteCtrl = reinterpret_cast(GetWindowLongPtrW(hWnd, GWLP_USERDATA)); + if (muteCtrl != nullptr) + { + muteCtrl->CompleteVolumeRestore(); + } +} + static LRESULT CALLBACK MuteControlWndProc( - HWND hWnd, - UINT msg, - WPARAM wParam, - LPARAM lParam) + HWND hWnd, + UINT msg, + WPARAM wParam, + LPARAM lParam) { - auto wm = reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); - switch (msg) { + auto wm = reinterpret_cast(GetWindowLongPtr(hWnd, GWLP_USERDATA)); + switch (msg) + { case WM_NCCREATE: { LPCREATESTRUCTW cs = reinterpret_cast(lParam); @@ -81,8 +94,8 @@ static LRESULT CALLBACK MuteControlWndProc( break; } return (wm) - ? wm->WindowProc(hWnd, msg, wParam, lParam) - : DefWindowProcW(hWnd, msg, wParam, lParam); + ? wm->WindowProc(hWnd, msg, wParam, lParam) + : DefWindowProcW(hWnd, msg, wParam, lParam); } MuteControl::MuteControl() @@ -90,7 +103,8 @@ MuteControl::MuteControl() MuteConfig initMuteConf; initMuteConf.active = false; initMuteConf.shouldMute = false; - for (int i = 0; i < MuteTypeCount; ++i) { + for (int i = 0; i < MuteTypeCount; ++i) + { muteConfig_.push_back(initMuteConf); } } @@ -103,32 +117,35 @@ MuteControl::~MuteControl() bool MuteControl::Init(HWND hParent, const TrayIcon *trayIcon) { - WNDCLASSEXW wndClass{ 0 }; + WNDCLASSEXW wndClass{0}; wndClass.cbSize = sizeof(wndClass); wndClass.lpfnWndProc = MuteControlWndProc; wndClass.hInstance = hglobInstance; wndClass.lpszClassName = MUTECONTROL_CLASS_NAME; - if (!RegisterClassExW(&wndClass)) { + if (!RegisterClassExW(&wndClass)) + { WMLog::GetInstance().LogWinError(L"RegisterClassEx"); return false; } hMuteCtrlWnd_ = CreateWindowEx( - WS_EX_TOOLWINDOW, - MUTECONTROL_CLASS_NAME, - L"", - 0, - 0, 0, 0, 0, - nullptr, - nullptr, - hglobInstance, - this); - if (hMuteCtrlWnd_ == nullptr) { + WS_EX_TOOLWINDOW, + MUTECONTROL_CLASS_NAME, + L"", + 0, + 0, 0, 0, 0, + nullptr, + nullptr, + hglobInstance, + this); + if (hMuteCtrlWnd_ == nullptr) + { WMLog::GetInstance().LogWinError(L"CreateWindowEx"); UnregisterClassW(MUTECONTROL_CLASS_NAME, hglobInstance); return false; } winAudio_ = std::make_unique(); - if (!winAudio_->Init(hParent)) { + if (!winAudio_->Init(hParent)) + { DestroyWindow(hMuteCtrlWnd_); UnregisterClassW(MUTECONTROL_CLASS_NAME, hglobInstance); return false; @@ -139,10 +156,10 @@ bool MuteControl::Init(HWND hParent, const TrayIcon *trayIcon) } LRESULT CALLBACK MuteControl::WindowProc( - HWND hWnd, - UINT msg, - WPARAM wParam, - LPARAM lParam) + HWND hWnd, + UINT msg, + WPARAM wParam, + LPARAM lParam) { return DefWindowProc(hWnd, msg, wParam, lParam); } @@ -156,24 +173,28 @@ void MuteControl::SetMute(bool mute) void MuteControl::SaveMuteStatus() { const bool alreadySaved = std::any_of( - muteConfig_.begin(), - muteConfig_.end(), - [](const MuteConfig &conf) { - return conf.shouldMute && conf.active; - } - ); - - if (alreadySaved) { + muteConfig_.begin(), + muteConfig_.end(), + [](const MuteConfig &conf) + { + return conf.shouldMute && conf.active; + }); + + if (alreadySaved) + { WMLog::GetInstance().LogInfo(L"Muting event already active. Skipping status save"); - } else { + } + else + { WMLog::GetInstance().LogInfo(L"Saving mute status"); winAudio_->SaveMuteStatus(); } } -void MuteControl::ShowNotification(const std::wstring& title, const std::wstring& text) +void MuteControl::ShowNotification(const std::wstring &title, const std::wstring &text) { - if (notificationsEnabled_ && trayIcon_ != nullptr) { + if (notificationsEnabled_ && trayIcon_ != nullptr) + { trayIcon_->ShowPopup(title, text); } } @@ -185,7 +206,8 @@ void MuteControl::SetNotifications(bool enable) void MuteControl::MuteDelayed(int magic) { - if (magic != MUTE_DELAY_MAGIC_VALUE || delayedMuteTimerId_ == 0) { + if (magic != MUTE_DELAY_MAGIC_VALUE || delayedMuteTimerId_ == 0) + { return; } WMLog::GetInstance().LogInfo(L"Muting workstation after delay"); @@ -197,11 +219,12 @@ void MuteControl::MuteDelayed(int magic) bool MuteControl::StartDelayedMute() { delayedMuteTimerId_ = SetTimer( - hMuteCtrlWnd_, - delayedMuteTimerId_, - muteDelaySeconds_ * 1000, - DelayedMuteTimerProc); - if (delayedMuteTimerId_ == 0) { + hMuteCtrlWnd_, + delayedMuteTimerId_, + muteDelaySeconds_ * 1000, + DelayedMuteTimerProc); + if (delayedMuteTimerId_ == 0) + { WMLog::GetInstance().LogWinError(L"SetTimer", GetLastError()); } return delayedMuteTimerId_ != 0; @@ -209,38 +232,65 @@ bool MuteControl::StartDelayedMute() void MuteControl::RestoreVolume(bool withDelay) { - WMLog& log = WMLog::GetInstance(); - if (!restoreVolume_) { + WMLog &log = WMLog::GetInstance(); + if (!restoreVolume_) + { log.LogInfo(L"Volume Restore has been disabled"); return; } const bool restore = !std::any_of( - muteConfig_.begin(), - muteConfig_.end(), - [](const MuteConfig &conf) { - return conf.shouldMute && conf.active; - } - ); - if (!restore) { + muteConfig_.begin(), + muteConfig_.end(), + [](const MuteConfig &conf) + { + return conf.shouldMute && conf.active; + }); + if (!restore) + { log.LogInfo(L"Skipping restore since other mute event is currently active"); - } else if (delayedMuteTimerId_ != 0) { + } + else if (delayedMuteTimerId_ != 0) + { KillTimer(hMuteCtrlWnd_, delayedMuteTimerId_); delayedMuteTimerId_ = 0; log.LogInfo(L"Skipping restore, since delayed mute was not triggered yet"); - } else { - log.LogInfo(L"Restoring previous mute state"); + } + else if (withDelay) + { + log.LogInfo(L"Restoring previous mute state after Bluetooth delay"); ShowNotification( - WMi18n::GetInstance().GetTranslationW("popup.volume-restored.title"), - WMi18n::GetInstance().GetTranslationW("popup.volume-restored.text")); - if (withDelay) { - Sleep(BLUETOOTH_RECONNECT_UNMUTE_DELAY); - } - winAudio_->RestoreMuteStatus(); - if (mediaWasPlaying_ && mediaConfig_.tryResume) { - MediaPlaybackController::TryPlayCurrentSession(); - mediaWasPlaying_ = false; + WMi18n::GetInstance().GetTranslationW("popup.volume-restored.title"), + WMi18n::GetInstance().GetTranslationW("popup.volume-restored.text")); + bluetoothUnmuteTimerId_ = SetTimer( + hMuteCtrlWnd_, + bluetoothUnmuteTimerId_, + BLUETOOTH_RECONNECT_UNMUTE_DELAY, + BluetoothUnmuteTimerProc); + if (bluetoothUnmuteTimerId_ == 0) + { + log.LogWinError(L"SetTimer (Bluetooth unmute delay)"); + CompleteVolumeRestore(); // fall back to immediate restore } } + else + { + log.LogInfo(L"Restoring previous mute state"); + ShowNotification( + WMi18n::GetInstance().GetTranslationW("popup.volume-restored.title"), + WMi18n::GetInstance().GetTranslationW("popup.volume-restored.text")); + CompleteVolumeRestore(); + } +} + +void MuteControl::CompleteVolumeRestore() +{ + bluetoothUnmuteTimerId_ = 0; + winAudio_->RestoreMuteStatus(); + if (mediaWasPlaying_ && mediaConfig_.tryResume) + { + MediaPlaybackController::TryPlayCurrentSession(); + mediaWasPlaying_ = false; + } } void MuteControl::SetRestoreVolume(bool enable) @@ -342,35 +392,45 @@ void MuteControl::MuteNow() { WMLog::GetInstance().LogInfo(L"Muting workstation"); ShowNotification( - WMi18n::GetInstance().GetTranslationW("popup.muting-workstation.title"), - WMi18n::GetInstance().GetTranslationW("popup.muting-workstation.text")); + WMi18n::GetInstance().GetTranslationW("popup.muting-workstation.title"), + WMi18n::GetInstance().GetTranslationW("popup.muting-workstation.text")); winAudio_->SetMute(true); - if (mediaConfig_.tryPause) { + if (mediaConfig_.tryPause) + { bool did_pause = false; - if (MediaPlaybackController::TryPauseCurrentSession(did_pause)) { + if (MediaPlaybackController::TryPauseCurrentSession(did_pause)) + { mediaWasPlaying_ = did_pause; } } } - void MuteControl::NotifyRestoreCondition(int type, bool active, bool withDelay) { - if (active) { + if (active) + { SaveMuteStatus(); muteConfig_[type].active = active; - if (muteConfig_[type].shouldMute) { - if (muteDelaySeconds_ == 0) { + if (muteConfig_[type].shouldMute) + { + if (muteDelaySeconds_ == 0) + { MuteNow(); - } else { + } + else + { WMLog::GetInstance().LogInfo(L"Starting delayed mute timer..."); StartDelayedMute(); } } - } else { - if (muteConfig_[type].active) { + } + else + { + if (muteConfig_[type].active) + { muteConfig_[type].active = false; - if (muteConfig_[type].shouldMute) { + if (muteConfig_[type].shouldMute) + { RestoreVolume(withDelay); } } @@ -380,22 +440,23 @@ void MuteControl::NotifyRestoreCondition(int type, bool active, bool withDelay) void MuteControl::NotifyWorkstationLock(bool active) { WMLog::GetInstance().LogInfo(L"Mute Event: Workstation Lock %s", - active ? L"start" : L"stop"); + active ? L"start" : L"stop"); NotifyRestoreCondition(MuteTypeWorkstationLock, active); } void MuteControl::NotifyRemoteSession(bool active) { WMLog::GetInstance().LogInfo(L"Mute Event: Remote Session %s", - active ? L"start" : L"stop"); + active ? L"start" : L"stop"); NotifyRestoreCondition(MuteTypeRemoteSession, active); } void MuteControl::NotifyDisplayStandby(bool active) { - if (displayWasOffOnce_ || active) { + if (displayWasOffOnce_ || active) + { WMLog::GetInstance().LogInfo(L"Mute Event: Display Standby %s", - active ? L"start" : L"stop"); + active ? L"start" : L"stop"); NotifyRestoreCondition(MuteTypeDisplayStandby, active); displayWasOffOnce_ = true; } @@ -404,15 +465,16 @@ void MuteControl::NotifyDisplayStandby(bool active) void MuteControl::NotifyBluetoothConnected(bool connected) { WMLog::GetInstance().LogInfo( - L"Mute Event: Bluetooth audio device %s", - connected ? L"connected" : L"disconnected"); + L"Mute Event: Bluetooth audio device %s", + connected ? L"connected" : L"disconnected"); NotifyRestoreCondition(MuteTypeBluetoothDisconnect, !connected, true); } void MuteControl::NotifyLogout() { WMLog::GetInstance().LogInfo(L"Mute Event: Logout start"); - if (muteConfig_[MuteTypeLogout].shouldMute) { + if (muteConfig_[MuteTypeLogout].shouldMute) + { winAudio_->SetMute(true); } } @@ -420,7 +482,8 @@ void MuteControl::NotifyLogout() void MuteControl::NotifySuspend(bool /*active*/) { WMLog::GetInstance().LogInfo(L"Mute Event: Suspend start"); - if (muteConfig_[MuteTypeSuspend].shouldMute) { + if (muteConfig_[MuteTypeSuspend].shouldMute) + { winAudio_->SetMute(true); } } @@ -428,27 +491,31 @@ void MuteControl::NotifySuspend(bool /*active*/) void MuteControl::NotifyShutdown() { WMLog::GetInstance().LogInfo(L"Mute Event: Shutdown start"); - if (muteConfig_[MuteTypeShutdown].shouldMute) { + if (muteConfig_[MuteTypeShutdown].shouldMute) + { winAudio_->SetMute(true); } } void MuteControl::NotifyQuietHours(bool active) { - if (active) { + if (active) + { SaveMuteStatus(); WMLog::GetInstance().LogInfo(L"Mute Event: Quiet Hours startet"); winAudio_->SaveMuteStatus(); winAudio_->SetMute(true); - } else { + } + else + { WMLog::GetInstance().LogInfo(L"Mute Event: Quiet Hours ended"); RestoreVolume(); } } void MuteControl::SetManagedEndpoints( - const std::vector& endpoints, - bool isAllowList) + const std::vector &endpoints, + bool isAllowList) { winAudio_->MuteSpecificEndpoints(true); winAudio_->SetManagedEndpoints(endpoints, isAllowList); diff --git a/WinMute/MuteControl.h b/WinMute/MuteControl.h index 63eb7c4..3127eb9 100644 --- a/WinMute/MuteControl.h +++ b/WinMute/MuteControl.h @@ -95,6 +95,7 @@ public: // Should only be called internally void MuteDelayed(int magic); + void CompleteVolumeRestore(); private: struct MuteConfig { bool shouldMute; @@ -110,6 +111,7 @@ private: bool mediaWasPlaying_ = false; int muteDelaySeconds_ = 0; UINT_PTR delayedMuteTimerId_ = 0; + UINT_PTR bluetoothUnmuteTimerId_ = 0; std::unique_ptr winAudio_; HWND hMuteCtrlWnd_ = nullptr; diff --git a/WinMute/VistaAudio.cpp b/WinMute/VistaAudio.cpp index 40e649f..2db0e32 100644 --- a/WinMute/VistaAudio.cpp +++ b/WinMute/VistaAudio.cpp @@ -137,9 +137,10 @@ bool VistaAudio::LoadAllEndpoints() endpoints_.push_back(std::move(ep)); } -exit_error: - return true; + +exit_error: + return false; } bool VistaAudio::Init(HWND hParent) @@ -147,7 +148,6 @@ bool VistaAudio::Init(HWND hParent) WMLog& log = WMLog::GetInstance(); hParent_ = hParent; - reInit_ = false; if (FAILED(deviceEnumerator_.CoCreateInstance( __uuidof(MMDeviceEnumerator), @@ -183,37 +183,39 @@ void VistaAudio::ShouldReInit() reInit_ = true; } +void VistaAudio::OnAudioServiceShutdown() +{ + PostMessageW(hParent_, WM_WINMUTE_AUDIO_SERVICE_SHUTDOWN, 0, 0); +} + bool VistaAudio::CheckForReInit() { - bool ok = true; - if (reInit_) { + if (reInit_.exchange(false)) { Uninit(); - ok = Init(hParent_); + return Init(hParent_); } - return ok; + return true; } bool VistaAudio::AllEndpointsMuted() { - bool allMuted = true; WMLog& log = WMLog::GetInstance(); - if (CheckForReInit()) { - for (auto& e : endpoints_) { - BOOL isMuted = FALSE; - if (FAILED(e->endpointVolume->GetMute(&isMuted))) { - log.LogError( - L"Failed to get mute status for \"%s\"", - e->deviceName.c_str()); - allMuted = false; - break; - } else if (isMuted == FALSE) { - allMuted = false; - break; - } + if (!CheckForReInit()) { + return false; + } + for (auto& e : endpoints_) { + BOOL isMuted = FALSE; + if (FAILED(e->endpointVolume->GetMute(&isMuted))) { + log.LogError( + L"Failed to get mute status for \"%s\"", + e->deviceName.c_str()); + return false; + } else if (isMuted == FALSE) { + return false; } } - return allMuted; + return true; } bool VistaAudio::SaveMuteStatus() diff --git a/WinMute/VistaAudioSessionEvents.cpp b/WinMute/VistaAudioSessionEvents.cpp index 4b40b76..ee3b112 100644 --- a/WinMute/VistaAudioSessionEvents.cpp +++ b/WinMute/VistaAudioSessionEvents.cpp @@ -131,18 +131,9 @@ HRESULT STDMETHODCALLTYPE VistaAudioSessionEvents::OnSessionDisconnected( case DisconnectReasonSessionDisconnected: notifyParent_->ShouldReInit(); break; - case DisconnectReasonServerShutdown: { - WMi18n &i18n = WMi18n::GetInstance(); - TaskDialog(nullptr, - nullptr, - PROGRAM_NAME, - i18n.GetTranslationW("general.error.audio-service-shutdown.title").c_str(), - i18n.GetTranslationW("general.error.audio-service-shutdown.text").c_str(), - TDCBF_OK_BUTTON, - TD_WARNING_ICON, - nullptr); + case DisconnectReasonServerShutdown: + notifyParent_->OnAudioServiceShutdown(); break; - } case DisconnectReasonSessionLogoff: break; case DisconnectReasonExclusiveModeOverride: diff --git a/WinMute/WinAudio.h b/WinMute/WinAudio.h index 4a54437..82a7572 100644 --- a/WinMute/WinAudio.h +++ b/WinMute/WinAudio.h @@ -42,6 +42,7 @@ class WinAudio { public: virtual bool Init(HWND hParent) = 0; virtual void ShouldReInit() = 0; + virtual void OnAudioServiceShutdown() = 0; virtual bool AllEndpointsMuted() = 0; virtual bool SaveMuteStatus() = 0; virtual bool RestoreMuteStatus() = 0; @@ -74,6 +75,7 @@ public: bool Init(HWND hParent) override; void ShouldReInit() override; + void OnAudioServiceShutdown() override; bool AllEndpointsMuted() override; bool SaveMuteStatus() override; bool RestoreMuteStatus() override; @@ -95,7 +97,7 @@ private: CComPtr mmnAudioEvents_; CComPtr deviceEnumerator_; - bool reInit_; + std::atomic reInit_; bool muteSpecificEndpoints_; bool muteSpecificEndpointsAllowList_; HWND hParent_; diff --git a/WinMute/WinMute.cpp b/WinMute/WinMute.cpp index ea74ac4..8ebb4cf 100644 --- a/WinMute/WinMute.cpp +++ b/WinMute/WinMute.cpp @@ -618,6 +618,16 @@ LRESULT WinMute::OnQuietHours(HWND, UINT msg, WPARAM, LPARAM) return 0; } +LRESULT WinMute::OnAudioServiceShutdown(HWND hWnd, WPARAM, LPARAM) +{ + TaskDialog( + hWnd, hglobInstance, PROGRAM_NAME, + i18n_.GetTranslationW("general.error.audio-service-shutdown.title").c_str(), + i18n_.GetTranslationW("general.error.audio-service-shutdown.text").c_str(), + TDCBF_OK_BUTTON, TD_WARNING_ICON, nullptr); + return 0; +} + LRESULT WinMute::OnDeviceChange(HWND, UINT msg, WPARAM wParam, LPARAM lParam) { if (muteConfig_.muteOnBluetooth) { @@ -706,6 +716,8 @@ LRESULT CALLBACK WinMute::WindowProc( case WM_WINMUTE_QUIETHOURS_START: // fall through case WM_WINMUTE_QUIETHOURS_END: return OnQuietHours(hWnd, msg, wParam, lParam); + case WM_WINMUTE_AUDIO_SERVICE_SHUTDOWN: + return OnAudioServiceShutdown(hWnd, wParam, lParam); case WM_DEVICECHANGE: return OnDeviceChange(hWnd, msg, wParam, lParam); case WM_WIFISTATUSCHANGED: diff --git a/WinMute/WinMute.h b/WinMute/WinMute.h index f24dce2..c1f9eb3 100644 --- a/WinMute/WinMute.h +++ b/WinMute/WinMute.h @@ -99,4 +99,5 @@ private: LRESULT OnDeviceChange(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT OnQuietHours(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); + LRESULT OnAudioServiceShutdown(HWND hWnd, WPARAM wParam, LPARAM lParam); }; diff --git a/WinMute/common.h b/WinMute/common.h index 28caf5d..d766ff2 100644 --- a/WinMute/common.h +++ b/WinMute/common.h @@ -137,3 +137,4 @@ static const wchar_t *LOG_FILE_NAME = L"WinMute.log"; constexpr int WM_SAVESETTINGS = WM_USER + 300; constexpr int WM_WINMUTE_UPDATE_POPUP = WM_USER + 301; constexpr int WM_LOG_UPDATED = WM_USER + 302; +constexpr int WM_WINMUTE_AUDIO_SERVICE_SHUTDOWN = WM_USER + 303; -- 2.43.0