From 7504da75e5360ff620ac385481f4345d4fcefad5 Mon Sep 17 00:00:00 2001 From: lx-s Date: Thu, 13 Apr 2023 14:53:42 +0200 Subject: [PATCH] Added endpoint control. --- WinMute/MuteControl.cpp | 12 + WinMute/MuteControl.h | 4 + WinMute/Settings_BluetoothDlg.cpp | 10 +- WinMute/Settings_ManageEndpointsDlg.cpp | 325 ++++++++++++++++++++++++ WinMute/Settings_MuteDlg.cpp | 23 ++ WinMute/VistaAudio.cpp | 46 ++++ WinMute/WMSettings.cpp | 25 +- WinMute/WMSettings.h | 14 +- WinMute/WinAudio.h | 14 + WinMute/WinMute.cpp | 8 + WinMute/WinMute.rc | 77 +++++- WinMute/WinMute.vcxproj | 1 + WinMute/WinMute.vcxproj.filters | 3 + WinMute/resource.h | 13 +- 14 files changed, 553 insertions(+), 22 deletions(-) diff --git a/WinMute/MuteControl.cpp b/WinMute/MuteControl.cpp index 9315815..9b12165 100644 --- a/WinMute/MuteControl.cpp +++ b/WinMute/MuteControl.cpp @@ -308,3 +308,15 @@ void MuteControl::NotifyQuietHours(bool active) RestoreVolume(); } } + +void MuteControl::SetManagedEndpoints( + const std::vector endpoints, + bool isAllowList) +{ + winAudio_->SetManagedEndpoints(endpoints, isAllowList); +} + +void MuteControl::ClearManagedEndpoints() +{ + winAudio_->MuteSpecificEndpoints(false); +} diff --git a/WinMute/MuteControl.h b/WinMute/MuteControl.h index a566802..db22ba4 100644 --- a/WinMute/MuteControl.h +++ b/WinMute/MuteControl.h @@ -80,6 +80,10 @@ public: void NotifyShutdown(); void NotifyQuietHours(bool active); + + void SetManagedEndpoints(const std::vector endpoints, + bool isAllowList); + void ClearManagedEndpoints(); private: struct MuteConfig { bool shouldMute; diff --git a/WinMute/Settings_BluetoothDlg.cpp b/WinMute/Settings_BluetoothDlg.cpp index f6069e9..3d7417f 100644 --- a/WinMute/Settings_BluetoothDlg.cpp +++ b/WinMute/Settings_BluetoothDlg.cpp @@ -35,7 +35,7 @@ POSSIBILITY OF SUCH DAMAGE. static constexpr int BT_DEV_NAME_MAX_LEN = 100; -struct BtDeviceName { +struct BtDeviceData { std::wstring devName; }; @@ -71,7 +71,7 @@ static INT_PTR CALLBACK Settings_BluetoothAddDlgProc(HWND hDlg, UINT msg, WPARAM switch (msg) { case WM_INITDIALOG: { HWND hBtDevName = GetDlgItem(hDlg, IDC_BT_DEVICE_NAME); - BtDeviceName* btDeviceData = reinterpret_cast(lParam); + BtDeviceData* btDeviceData = reinterpret_cast(lParam); if (btDeviceData == nullptr) { return FALSE; } @@ -119,7 +119,7 @@ static INT_PTR CALLBACK Settings_BluetoothAddDlgProc(HWND hDlg, UINT msg, WPARAM Edit_ShowBalloonTip(hDevName, &ebt); } else { wchar_t devNameBuf[BT_DEV_NAME_MAX_LEN + 1] = { 'L\0' }; - BtDeviceName* btDevName = reinterpret_cast(GetWindowLongPtr(hDlg, GWLP_USERDATA)); + BtDeviceData* btDevName = reinterpret_cast(GetWindowLongPtr(hDlg, GWLP_USERDATA)); if (btDevName != nullptr) { Edit_GetText(hDevName, devNameBuf, ARRAY_SIZE(devNameBuf)); btDevName->devName = devNameBuf; @@ -251,7 +251,7 @@ INT_PTR CALLBACK Settings_BluetoothDlgProc(HWND hDlg, UINT msg, WPARAM wParam, L Button_Enable(GetDlgItem(hDlg, IDC_BLUETOOTH_REMOVE), entrySelected); } } else if (LOWORD(wParam) == IDC_BLUETOOTH_ADD) { - BtDeviceName btDeviceName; + BtDeviceData btDeviceName; if (DialogBoxParam( nullptr, MAKEINTRESOURCE(IDD_SETTINGS_BLUETOOTH_ADD), @@ -279,7 +279,7 @@ INT_PTR CALLBACK Settings_BluetoothDlgProc(HWND hDlg, UINT msg, WPARAM wParam, L if ((textBuf = new wchar_t[static_cast(len) + 1]) != nullptr) { ListBox_GetText(hList, sel, textBuf); - BtDeviceName btDevName; + BtDeviceData btDevName; btDevName.devName = textBuf; delete[] textBuf; diff --git a/WinMute/Settings_ManageEndpointsDlg.cpp b/WinMute/Settings_ManageEndpointsDlg.cpp index e69de29..16f9f4b 100644 --- a/WinMute/Settings_ManageEndpointsDlg.cpp +++ b/WinMute/Settings_ManageEndpointsDlg.cpp @@ -0,0 +1,325 @@ +/* + WinMute + Copyright (c) 2023, Alexander Steinhoefer + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the author nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +----------------------------------------------------------------------------- +*/ + +#include "common.h" +#include +#include + +static constexpr int ENDPOINT_NAME_MAX_LEN = 200; + +struct EndpointData { + std::wstring name; +}; + +_COM_SMARTPTR_TYPEDEF(IPropertyStore, __uuidof(IPropertyStore)); +_COM_SMARTPTR_TYPEDEF(IMMDevice, __uuidof(IMMDevice)); +_COM_SMARTPTR_TYPEDEF(IMMDeviceEnumerator, __uuidof(IMMDeviceEnumerator)); +_COM_SMARTPTR_TYPEDEF(IMMDeviceCollection, __uuidof(IMMDeviceCollection)); + +static bool GetAudioEndpoints(std::vector& endpoints) +{ + WMLog &log = WMLog::GetInstance(); + + IMMDeviceEnumeratorPtr deviceEnumerator; + if (FAILED(deviceEnumerator.CreateInstance( + __uuidof(MMDeviceEnumerator), + nullptr, + CLSCTX_INPROC_SERVER))) { + return false; + } + IMMDeviceCollectionPtr audioEndpoints; + if (FAILED(deviceEnumerator->EnumAudioEndpoints( + eRender, + DEVICE_STATE_ACTIVE, + &audioEndpoints))) { + return false; + } + + UINT epCount = 0; + if (FAILED(audioEndpoints->GetCount(&epCount))) { + return false; + } + + for (UINT i = 0; i < epCount; ++i) { + IMMDevicePtr device = nullptr; + if (FAILED(audioEndpoints->Item(i, &device))) { + log.Write(L"Failed to get audio endpoint #%d", i); + continue; + } + + IPropertyStorePtr propStore = nullptr; + if (FAILED(device->OpenPropertyStore(STGM_READ, &propStore))) { + log.Write(L"Failed to open property store for audio endpoint #%d", i); + continue; + } + + PROPVARIANT value; + PropVariantInit(&value); + if (FAILED(propStore->GetValue(PKEY_Device_FriendlyName, &value))) { + log.Write(L"Failed to get device name for audio endpoint #%d", i); + continue; + } + + wchar_t deviceName[100]; + StringCchCopy(deviceName, + sizeof(deviceName) / sizeof(deviceName[0]), + value.pwszVal); + PropVariantClear(&value); + endpoints.push_back(deviceName); + } + + return true; +} + +static INT_PTR CALLBACK Settings_EndpointAddDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + { + HWND hEndpointName = GetDlgItem(hDlg, IDC_ENDPOINT_NAME); + EndpointData *endpointData = reinterpret_cast(lParam); + if (endpointData == nullptr) { + return FALSE; + } + SetWindowLongPtr(hDlg, GWLP_USERDATA, reinterpret_cast(endpointData)); + + if (endpointData->name.length() == 0) { + if (!SetWindowTextW(hDlg, L"Add endpoint")) { + PrintWindowsError(L"SetWindowText", GetLastError()); + return FALSE; + } + } else { + if (!SetWindowTextW(hDlg, L"Edit endpoint") || + !SetWindowTextW(GetDlgItem(hDlg, IDC_BT_DEVICE_NAME), + endpointData->name.c_str())) { + PrintWindowsError(L"SetWindowText", GetLastError()); + return FALSE; + } + } + // Fill Combobox + std::vector audioEndpoints; + if (GetAudioEndpoints(audioEndpoints)) { + for (const auto &devName : audioEndpoints) { + ComboBox_AddString(hEndpointName, devName.c_str()); + } + } + + Edit_LimitText(hEndpointName, ENDPOINT_NAME_MAX_LEN); + if (GetDlgCtrlID(reinterpret_cast(wParam)) != IDC_BT_DEVICE_NAME) { + SetFocus(hEndpointName); + return FALSE; + } + return TRUE; + } + case WM_COMMAND: + if (LOWORD(wParam) == IDOK) { + HWND hEpName = GetDlgItem(hDlg, IDC_ENDPOINT_NAME); + const int textLen = Edit_GetTextLength(hEpName); + if (textLen == 0) { + EDITBALLOONTIP ebt; + ZeroMemory(&ebt, sizeof(ebt)); + ebt.cbStruct = sizeof(ebt); + ebt.pszText = L"Please enter a endpoint name"; + ebt.pszTitle = L"Endpoint Name"; + ebt.ttiIcon = TTI_INFO; + Edit_ShowBalloonTip(hEpName, &ebt); + } else { + wchar_t epNameBuf[ENDPOINT_NAME_MAX_LEN + 1] = { 'L\0' }; + EndpointData *epData = reinterpret_cast(GetWindowLongPtr(hDlg, GWLP_USERDATA)); + if (epData != nullptr) { + Edit_GetText(hEpName, epNameBuf, ARRAY_SIZE(epNameBuf)); + epData->name = epNameBuf; + EndDialog(hDlg, 0); + } else { + EndDialog(hDlg, 1); + } + } + } else if (LOWORD(wParam) == IDCANCEL) { + EndDialog(hDlg, 1); + } + return FALSE; + case WM_CLOSE: + EndDialog(hDlg, 1); + return TRUE; + default: + break; + } + return FALSE; +} + +static std::vector ExportEndpointNameList(HWND hList) +{ + std::vector items; + DWORD itemCount = ListBox_GetCount(hList); + for (DWORD i = 0; i < itemCount; ++i) { + wchar_t textBuf[ENDPOINT_NAME_MAX_LEN + 1] = { 0 }; + DWORD textLen = ListBox_GetTextLen(hList, i); + if (textLen < ARRAY_SIZE(textBuf)) { + ListBox_GetText(hList, i, textBuf); + items.push_back(textBuf); + } + } + return items; +} + +INT_PTR CALLBACK Settings_ManageEndpointsDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch (msg) { + case WM_INITDIALOG: + { + if (IsAppThemed()) { + EnableThemeDialogTexture(hDlg, ETDT_ENABLETAB); + } + WMSettings *settings = reinterpret_cast(lParam); + assert(settings != nullptr); + SetWindowLongPtr(hDlg, GWLP_USERDATA, reinterpret_cast(settings)); + + DWORD endpointMode = settings->QueryValue(SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS_MODE); + if (endpointMode == MUTE_ENDPOINT_MODE_INDIVIDUAL_ALLOW_LIST) { + Button_SetCheck(GetDlgItem(hDlg, IDC_ENDPOINT_LIST_IS_ALLOWLIST), BST_CHECKED); + Button_SetCheck(GetDlgItem(hDlg, IDC_ENDPOINT_LIST_IS_BLOCKLIST), BST_UNCHECKED); + } else if (endpointMode == MUTE_ENDPOINT_MODE_INDIVIDUAL_BLOCK_LIST) { + Button_SetCheck(GetDlgItem(hDlg, IDC_ENDPOINT_LIST_IS_ALLOWLIST), BST_UNCHECKED); + Button_SetCheck(GetDlgItem(hDlg, IDC_ENDPOINT_LIST_IS_BLOCKLIST), BST_CHECKED); + } + + HWND hList = GetDlgItem(hDlg, IDC_ENDPOINT_LIST); + const auto devices = settings->GetManagedAudioEndpoints(); + for (const auto &dev : devices) { + ListBox_AddString(hList, dev.c_str()); + } + Button_Enable( + GetDlgItem(hDlg, IDC_ENDPOINT_REMOVEALL), + ListBox_GetCount(hList) > 0); + + return TRUE; + } + case WM_COMMAND: + { + if (LOWORD(wParam) == IDC_ENDPOINT_LIST) { + HWND hList = GetDlgItem(hDlg, IDC_ENDPOINT_LIST); + if (HIWORD(wParam) == LBN_SELCHANGE || HIWORD(wParam) == LBN_SELCANCEL) { + const bool entrySelected = (ListBox_GetCurSel(hList) != LB_ERR); + Button_Enable(GetDlgItem(hDlg, IDC_ENDPOINT_EDIT), entrySelected); + Button_Enable(GetDlgItem(hDlg, IDC_ENDPOINT_REMOVE), entrySelected); + } else if (HIWORD(wParam) == LBN_KILLFOCUS) { + const bool entrySelected = (ListBox_GetCurSel(hList) != LB_ERR); + Button_Enable(GetDlgItem(hDlg, IDC_ENDPOINT_EDIT), entrySelected); + Button_Enable(GetDlgItem(hDlg, IDC_ENDPOINT_REMOVE), entrySelected); + } + } else if (LOWORD(wParam) == IDC_ENDPOINT_ADD) { + EndpointData epData; + if (DialogBoxParam( + nullptr, + MAKEINTRESOURCE(IDD_MANAGE_ENDPOINTS_ADD), + hDlg, + Settings_EndpointAddDlgProc, + reinterpret_cast(&epData)) == 0) { + std::vector devices = ExportEndpointNameList(GetDlgItem(hDlg, IDC_ENDPOINT_LIST)); + if (std::find(begin(devices), end(devices), epData.name) == end(devices)) { + ListBox_AddString( + GetDlgItem(hDlg, IDC_ENDPOINT_LIST), + epData.name.c_str()); + HWND hRemoveAll = GetDlgItem(hDlg, IDC_ENDPOINT_REMOVEALL); + if (!IsWindowEnabled(hRemoveAll)) { + Button_Enable(hRemoveAll, TRUE); + } + } + } + } else if (LOWORD(wParam) == IDC_ENDPOINT_EDIT) { + HWND hList = GetDlgItem(hDlg, IDC_ENDPOINT_LIST); + const WPARAM sel = ListBox_GetCurSel(hList); + if (sel != LB_ERR) { + const int len = ListBox_GetTextLen(hList, sel); + wchar_t *textBuf = nullptr; + if (len != LB_ERR) { + if ((textBuf = new wchar_t[static_cast(len) + 1]) != nullptr) { + ListBox_GetText(hList, sel, textBuf); + + EndpointData epData; + epData.name = textBuf; + delete[] textBuf; + + if (DialogBoxParam( + nullptr, + MAKEINTRESOURCE(IDD_MANAGE_ENDPOINTS_ADD), + hDlg, + Settings_EndpointAddDlgProc, + reinterpret_cast(&epData)) == 0) { + std::vector networks = ExportEndpointNameList(GetDlgItem(hDlg, IDC_WIFI_LIST)); + if (std::find(begin(networks), end(networks), epData.name) == end(networks)) { + ListBox_InsertString(hList, sel, epData.name.c_str()); + ListBox_DeleteString(hList, sel + 1); + } else { + ListBox_DeleteString(hList, sel); + } + } + } + } + } + } else if (LOWORD(wParam) == IDC_ENDPOINT_REMOVE) { + HWND hList = GetDlgItem(hDlg, IDC_ENDPOINT_LIST); + const WPARAM sel = ListBox_GetCurSel(hList); + if (sel != LB_ERR) { + ListBox_DeleteString(hList, sel); + if (ListBox_GetCount(hList) == 0) { + Button_Enable(GetDlgItem(hDlg, IDC_ENDPOINT_EDIT), FALSE); + Button_Enable(GetDlgItem(hDlg, IDC_ENDPOINT_REMOVE), FALSE); + Button_Enable(GetDlgItem(hDlg, IDC_ENDPOINT_REMOVEALL), FALSE); + } + } + } else if (LOWORD(wParam) == IDC_ENDPOINT_REMOVEALL) { + ListBox_ResetContent(GetDlgItem(hDlg, IDC_ENDPOINT_LIST)); + Button_Enable(GetDlgItem(hDlg, IDC_ENDPOINT_EDIT), FALSE); + Button_Enable(GetDlgItem(hDlg, IDC_ENDPOINT_REMOVE), FALSE); + Button_Enable(GetDlgItem(hDlg, IDC_ENDPOINT_REMOVEALL), FALSE); + } else if (LOWORD(wParam) == IDOK) { + WMSettings *settings = reinterpret_cast(GetWindowLongPtr(hDlg, GWLP_USERDATA)); + std::vector devices = ExportEndpointNameList(GetDlgItem(hDlg, IDC_ENDPOINT_LIST)); + settings->StoreManagedAudioEndpoints(devices); + if (Button_GetCheck(GetDlgItem(hDlg, IDC_ENDPOINT_LIST_IS_ALLOWLIST))) { + settings->SetValue(SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS_MODE, MUTE_ENDPOINT_MODE_INDIVIDUAL_ALLOW_LIST); + } else if (Button_GetCheck(GetDlgItem(hDlg, IDC_ENDPOINT_LIST_IS_BLOCKLIST))) { + settings->SetValue(SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS_MODE, MUTE_ENDPOINT_MODE_INDIVIDUAL_BLOCK_LIST); + } + EndDialog(hDlg, 0); + } else if (LOWORD(wParam) == IDCANCEL) { + EndDialog(hDlg, 0); + } + return 0; + } + default: + break; + } + return FALSE; +} diff --git a/WinMute/Settings_MuteDlg.cpp b/WinMute/Settings_MuteDlg.cpp index 7be5ab9..380db97 100644 --- a/WinMute/Settings_MuteDlg.cpp +++ b/WinMute/Settings_MuteDlg.cpp @@ -33,6 +33,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "common.h" +extern INT_PTR CALLBACK Settings_ManageEndpointsDlgProc(HWND, UINT, WPARAM, LPARAM); + static void SetCheckButton(HWND hBtn, const WMSettings& settings, SettingsKey key) { const DWORD enabled = !!settings.QueryValue(key); @@ -57,6 +59,7 @@ INT_PTR CALLBACK Settings_MuteDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM } HWND hNotify = GetDlgItem(hDlg, IDC_SHOWNOTIFICATIONS); + HWND hManageEndpoints = GetDlgItem(hDlg, IDC_MANAGE_AUDIO_ENDPOINTS_INDIVIDUALLY); HWND hMuteOnLock = GetDlgItem(hDlg, IDC_MUTE_WHEN_WS_LOCKED); HWND hMuteOnScreenOff = GetDlgItem(hDlg, IDC_MUTE_WHEN_SCREEN_OFF); @@ -73,6 +76,8 @@ INT_PTR CALLBACK Settings_MuteDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM // General SetCheckButton(hNotify, *settings, SettingsKey::NOTIFICATIONS_ENABLED); + SetCheckButton(hManageEndpoints, *settings, SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS); + Button_Enable(GetDlgItem(hDlg, IDC_MANAGE_ENDPOINTS), Button_GetCheck(hManageEndpoints) == BST_CHECKED); // With restore SetCheckButton(hMuteOnLock, *settings, SettingsKey::MUTE_ON_LOCK); @@ -87,11 +92,28 @@ INT_PTR CALLBACK Settings_MuteDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM return TRUE; } + case WM_COMMAND: + if (LOWORD(wParam) == IDC_MANAGE_AUDIO_ENDPOINTS_INDIVIDUALLY) { + int isEnabled = Button_GetCheck(GetDlgItem(hDlg, IDC_MANAGE_AUDIO_ENDPOINTS_INDIVIDUALLY)); + Button_Enable(GetDlgItem(hDlg, IDC_MANAGE_ENDPOINTS), isEnabled); + } else if (LOWORD(wParam) == IDC_MANAGE_ENDPOINTS) { + WMSettings *settings = reinterpret_cast(GetWindowLongPtr(hDlg, GWLP_USERDATA)); + if (!DialogBoxParam( + nullptr, + MAKEINTRESOURCE(IDD_MANAGE_ENDPOINTS), + hDlg, + Settings_ManageEndpointsDlgProc, + reinterpret_cast(settings)) == 0) { + PrintWindowsError(L"DialogBoxParam", GetLastError()); + } + } + return 0; case WM_SAVESETTINGS: { WMSettings* settings = reinterpret_cast(GetWindowLongPtr(hDlg, GWLP_USERDATA)); HWND hNotify = GetDlgItem(hDlg, IDC_SHOWNOTIFICATIONS); + HWND hManageEndpoints = GetDlgItem(hDlg, IDC_MANAGE_AUDIO_ENDPOINTS_INDIVIDUALLY); HWND hMuteOnLock = GetDlgItem(hDlg, IDC_MUTE_WHEN_WS_LOCKED); HWND hMuteOnScreenOff = GetDlgItem(hDlg, IDC_MUTE_WHEN_SCREEN_OFF); @@ -104,6 +126,7 @@ INT_PTR CALLBACK Settings_MuteDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM // General SetOption(hNotify, *settings, SettingsKey::NOTIFICATIONS_ENABLED); + SetOption(hManageEndpoints, *settings, SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS); // With restore SetOption(hMuteOnLock, *settings, SettingsKey::MUTE_ON_LOCK); diff --git a/WinMute/VistaAudio.cpp b/WinMute/VistaAudio.cpp index 3a7f554..dddc7c3 100644 --- a/WinMute/VistaAudio.cpp +++ b/WinMute/VistaAudio.cpp @@ -61,6 +61,8 @@ VistaAudio::VistaAudio() : deviceEnumerator_(nullptr), mmnAudioEvents_(nullptr), reInit_(false), + muteSpecificEndpoints_(false), + muteSpecificEndpointsAllowList_(false), hParent_(nullptr) { if (FAILED(CoInitialize(nullptr))) { @@ -260,6 +262,10 @@ bool VistaAudio::RestoreMuteStatus() WMLog& log = WMLog::GetInstance(); for (auto& e : endpoints_) { + if (!IsEndpointManaged(e->deviceName)) { + log.Write(L"Skipping Endpoint %s", e->deviceName); + continue; + } log.Write(L"Restoring: Mute %s for \"%s\"", (e->wasMuted) ? L"true" : L"false", e->deviceName); @@ -282,6 +288,10 @@ void VistaAudio::SetMute(bool mute) if (CheckForReInit()) { for (auto& e : endpoints_) { BOOL isMuted = !mute; + if (!IsEndpointManaged(e->deviceName)) { + log.Write(L"Skipping Endpoint %s", e->deviceName); + continue; + } if (FAILED(e->endpointVolume->GetMute(&isMuted))) { log.Write(L"Failed to get mute status for \"%s\"", e->deviceName); @@ -296,3 +306,39 @@ void VistaAudio::SetMute(bool mute) } } } + +bool VistaAudio::IsEndpointManaged(const std::wstring &endpointName) const +{ + if (!muteSpecificEndpoints_) { + return true; + } + + const bool inManagedEndpoints = std::find( + std::begin(managedEndpointNames_), + std::end(managedEndpointNames_), + endpointName) != std::end(managedEndpointNames_); + + // ---------------------------- + // | In List | Not in List | + // ------------+----------+-------------+ + // Allow List | Mute | Not mute | + // Block List | Not mute | Mute | + return inManagedEndpoints + ? muteSpecificEndpointsAllowList_ + : !muteSpecificEndpointsAllowList_; +} + +void VistaAudio::MuteSpecificEndpoints(bool muteSpecific) +{ + muteSpecificEndpoints_ = muteSpecific; +} + +void VistaAudio::SetManagedEndpoints( + const std::vector &endpoints, + bool isAllowList) +{ + managedEndpointNames_ = endpoints; + muteSpecificEndpointsAllowList_ = isAllowList; +} + + diff --git a/WinMute/WMSettings.cpp b/WinMute/WMSettings.cpp index cc3d5b5..aecf1fd 100644 --- a/WinMute/WMSettings.cpp +++ b/WinMute/WMSettings.cpp @@ -33,6 +33,8 @@ POSSIBILITY OF SUCH DAMAGE. #include "common.h" +static constexpr int CURRENT_SETTINGS_VERSION = 0; + static const wchar_t *LX_SYSTEMS_SUBKEY = L"SOFTWARE\\lx-systems\\WinMute"; static const wchar_t* LX_SYSTEMS_WIFI_SUBKEY @@ -49,6 +51,9 @@ static const wchar_t* KeyToStr(SettingsKey key) { const wchar_t* keyStr = nullptr; switch (key) { + case SettingsKey::SETTINGS_VERSION: + keyStr = L"SettingsVersion"; + break; case SettingsKey::MUTE_ON_LOCK: keyStr = L"MuteOnLock"; break; @@ -85,6 +90,9 @@ static const wchar_t* KeyToStr(SettingsKey key) case SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS: keyStr = L"MuteIndividualEndpoints"; break; + case SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS_MODE: + keyStr = L"MuteIndividualEndpointsMode"; + break; case SettingsKey::QUIETHOURS_ENABLE: keyStr = L"QuietHoursEnabled"; break; @@ -113,6 +121,8 @@ static const wchar_t* KeyToStr(SettingsKey key) static DWORD GetDefaultSetting(SettingsKey key) { switch (key) { + case SettingsKey::SETTINGS_VERSION: + return 0; case SettingsKey::MUTE_ON_LOCK: return 1; case SettingsKey::MUTE_ON_DISPLAYSTANDBY: @@ -133,6 +143,8 @@ static DWORD GetDefaultSetting(SettingsKey key) return 0; case SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS: return 0; + case SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS_MODE: + return MUTE_ENDPOINT_MODE_INDIVIDUAL_ALLOW_LIST; case SettingsKey::QUIETHOURS_ENABLE: return 0; case SettingsKey::QUIETHOURS_FORCEUNMUTE: @@ -202,6 +214,15 @@ WMSettings::~WMSettings() Unload(); } +bool WMSettings::MigrateSettings() +{ + DWORD version = QueryValue(SettingsKey::SETTINGS_VERSION); + if (version != CURRENT_SETTINGS_VERSION) { + WMLog::GetInstance().Write(L"Unexpected settings version: %d", version); + } + return true; +} + bool WMSettings::Init() { if (hSettingsKey_ == nullptr) { @@ -267,7 +288,7 @@ bool WMSettings::Init() 0, KEY_READ | KEY_WRITE, nullptr, - &hBluetoothKey_, + &hAudioEndpointsKey_, nullptr); if (regError != ERROR_SUCCESS) { PrintWindowsError(L"RegCreateKeyEx", regError); @@ -281,6 +302,8 @@ bool WMSettings::Init() } } + MigrateSettings(); + return true; } diff --git a/WinMute/WMSettings.h b/WinMute/WMSettings.h index 081c659..46bfe98 100644 --- a/WinMute/WMSettings.h +++ b/WinMute/WMSettings.h @@ -35,8 +35,14 @@ POSSIBILITY OF SUCH DAMAGE. #include "common.h" +enum MuteEndPointMode { + MUTE_ENDPOINT_MODE_INDIVIDUAL_ALLOW_LIST = 0, + MUTE_ENDPOINT_MODE_INDIVIDUAL_BLOCK_LIST = 1 +}; + enum class SettingsKey { - MUTE_ON_LOCK + SETTINGS_VERSION + , MUTE_ON_LOCK , MUTE_ON_DISPLAYSTANDBY , MUTE_ON_RDP , RESTORE_AUDIO @@ -47,8 +53,9 @@ enum class SettingsKey { , MUTE_ON_BLUETOOTH_DEVICELIST , MUTE_ON_WLAN , MUTE_ON_WLAN_ALLOWLIST - // 0 = mute all, 1 = mute specific (allowlist), 2 = mute specific (blocklist) - , MUTE_INDIVIDUAL_ENDPOINTS + , MUTE_INDIVIDUAL_ENDPOINTS + // 1 = mute specific (allowlist), 2 = mute specific (blocklist) + , MUTE_INDIVIDUAL_ENDPOINTS_MODE , QUIETHOURS_ENABLE , QUIETHOURS_FORCEUNMUTE , QUIETHOURS_NOTIFICATIONS @@ -91,5 +98,6 @@ private: HKEY hBluetoothKey_; HKEY hAudioEndpointsKey_; + bool MigrateSettings(); HKEY OpenAutostartKey(REGSAM samDesired); }; diff --git a/WinMute/WinAudio.h b/WinMute/WinAudio.h index 2152cba..d6b7dba 100644 --- a/WinMute/WinAudio.h +++ b/WinMute/WinAudio.h @@ -50,6 +50,10 @@ public: virtual bool SaveMuteStatus() = 0; virtual bool RestoreMuteStatus() = 0; virtual void SetMute(bool mute) = 0; + virtual void MuteSpecificEndpoints(bool muteSpecific) = 0; + virtual void SetManagedEndpoints( + const std::vector &endpoints, + bool isAllowList) = 0; virtual ~WinAudio() noexcept {}; }; @@ -79,19 +83,29 @@ public: bool RestoreMuteStatus() override; void SetMute(bool mute) override; + void MuteSpecificEndpoints(bool muteSpecific); + void SetManagedEndpoints( + const std::vector &endpoints, + bool isAllowList); + private: void Uninit(); bool CheckForReInit(); bool LoadAllEndpoints(); + bool IsEndpointManaged(const std::wstring& endpointName) const; std::vector> endpoints_; MMNotificationClient* mmnAudioEvents_; IMMDeviceEnumeratorPtr deviceEnumerator_; bool reInit_; + bool muteSpecificEndpoints_; + bool muteSpecificEndpointsAllowList_; HWND hParent_; + std::vector managedEndpointNames_; + // non copy-able VistaAudio(const VistaAudio& other) = delete; VistaAudio& operator=(const VistaAudio& other) = delete; diff --git a/WinMute/WinMute.cpp b/WinMute/WinMute.cpp index 7ca9a9d..0c37005 100644 --- a/WinMute/WinMute.cpp +++ b/WinMute/WinMute.cpp @@ -311,8 +311,16 @@ bool WinMute::LoadSettings() log.Write(L"\t\tUse devicelist: %s", settings_.QueryValue(SettingsKey::MUTE_ON_BLUETOOTH_DEVICELIST) ? L"Yes" : L"No"); log.Write(L"\tMute on WLAN: %s", settings_.QueryValue(SettingsKey::MUTE_ON_WLAN) ? L"Yes" : L"No"); log.Write(L"\t\tUse allowlist: %s", settings_.QueryValue(SettingsKey::MUTE_ON_WLAN_ALLOWLIST) ? L"Yes" : L"No"); + log.Write(L"\tMute specific endpoints only: %s", settings_.QueryValue(SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS) ? L"Yes" : L"No"); } + if (!settings_.QueryValue(SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS)) { + muteCtrl_.ClearManagedEndpoints(); + } else { + const auto endpoints = settings_.GetManagedAudioEndpoints(); + const bool isAllowList = settings_.QueryValue(SettingsKey::MUTE_INDIVIDUAL_ENDPOINTS_MODE) == MUTE_ENDPOINT_MODE_INDIVIDUAL_ALLOW_LIST; + muteCtrl_.SetManagedEndpoints(endpoints, isAllowList); + } muteCtrl_.SetRestoreVolume(settings_.QueryValue(SettingsKey::RESTORE_AUDIO)); muteCtrl_.SetMuteOnWorkstationLock(settings_.QueryValue(SettingsKey::MUTE_ON_LOCK)); muteCtrl_.SetMuteOnDisplayStandby(settings_.QueryValue(SettingsKey::MUTE_ON_DISPLAYSTANDBY)); diff --git a/WinMute/WinMute.rc b/WinMute/WinMute.rc index 3cc6502..3e6dd45 100644 --- a/WinMute/WinMute.rc +++ b/WinMute/WinMute.rc @@ -127,7 +127,7 @@ FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN GROUPBOX "General",IDC_STATIC,7,7,228,57 CONTROL "Show mute event notifications",IDC_SHOWNOTIFICATIONS, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,17,219,18 + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,15,219,18 GROUPBOX "Mute with volume restore, when...",IDC_STATIC,7,67,228,57 CONTROL "... the workstation is locked.",IDC_MUTE_WHEN_WS_LOCKED, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,81,107,10 @@ -143,8 +143,8 @@ BEGIN CONTROL "... when WinMute is started within a RDP session.",IDC_MUTE_WHEN_RDP_SESSION, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,20,180,175,10 CONTROL "Manage audio endpoints individually",IDC_MANAGE_AUDIO_ENDPOINTS_INDIVIDUALLY, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,31,219,18 - PUSHBUTTON "Manage endpoints...",IDC_MANAGE_ENDPOINTS,26,47,74,14 + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,13,28,219,18 + PUSHBUTTON "Manage endpoints...",IDC_MANAGE_ENDPOINTS,26,46,74,15 END IDD_SETTINGS_WIFI DIALOGEX 0, 0, 242, 204 @@ -203,6 +203,37 @@ BEGIN COMBOBOX IDC_BT_DEVICE_NAME,7,22,187,17,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP END +IDD_MANAGE_ENDPOINTS DIALOGEX 0, 0, 241, 200 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Manage Endpoints" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "OK",IDOK,133,179,50,14 + PUSHBUTTON "Cancel",IDCANCEL,184,179,50,14 + LISTBOX IDC_ENDPOINT_LIST,13,60,159,110,LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Add",IDC_ENDPOINT_ADD,175,60,56,16 + PUSHBUTTON "Edit",IDC_ENDPOINT_EDIT,175,78,56,16 + PUSHBUTTON "Remove",IDC_ENDPOINT_REMOVE,175,97,56,16 + PUSHBUTTON "Remove all",IDC_ENDPOINT_REMOVEALL,175,120,56,16 + CONTROL "Mute only listed endpoints",IDC_ENDPOINT_LIST_IS_ALLOWLIST, + "Button",BS_AUTORADIOBUTTON,13,19,99,10 + CONTROL "Mute all but the listed endpoints",IDC_ENDPOINT_LIST_IS_BLOCKLIST, + "Button",BS_AUTORADIOBUTTON,13,32,118,10 + GROUPBOX "List behaviour",IDC_STATIC,7,7,227,41 + GROUPBOX "Endpoints",IDC_STATIC,7,48,227,128 +END + +IDD_MANAGE_ENDPOINTS_ADD DIALOGEX 0, 0, 201, 62 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Add Endpoint" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + DEFPUSHBUTTON "Save",IDOK,91,41,50,14 + PUSHBUTTON "Cancel",IDCANCEL,144,41,50,14 + LTEXT "Endpoint name:",IDC_STATIC,7,7,187,14 + COMBOBOX IDC_ENDPOINT_NAME,7,22,187,17,CBS_DROPDOWN | CBS_SORT | WS_VSCROLL | WS_TABSTOP +END + ///////////////////////////////////////////////////////////////////////////// // @@ -299,6 +330,22 @@ BEGIN TOPMARGIN, 7 BOTTOMMARGIN, 55 END + + IDD_MANAGE_ENDPOINTS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 234 + TOPMARGIN, 7 + BOTTOMMARGIN, 193 + END + + IDD_MANAGE_ENDPOINTS_ADD, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 194 + TOPMARGIN, 7 + BOTTOMMARGIN, 55 + END END #endif // APSTUDIO_INVOKED @@ -364,6 +411,16 @@ BEGIN 0 END +IDD_MANAGE_ENDPOINTS AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_MANAGE_ENDPOINTS_ADD AFX_DIALOG_LAYOUT +BEGIN + 0 +END + ///////////////////////////////////////////////////////////////////////////// // @@ -418,8 +475,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 2,2,0,0 - PRODUCTVERSION 2,2,0,0 + FILEVERSION 2,3,0,0 + PRODUCTVERSION 2,3,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -436,23 +493,23 @@ BEGIN BEGIN VALUE "CompanyName", "LX-Systems" VALUE "FileDescription", "WinMute" - VALUE "FileVersion", "2.2.0.0" + VALUE "FileVersion", "2.3.0.0" VALUE "InternalName", "WinMute.exe" VALUE "LegalCopyright", "Copyright (C) 2022, Alexander Steinhöfer" VALUE "OriginalFilename", "WinMute.exe" VALUE "ProductName", "WinMute" - VALUE "ProductVersion", "2.2.0.0" + VALUE "ProductVersion", "2.3.0.0" END BLOCK "040704b0" BEGIN VALUE "CompanyName", "LX-Systems" VALUE "FileDescription", "WinMute" - VALUE "FileVersion", "2.2.0.0" + VALUE "FileVersion", "2.3.0.0" VALUE "InternalName", "WinMute.exe" - VALUE "LegalCopyright", "Copyright (C) 2022, Alexander Steinhöfer" + VALUE "LegalCopyright", "Copyright (C) 2023, Alexander Steinhöfer" VALUE "OriginalFilename", "WinMute.exe" VALUE "ProductName", "WinMute" - VALUE "ProductVersion", "2.2.0.0" + VALUE "ProductVersion", "2.3.0.0" END END BLOCK "VarFileInfo" diff --git a/WinMute/WinMute.vcxproj b/WinMute/WinMute.vcxproj index cf8e9b6..f5f0ef3 100644 --- a/WinMute/WinMute.vcxproj +++ b/WinMute/WinMute.vcxproj @@ -232,6 +232,7 @@ Create + diff --git a/WinMute/WinMute.vcxproj.filters b/WinMute/WinMute.vcxproj.filters index 28012d9..bc4070f 100644 --- a/WinMute/WinMute.vcxproj.filters +++ b/WinMute/WinMute.vcxproj.filters @@ -185,5 +185,8 @@ Source Files\Controllers\Bluetooth + + Source Files\UI\Dialogs\Settings + \ No newline at end of file diff --git a/WinMute/resource.h b/WinMute/resource.h index 8412e7e..d435e72 100644 --- a/WinMute/resource.h +++ b/WinMute/resource.h @@ -21,6 +21,8 @@ #define IDD_DIALOG1 133 #define IDD_SETTINGS_BLUETOOTH_ADD 133 #define IDD_MANAGE_ENDPOINTS 135 +#define IDD_DIALOG2 137 +#define IDD_MANAGE_ENDPOINTS_ADD 137 #define IDC_LINK_HOMEPAGE 1001 #define IDC_LINK_SUPPORT 1002 #define IDC_LINK_PROJECT 1002 @@ -76,8 +78,13 @@ #define IDC_BUTTON1 1041 #define IDC_MANAGE_ENDPOINTS 1041 #define IDC_ENDPOINT_LIST_IS_ALLOWLIST 1042 -#define IDC_MUTE_SPECIFIC_ENDPOINTS2 1043 #define IDC_ENDPOINT_LIST_IS_BLOCKLIST 1043 +#define IDC_ENDPOINT_LIST 1044 +#define IDC_ENDPOINT_ADD 1045 +#define IDC_ENDPOINT_EDIT 1046 +#define IDC_ENDPOINT_REMOVE 1047 +#define IDC_ENDPOINT_REMOVEALL 1048 +#define IDC_ENDPOINT_NAME 1049 #define ID_TRAYMENU_INFO 40001 #define ID_TRAYMENU_ 40002 #define ID_TRAYMENU_MUTEON 40003 @@ -119,9 +126,9 @@ // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 137 +#define _APS_NEXT_RESOURCE_VALUE 139 #define _APS_NEXT_COMMAND_VALUE 40037 -#define _APS_NEXT_CONTROL_VALUE 1043 +#define _APS_NEXT_CONTROL_VALUE 1050 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif -- 2.43.0