From d246127ad9fb37e6d50b1d6f5c2d9a5f2c66d1a6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:15:55 +0000 Subject: [PATCH 1/7] Throttle serial update dispatch and cache process lookups Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> --- ControlPad/Arduino/ArduinoController.cs | 10 +++++++++- ControlPad/System/AudioController.cs | 25 ++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/ControlPad/Arduino/ArduinoController.cs b/ControlPad/Arduino/ArduinoController.cs index 853eeee..2d834ef 100644 --- a/ControlPad/Arduino/ArduinoController.cs +++ b/ControlPad/Arduino/ArduinoController.cs @@ -24,6 +24,8 @@ public static class ArduinoController private static readonly StringBuilder _lineBuf = new(); private static BoardType _lastBoardType = BoardType.None; private static BadgeType _lastBadgeType = BadgeType.None; + private static long _lastEventDispatchTick; + private const int EventDispatchIntervalMs = 16; public static void Initialize(MainWindow mainWindow, EventHandler eventHandler) { @@ -202,7 +204,13 @@ private static void ProcessLine(string line) UpdateValues(inputs[2..]); - // UI-Updates ggf. drosseln + long nowTick = Environment.TickCount64; + if (nowTick - _lastEventDispatchTick < EventDispatchIntervalMs) + { + return; + } + _lastEventDispatchTick = nowTick; + _mainWindow._homeUserControl.Dispatcher.BeginInvoke(() => _eventHandler.Update(DataHandler.SliderValues, DataHandler.ButtonValues) ); diff --git a/ControlPad/System/AudioController.cs b/ControlPad/System/AudioController.cs index 6a422ca..a7307ae 100644 --- a/ControlPad/System/AudioController.cs +++ b/ControlPad/System/AudioController.cs @@ -6,12 +6,15 @@ using System.Diagnostics; using System.Threading; using System.Windows; +using System.Linq; namespace ControlPad { public class AudioController { private MMDeviceEnumerator _enum; + private readonly Dictionary ProcessIds)> _processIdsCache = new(); + private const int ProcessIdsCacheLifetimeMs = 500; public AudioController() { @@ -24,7 +27,7 @@ public void SetProcessVolume(string processName, float volume) volume = Math.Clamp(volume, 0f, 1f); - List processIds = Process.GetProcessesByName(processName).Select(c => c.Id).ToList(); // this might be slow + HashSet processIds = GetProcessIds(processName); for (int i = 0; i < sessions?.Count; i++) @@ -61,7 +64,7 @@ public void MuteProcess(string processName, bool mute) using var device = _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); var sessions = device.AudioSessionManager.Sessions; - List processIds = Process.GetProcessesByName(processName).Select(c => c.Id).ToList(); // this might be slow + HashSet processIds = GetProcessIds(processName); for (int i = 0; i < sessions?.Count; i++) { @@ -94,7 +97,7 @@ public bool IsProcessMute(string processName) using var device = _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); var sessions = device.AudioSessionManager.Sessions; - List processIds = Process.GetProcessesByName(processName).Select(c => c.Id).ToList(); // this might be slow + HashSet processIds = GetProcessIds(processName); for (int i = 0; i < sessions?.Count; i++) { @@ -147,5 +150,21 @@ public SessionCollection GetAudioSessions() var sessions = device.AudioSessionManager.Sessions; return sessions; } + + private HashSet GetProcessIds(string processName) + { + long nowTick = Environment.TickCount64; + if (_processIdsCache.TryGetValue(processName, out var cacheEntry)) + { + if (nowTick - cacheEntry.Tick <= ProcessIdsCacheLifetimeMs) + { + return cacheEntry.ProcessIds; + } + } + + var processIds = Process.GetProcessesByName(processName).Select(c => c.Id).ToHashSet(); + _processIdsCache[processName] = (nowTick, processIds); + return processIds; + } } } From 93fd764a09fd12f0707c3bbf8c7d02d8d0816c6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:19:51 +0000 Subject: [PATCH 2/7] Refine throttling and cache housekeeping Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> --- ControlPad/Arduino/ArduinoController.cs | 16 +++++++++++++--- ControlPad/System/AudioController.cs | 25 ++++++++++++++++--------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/ControlPad/Arduino/ArduinoController.cs b/ControlPad/Arduino/ArduinoController.cs index 2d834ef..f32508d 100644 --- a/ControlPad/Arduino/ArduinoController.cs +++ b/ControlPad/Arduino/ArduinoController.cs @@ -26,6 +26,7 @@ public static class ArduinoController private static BadgeType _lastBadgeType = BadgeType.None; private static long _lastEventDispatchTick; private const int EventDispatchIntervalMs = 16; + private static readonly object _eventDispatchLock = new(); public static void Initialize(MainWindow mainWindow, EventHandler eventHandler) { @@ -204,12 +205,21 @@ private static void ProcessLine(string line) UpdateValues(inputs[2..]); - long nowTick = Environment.TickCount64; - if (nowTick - _lastEventDispatchTick < EventDispatchIntervalMs) + bool shouldDispatch = false; + lock (_eventDispatchLock) + { + long nowTick = Environment.TickCount64; + if (nowTick - _lastEventDispatchTick >= EventDispatchIntervalMs) + { + _lastEventDispatchTick = nowTick; + shouldDispatch = true; + } + } + + if (!shouldDispatch) { return; } - _lastEventDispatchTick = nowTick; _mainWindow._homeUserControl.Dispatcher.BeginInvoke(() => _eventHandler.Update(DataHandler.SliderValues, DataHandler.ButtonValues) diff --git a/ControlPad/System/AudioController.cs b/ControlPad/System/AudioController.cs index a7307ae..7517609 100644 --- a/ControlPad/System/AudioController.cs +++ b/ControlPad/System/AudioController.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Collections.Concurrent; using System.Diagnostics; using System.Threading; using System.Windows; @@ -13,7 +14,7 @@ namespace ControlPad public class AudioController { private MMDeviceEnumerator _enum; - private readonly Dictionary ProcessIds)> _processIdsCache = new(); + private readonly ConcurrentDictionary _processIdsCache = new(); private const int ProcessIdsCacheLifetimeMs = 500; public AudioController() @@ -27,14 +28,14 @@ public void SetProcessVolume(string processName, float volume) volume = Math.Clamp(volume, 0f, 1f); - HashSet processIds = GetProcessIds(processName); + int[] processIds = GetProcessIds(processName); for (int i = 0; i < sessions?.Count; i++) { var session = sessions[i]; - if (processIds.Contains((int)session.GetProcessID)) + if (Array.IndexOf(processIds, (int)session.GetProcessID) >= 0) { session.SimpleAudioVolume.Volume = volume; } @@ -64,12 +65,12 @@ public void MuteProcess(string processName, bool mute) using var device = _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); var sessions = device.AudioSessionManager.Sessions; - HashSet processIds = GetProcessIds(processName); + int[] processIds = GetProcessIds(processName); for (int i = 0; i < sessions?.Count; i++) { var session = sessions[i]; - if (processIds.Contains((int)session.GetProcessID)) + if (Array.IndexOf(processIds, (int)session.GetProcessID) >= 0) { session.SimpleAudioVolume.Mute = mute; } @@ -97,12 +98,12 @@ public bool IsProcessMute(string processName) using var device = _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); var sessions = device.AudioSessionManager.Sessions; - HashSet processIds = GetProcessIds(processName); + int[] processIds = GetProcessIds(processName); for (int i = 0; i < sessions?.Count; i++) { var session = sessions[i]; - if (processIds.Contains((int)session.GetProcessID)) + if (Array.IndexOf(processIds, (int)session.GetProcessID) >= 0) { return session.SimpleAudioVolume.Mute; } @@ -151,7 +152,7 @@ public SessionCollection GetAudioSessions() return sessions; } - private HashSet GetProcessIds(string processName) + private int[] GetProcessIds(string processName) { long nowTick = Environment.TickCount64; if (_processIdsCache.TryGetValue(processName, out var cacheEntry)) @@ -162,7 +163,13 @@ private HashSet GetProcessIds(string processName) } } - var processIds = Process.GetProcessesByName(processName).Select(c => c.Id).ToHashSet(); + var processes = Process.GetProcessesByName(processName); + var processIds = new int[processes.Length]; + for (int i = 0; i < processes.Length; i++) + { + processIds[i] = processes[i].Id; + processes[i].Dispose(); + } _processIdsCache[processName] = (nowTick, processIds); return processIds; } From 27964d3f0b9496ba89c4c748e79a9e4ee6ab1f84 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 19:23:47 +0000 Subject: [PATCH 3/7] Finalize issue #20 performance improvements Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> --- ControlPad/Arduino/ArduinoController.cs | 2 +- ControlPad/System/AudioController.cs | 32 ++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/ControlPad/Arduino/ArduinoController.cs b/ControlPad/Arduino/ArduinoController.cs index f32508d..be640bf 100644 --- a/ControlPad/Arduino/ArduinoController.cs +++ b/ControlPad/Arduino/ArduinoController.cs @@ -209,7 +209,7 @@ private static void ProcessLine(string line) lock (_eventDispatchLock) { long nowTick = Environment.TickCount64; - if (nowTick - _lastEventDispatchTick >= EventDispatchIntervalMs) + if ((nowTick - _lastEventDispatchTick) >= EventDispatchIntervalMs) { _lastEventDispatchTick = nowTick; shouldDispatch = true; diff --git a/ControlPad/System/AudioController.cs b/ControlPad/System/AudioController.cs index 7517609..2b7e1dd 100644 --- a/ControlPad/System/AudioController.cs +++ b/ControlPad/System/AudioController.cs @@ -16,6 +16,8 @@ public class AudioController private MMDeviceEnumerator _enum; private readonly ConcurrentDictionary _processIdsCache = new(); private const int ProcessIdsCacheLifetimeMs = 500; + private const int ProcessIdsCacheMaxEntries = 128; + private long _lastProcessCacheTrimTick = Environment.TickCount64; public AudioController() { @@ -157,7 +159,7 @@ private int[] GetProcessIds(string processName) long nowTick = Environment.TickCount64; if (_processIdsCache.TryGetValue(processName, out var cacheEntry)) { - if (nowTick - cacheEntry.Tick <= ProcessIdsCacheLifetimeMs) + if ((nowTick - cacheEntry.Tick) <= ProcessIdsCacheLifetimeMs) { return cacheEntry.ProcessIds; } @@ -171,7 +173,35 @@ private int[] GetProcessIds(string processName) processes[i].Dispose(); } _processIdsCache[processName] = (nowTick, processIds); + long lastTrimTick = Interlocked.Read(ref _lastProcessCacheTrimTick); + if (_processIdsCache.Count >= ProcessIdsCacheMaxEntries || + (nowTick - lastTrimTick) > ProcessIdsCacheLifetimeMs) + { + TrimProcessCache(nowTick); + Interlocked.Exchange(ref _lastProcessCacheTrimTick, nowTick); + } return processIds; } + + private void TrimProcessCache(long nowTick) + { + var staleKeys = _processIdsCache + .Where(entry => (nowTick - entry.Value.Tick) > ProcessIdsCacheLifetimeMs) + .Select(entry => entry.Key) + .ToList(); + foreach (var key in staleKeys) + _processIdsCache.TryRemove(key, out _); + + int overflow = _processIdsCache.Count - ProcessIdsCacheMaxEntries; + if (overflow <= 0) return; + + var oldestKeys = _processIdsCache + .OrderBy(entry => entry.Value.Tick) + .Take(overflow) + .Select(entry => entry.Key) + .ToList(); + foreach (var key in oldestKeys) + _processIdsCache.TryRemove(key, out _); + } } } From f4b3d985e723e6cb4f2c5afb1d766a5524f220cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:12:27 +0000 Subject: [PATCH 4/7] Merge main and optimize output-device audio path Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> --- ControlPad/EventHandler.cs | 2 +- ControlPad/System/AudioController.cs | 43 +++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/ControlPad/EventHandler.cs b/ControlPad/EventHandler.cs index bea3292..9a2752a 100644 --- a/ControlPad/EventHandler.cs +++ b/ControlPad/EventHandler.cs @@ -67,7 +67,7 @@ private void SliderEvent(CustomSlider slider, int value) else if (stream.MicName != null) Task.Run(() => AudioController.MuteMic(stream.MicName, false)); else if (stream.Process == null && stream.MicName == null) - Task.Run(() => AudioController.MuteSystem(false)); + Task.Run(() => AudioController.MuteSystem(false, stream.DeviceName)); } if (stream.Process != null) diff --git a/ControlPad/System/AudioController.cs b/ControlPad/System/AudioController.cs index 8205c07..24c3088 100644 --- a/ControlPad/System/AudioController.cs +++ b/ControlPad/System/AudioController.cs @@ -15,8 +15,10 @@ public class AudioController { private MMDeviceEnumerator _enum; private readonly ConcurrentDictionary _processIdsCache = new(); + private readonly ConcurrentDictionary _outputDeviceIdCache = new(); private const int ProcessIdsCacheLifetimeMs = 500; private const int ProcessIdsCacheMaxEntries = 128; + private const int OutputDeviceCacheLifetimeMs = 3000; private long _lastProcessCacheTrimTick = Environment.TickCount64; public AudioController() @@ -89,7 +91,13 @@ public void MuteProcess(string processName, bool mute) public void MuteSystem(bool mute) { - using var device = _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + using var device = GetOutputDevice(null); + if (device != null) device.AudioEndpointVolume.Mute = mute; + } + + public void MuteSystem(bool mute, string? outputDeviceName) + { + using var device = GetOutputDevice(outputDeviceName); if (device != null) device.AudioEndpointVolume.Mute = mute; } @@ -123,7 +131,14 @@ public bool IsProcessMute(string processName) public bool IsSystemMute() { - using var device = _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + using var device = GetOutputDevice(null); + if (device != null) return device.AudioEndpointVolume.Mute; + return false; + } + + public bool IsSystemMute(string? outputDeviceName) + { + using var device = GetOutputDevice(outputDeviceName); if (device != null) return device.AudioEndpointVolume.Mute; return false; } @@ -222,8 +237,28 @@ public List GetOutputDevices() if (string.IsNullOrWhiteSpace(outputDeviceName)) return _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - return GetOutputDevices().FirstOrDefault(d => d.DeviceFriendlyName == outputDeviceName) - ?? _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); + long nowTick = Environment.TickCount64; + if (_outputDeviceIdCache.TryGetValue(outputDeviceName, out var cacheEntry) && + (nowTick - cacheEntry.Tick) <= OutputDeviceCacheLifetimeMs) + { + try + { + return _enum.GetDevice(cacheEntry.DeviceId); + } + catch + { + _outputDeviceIdCache.TryRemove(outputDeviceName, out _); + } + } + + var device = GetOutputDevices().FirstOrDefault(d => d.DeviceFriendlyName == outputDeviceName); + if (device != null) + { + _outputDeviceIdCache[outputDeviceName] = (nowTick, device.ID); + return device; + } + + return _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); } } } From 43f289cdebc8c9c0fb79442a21bff78c9664b776 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:15:08 +0000 Subject: [PATCH 5/7] Address review feedback for lock-free throttle and cache trim Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> --- ControlPad/Arduino/ArduinoController.cs | 15 ++++---------- ControlPad/System/AudioController.cs | 27 +++++++++++++++++-------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/ControlPad/Arduino/ArduinoController.cs b/ControlPad/Arduino/ArduinoController.cs index be640bf..6c7f582 100644 --- a/ControlPad/Arduino/ArduinoController.cs +++ b/ControlPad/Arduino/ArduinoController.cs @@ -26,7 +26,6 @@ public static class ArduinoController private static BadgeType _lastBadgeType = BadgeType.None; private static long _lastEventDispatchTick; private const int EventDispatchIntervalMs = 16; - private static readonly object _eventDispatchLock = new(); public static void Initialize(MainWindow mainWindow, EventHandler eventHandler) { @@ -205,16 +204,10 @@ private static void ProcessLine(string line) UpdateValues(inputs[2..]); - bool shouldDispatch = false; - lock (_eventDispatchLock) - { - long nowTick = Environment.TickCount64; - if ((nowTick - _lastEventDispatchTick) >= EventDispatchIntervalMs) - { - _lastEventDispatchTick = nowTick; - shouldDispatch = true; - } - } + long nowTick = Environment.TickCount64; + long lastTick = Interlocked.Read(ref _lastEventDispatchTick); + bool shouldDispatch = (nowTick - lastTick) >= EventDispatchIntervalMs && + Interlocked.CompareExchange(ref _lastEventDispatchTick, nowTick, lastTick) == lastTick; if (!shouldDispatch) { diff --git a/ControlPad/System/AudioController.cs b/ControlPad/System/AudioController.cs index 24c3088..c05d155 100644 --- a/ControlPad/System/AudioController.cs +++ b/ControlPad/System/AudioController.cs @@ -17,6 +17,7 @@ public class AudioController private readonly ConcurrentDictionary _processIdsCache = new(); private readonly ConcurrentDictionary _outputDeviceIdCache = new(); private const int ProcessIdsCacheLifetimeMs = 500; + private const int ProcessIdsCacheTrimIntervalMs = 5000; private const int ProcessIdsCacheMaxEntries = 128; private const int OutputDeviceCacheLifetimeMs = 3000; private long _lastProcessCacheTrimTick = Environment.TickCount64; @@ -198,27 +199,37 @@ private int[] GetProcessIds(string processName) _processIdsCache[processName] = (nowTick, processIds); long lastTrimTick = Interlocked.Read(ref _lastProcessCacheTrimTick); if (_processIdsCache.Count >= ProcessIdsCacheMaxEntries || - (nowTick - lastTrimTick) > ProcessIdsCacheLifetimeMs) + (nowTick - lastTrimTick) > ProcessIdsCacheTrimIntervalMs) { - TrimProcessCache(nowTick); - Interlocked.Exchange(ref _lastProcessCacheTrimTick, nowTick); + if (Interlocked.CompareExchange(ref _lastProcessCacheTrimTick, nowTick, lastTrimTick) == lastTrimTick) + { + TrimProcessCache(nowTick); + } } return processIds; } private void TrimProcessCache(long nowTick) { - var staleKeys = _processIdsCache - .Where(entry => (nowTick - entry.Value.Tick) > ProcessIdsCacheLifetimeMs) - .Select(entry => entry.Key) - .ToList(); + var snapshot = _processIdsCache.ToArray(); + var staleKeys = new List(); + foreach (var entry in snapshot) + { + if ((nowTick - entry.Value.Tick) > ProcessIdsCacheLifetimeMs) + staleKeys.Add(entry.Key); + } + foreach (var key in staleKeys) _processIdsCache.TryRemove(key, out _); int overflow = _processIdsCache.Count - ProcessIdsCacheMaxEntries; if (overflow <= 0) return; - var oldestKeys = _processIdsCache + var staleKeySet = staleKeys.Count > 0 ? new HashSet(staleKeys) : null; + var candidates = staleKeySet == null + ? snapshot.AsEnumerable() + : snapshot.Where(entry => !staleKeySet.Contains(entry.Key)); + var oldestKeys = candidates .OrderBy(entry => entry.Value.Tick) .Take(overflow) .Select(entry => entry.Key) From ae1410fef50faf6f100137cdabb1d122317aab0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 16:17:36 +0000 Subject: [PATCH 6/7] Finalize AudioController cache and output-device handling polish Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> --- ControlPad/System/AudioController.cs | 43 ++++++++++++++++++---------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/ControlPad/System/AudioController.cs b/ControlPad/System/AudioController.cs index c05d155..3ac8b11 100644 --- a/ControlPad/System/AudioController.cs +++ b/ControlPad/System/AudioController.cs @@ -211,25 +211,26 @@ private int[] GetProcessIds(string processName) private void TrimProcessCache(long nowTick) { - var snapshot = _processIdsCache.ToArray(); - var staleKeys = new List(); - foreach (var entry in snapshot) + var staleEntries = new List<(string Key, long Tick)>(); + foreach (var entry in _processIdsCache) { if ((nowTick - entry.Value.Tick) > ProcessIdsCacheLifetimeMs) - staleKeys.Add(entry.Key); + staleEntries.Add((entry.Key, entry.Value.Tick)); } - foreach (var key in staleKeys) - _processIdsCache.TryRemove(key, out _); + foreach (var entry in staleEntries) + { + _processIdsCache.TryRemove(entry.Key, out _); + } int overflow = _processIdsCache.Count - ProcessIdsCacheMaxEntries; if (overflow <= 0) return; - var staleKeySet = staleKeys.Count > 0 ? new HashSet(staleKeys) : null; - var candidates = staleKeySet == null - ? snapshot.AsEnumerable() - : snapshot.Where(entry => !staleKeySet.Contains(entry.Key)); - var oldestKeys = candidates + var staleKeySet = staleEntries.Count > 0 + ? new HashSet(staleEntries.Select(e => e.Key)) + : null; + var oldestKeys = _processIdsCache + .Where(entry => staleKeySet == null || !staleKeySet.Contains(entry.Key)) .OrderBy(entry => entry.Value.Tick) .Take(overflow) .Select(entry => entry.Key) @@ -262,11 +263,23 @@ public List GetOutputDevices() } } - var device = GetOutputDevices().FirstOrDefault(d => d.DeviceFriendlyName == outputDeviceName); - if (device != null) + MMDevice? matchedDevice = null; + foreach (var device in _enum.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active)) + { + if (matchedDevice == null && device.DeviceFriendlyName == outputDeviceName) + { + matchedDevice = device; + } + else + { + device.Dispose(); + } + } + + if (matchedDevice != null) { - _outputDeviceIdCache[outputDeviceName] = (nowTick, device.ID); - return device; + _outputDeviceIdCache[outputDeviceName] = (nowTick, matchedDevice.ID); + return matchedDevice; } return _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); From d9c6ac585ae69ea528ad96f6fcec118addcdfa12 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 20 Mar 2026 17:13:24 +0000 Subject: [PATCH 7/7] Optimize mic/output selector popups to avoid MMDevice object churn Co-authored-by: PalmarHealer <93807726+PalmarHealer@users.noreply.github.com> Agent-Logs-Url: https://github.com/ControlPad/App/sessions/5fd76ff0-85b8-4691-b076-feb6828edd52 --- ControlPad/System/AudioController.cs | 23 +++++++++++++++++ .../Windows/EditButtonCategoryWindow.xaml.cs | 6 ++--- .../Windows/EditSliderCategoryWindow.xaml.cs | 4 +-- .../Windows/Popups/SelectMicPopup.xaml.cs | 25 ++++--------------- .../Popups/SelectOutputDevicePopup.xaml.cs | 12 ++++----- 5 files changed, 38 insertions(+), 32 deletions(-) diff --git a/ControlPad/System/AudioController.cs b/ControlPad/System/AudioController.cs index 3ac8b11..4d7447a 100644 --- a/ControlPad/System/AudioController.cs +++ b/ControlPad/System/AudioController.cs @@ -171,6 +171,18 @@ public List GetMics() return mics; } + public List GetMicNames() + { + var names = new List(); + using var enumerator = new MMDeviceEnumerator(); + foreach (var device in enumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active)) + { + names.Add(device.DeviceFriendlyName); + device.Dispose(); + } + return names; + } + public SessionCollection GetAudioSessions() { using var device = _enum.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); @@ -244,6 +256,17 @@ public List GetOutputDevices() return _enum.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active).ToList(); } + public List GetOutputDeviceNames() + { + var names = new List(); + foreach (var device in _enum.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active)) + { + names.Add(device.DeviceFriendlyName); + device.Dispose(); + } + return names; + } + private MMDevice? GetOutputDevice(string? outputDeviceName) { if (string.IsNullOrWhiteSpace(outputDeviceName)) diff --git a/ControlPad/Windows/EditButtonCategoryWindow.xaml.cs b/ControlPad/Windows/EditButtonCategoryWindow.xaml.cs index 0f8c57d..f10e385 100644 --- a/ControlPad/Windows/EditButtonCategoryWindow.xaml.cs +++ b/ControlPad/Windows/EditButtonCategoryWindow.xaml.cs @@ -125,9 +125,9 @@ private void btn_settings_Click(object sender, EventArgs e) if (micDialog.ShowDialog() == true) { - control.ButtonAction.ActionProperty = micDialog.SelectedMic?.DeviceFriendlyName; - control.ButtonAction.ActionPropertyDisplay = micDialog.SelectedMic?.DeviceFriendlyName; - control.TextBlock.Text = $"{control.ButtonAction.ActionType.Description}: {micDialog.SelectedMic?.DeviceFriendlyName}"; + control.ButtonAction.ActionProperty = micDialog.SelectedMicName; + control.ButtonAction.ActionPropertyDisplay = micDialog.SelectedMicName; + control.TextBlock.Text = $"{control.ButtonAction.ActionType.Description}: {micDialog.SelectedMicName}"; } break; } diff --git a/ControlPad/Windows/EditSliderCategoryWindow.xaml.cs b/ControlPad/Windows/EditSliderCategoryWindow.xaml.cs index c012a58..9cf8cd4 100644 --- a/ControlPad/Windows/EditSliderCategoryWindow.xaml.cs +++ b/ControlPad/Windows/EditSliderCategoryWindow.xaml.cs @@ -49,7 +49,7 @@ private void btn_AddMic_Click(object sender, RoutedEventArgs e) if (dialog.ShowDialog() == true) { - DataHandler.SliderCategories[indexOfCategory].AudioStreams.Add(new AudioStream(null, dialog.SelectedMic?.DeviceFriendlyName)); + DataHandler.SliderCategories[indexOfCategory].AudioStreams.Add(new AudioStream(null, dialog.SelectedMicName)); } } @@ -74,7 +74,7 @@ private void btn_AddMainOutput_Click(object sender, RoutedEventArgs e) if (dialog.ShowDialog() == true) { DataHandler.SliderCategories[indexOfCategory].AudioStreams.Add( - new AudioStream(null, null, dialog.SelectedOutputDevice?.DeviceFriendlyName) + new AudioStream(null, null, dialog.SelectedOutputDeviceName) ); } } diff --git a/ControlPad/Windows/Popups/SelectMicPopup.xaml.cs b/ControlPad/Windows/Popups/SelectMicPopup.xaml.cs index bad3cd3..2bc78bb 100644 --- a/ControlPad/Windows/Popups/SelectMicPopup.xaml.cs +++ b/ControlPad/Windows/Popups/SelectMicPopup.xaml.cs @@ -1,18 +1,4 @@ -using NAudio.CoreAudioApi; -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Shapes; +using System.Windows; using Wpf.Ui.Controls; namespace ControlPad @@ -20,19 +6,18 @@ namespace ControlPad public partial class SelectMicPopup : FluentWindow { AudioController audioController = new AudioController(); - public MMDevice? SelectedMic { get; set; } + public string? SelectedMicName { get; set; } public SelectMicPopup() { InitializeComponent(); - cb_Mics.DisplayMemberPath = "DeviceFriendlyName"; - cb_Mics.ItemsSource = audioController.GetMics(); + cb_Mics.ItemsSource = audioController.GetMicNames(); } private void btn_Ok_Click(object sender, RoutedEventArgs e) { - if (cb_Mics.SelectedItem is not MMDevice device) return; + if (cb_Mics.SelectedItem is not string micName) return; - SelectedMic = device; + SelectedMicName = micName; DialogResult = true; } diff --git a/ControlPad/Windows/Popups/SelectOutputDevicePopup.xaml.cs b/ControlPad/Windows/Popups/SelectOutputDevicePopup.xaml.cs index 47063d3..564b1cb 100644 --- a/ControlPad/Windows/Popups/SelectOutputDevicePopup.xaml.cs +++ b/ControlPad/Windows/Popups/SelectOutputDevicePopup.xaml.cs @@ -1,25 +1,23 @@ -using NAudio.CoreAudioApi; -using System.Windows; +using System.Windows; using Wpf.Ui.Controls; namespace ControlPad { public partial class SelectOutputDevicePopup : FluentWindow { - public MMDevice? SelectedOutputDevice { get; set; } + public string? SelectedOutputDeviceName { get; set; } public SelectOutputDevicePopup() { InitializeComponent(); - cb_OutputDevices.DisplayMemberPath = "DeviceFriendlyName"; - cb_OutputDevices.ItemsSource = new AudioController().GetOutputDevices(); + cb_OutputDevices.ItemsSource = new AudioController().GetOutputDeviceNames(); } private void btn_Ok_Click(object sender, RoutedEventArgs e) { - if (cb_OutputDevices.SelectedItem is not MMDevice device) return; + if (cb_OutputDevices.SelectedItem is not string outputName) return; - SelectedOutputDevice = device; + SelectedOutputDeviceName = outputName; DialogResult = true; }