]> repos.lx-s.de Git - winmute.git/commitdiff
Added endpoint control.
authorlx-s <lx-s@users.noreply.github.com>
Thu, 13 Apr 2023 12:53:42 +0000 (14:53 +0200)
committerlx-s <lx-s@users.noreply.github.com>
Thu, 13 Apr 2023 12:53:42 +0000 (14:53 +0200)
14 files changed:
WinMute/MuteControl.cpp
WinMute/MuteControl.h
WinMute/Settings_BluetoothDlg.cpp
WinMute/Settings_ManageEndpointsDlg.cpp
WinMute/Settings_MuteDlg.cpp
WinMute/VistaAudio.cpp
WinMute/WMSettings.cpp
WinMute/WMSettings.h
WinMute/WinAudio.h
WinMute/WinMute.cpp
WinMute/WinMute.rc
WinMute/WinMute.vcxproj
WinMute/WinMute.vcxproj.filters
WinMute/resource.h

index 9315815819e56a54b8fb8d953993fae8d7a2a5ef..9b121651a876e6f27b4c5b1239ccf863aa21de78 100644 (file)
@@ -308,3 +308,15 @@ void MuteControl::NotifyQuietHours(bool active)
       RestoreVolume();
    }
 }
+
+void MuteControl::SetManagedEndpoints(
+   const std::vector<std::wstring> endpoints,
+   bool isAllowList)
+{
+   winAudio_->SetManagedEndpoints(endpoints, isAllowList);
+}
+
+void MuteControl::ClearManagedEndpoints()
+{
+   winAudio_->MuteSpecificEndpoints(false);
+}
index a5668024143295039f523bb358d5216e82fda6c9..db22ba4ecd7da3323bb4e89eab95874f8892e382 100644 (file)
@@ -80,6 +80,10 @@ public:
    void NotifyShutdown();
 
    void NotifyQuietHours(bool active);
+
+   void SetManagedEndpoints(const std::vector<std::wstring> endpoints,
+                            bool isAllowList);
+   void ClearManagedEndpoints();
 private:
    struct MuteConfig {
       bool shouldMute;
index f6069e9fce221a94805967f5a6ffd11c6b788672..3d7417fd66174166fcfee50906a1038b9c85c1e1 100644 (file)
@@ -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<BtDeviceName*>(lParam);
+      BtDeviceData* btDeviceData = reinterpret_cast<BtDeviceData*>(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<BtDeviceName*>(GetWindowLongPtr(hDlg, GWLP_USERDATA));
+            BtDeviceData* btDevName = reinterpret_cast<BtDeviceData*>(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<size_t>(len) + 1]) != nullptr) {
                   ListBox_GetText(hList, sel, textBuf);
 
-                  BtDeviceName btDevName;
+                  BtDeviceData btDevName;
                   btDevName.devName = textBuf;
                   delete[] textBuf;
 
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..16f9f4bae99473c887e45c8278171c449c92fe9c 100644 (file)
@@ -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 <mmdeviceapi.h>
+#include <Functiondiscoverykeys_devpkey.h>
+
+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<std::wstring>& 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<EndpointData*>(lParam);
+      if (endpointData == nullptr) {
+         return FALSE;
+      }
+      SetWindowLongPtr(hDlg, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(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<std::wstring> 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<HWND>(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<EndpointData*>(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<std::wstring> ExportEndpointNameList(HWND hList)
+{
+   std::vector<std::wstring> 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<WMSettings *>(lParam);
+      assert(settings != nullptr);
+      SetWindowLongPtr(hDlg, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(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<LPARAM>(&epData)) == 0) {
+            std::vector<std::wstring> 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<size_t>(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<LPARAM>(&epData)) == 0) {
+                     std::vector<std::wstring> 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<WMSettings *>(GetWindowLongPtr(hDlg, GWLP_USERDATA));
+         std::vector<std::wstring> 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;
+}
index 7be5ab97ecbae3c29be56f7eda681ae44b41b279..380db97c4beea80a5bebed87a586423bfe1eda99 100644 (file)
@@ -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<WMSettings *>(GetWindowLongPtr(hDlg, GWLP_USERDATA));
+         if (!DialogBoxParam(
+               nullptr,
+               MAKEINTRESOURCE(IDD_MANAGE_ENDPOINTS),
+               hDlg,
+               Settings_ManageEndpointsDlgProc,
+               reinterpret_cast<LPARAM>(settings)) == 0) {
+            PrintWindowsError(L"DialogBoxParam", GetLastError());
+         }
+      }
+      return 0;
    case WM_SAVESETTINGS:
    {
       WMSettings* settings = reinterpret_cast<WMSettings*>(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);
index 3a7f554b4225214536fd467707912e285f157a10..dddc7c3f6235ed42e093f39513444e5c6d996b08 100644 (file)
@@ -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<std::wstring> &endpoints,
+   bool isAllowList)
+{
+   managedEndpointNames_ = endpoints;
+   muteSpecificEndpointsAllowList_ = isAllowList;
+}
+
+
index cc3d5b585add67d1181e4e53d0d2729e4f36541f..aecf1fdeea15de0b67e9715e89a34356bdfb077b 100644 (file)
@@ -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;
 }
 
index 081c6595ceb52dac6fa34278792451afc0fc6935..46bfe98a0009c058948a59bf78dfa45f0f746f0c 100644 (file)
@@ -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);
 };
index 2152cba4205e1fd69217638b637a95ef633c3d6d..d6b7dbad8f33d7e5b31f7e25897c8c2ad28b573b 100644 (file)
@@ -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<std::wstring> &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<std::wstring> &endpoints,
+      bool isAllowList);
+
 private:
    void Uninit();
    bool CheckForReInit();
 
    bool LoadAllEndpoints();
+   bool IsEndpointManaged(const std::wstring& endpointName) const;
 
    std::vector<std::unique_ptr<Endpoint>> endpoints_;
    MMNotificationClient* mmnAudioEvents_;
    IMMDeviceEnumeratorPtr deviceEnumerator_;
 
    bool reInit_;
+   bool muteSpecificEndpoints_;
+   bool muteSpecificEndpointsAllowList_;
    HWND hParent_;
 
+   std::vector<std::wstring> managedEndpointNames_;
+
    // non copy-able
    VistaAudio(const VistaAudio& other) = delete;
    VistaAudio& operator=(const VistaAudio& other) = delete;
index 7ca9a9d3a6f12c17d2ab1485f9bce8ab7f895ead..0c3700543312628f2e398051585592b1051c283b 100644 (file)
@@ -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));
index 3cc6502460c6eeb041f2029119e42ae4602603a1..3e6dd45e27969df26bd4fd8f781188c75f89c35a 100644 (file)
@@ -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"
index cf8e9b60ea3db8171726df135ace44c726aae24f..f5f0ef36f083d99d9714c2ec62c04d25bb1eb96f 100644 (file)
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
     </ClCompile>
     <ClCompile Include="Settings_BluetoothDlg.cpp" />
+    <ClCompile Include="Settings_ManageEndpointsDlg.cpp" />
     <ClCompile Include="Settings_MuteDlg.cpp" />
     <ClCompile Include="Settings_WifiDlg.cpp" />
     <ClCompile Include="WifiDetector.cpp" />
index 28012d972182f1445b2cc17506331ff7f9fff999..bc4070f97a08daac927e79a6b92a61100c74a183 100644 (file)
     <ClCompile Include="BluetoothDetector.cpp">
       <Filter>Source Files\Controllers\Bluetooth</Filter>
     </ClCompile>
+    <ClCompile Include="Settings_ManageEndpointsDlg.cpp">
+      <Filter>Source Files\UI\Dialogs\Settings</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>
\ No newline at end of file
index 8412e7e68c7cb0e188a4621e5fd7db54854bc467..d435e72afef6c3fb6d29ca1fefa05bf556f4829f 100644 (file)
@@ -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
 #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
 // 
 #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