From 84d2435b833c2124878344ab80e4555a956abe46 Mon Sep 17 00:00:00 2001 From: Chris Kaczor Date: Tue, 18 Aug 2026 12:18:31 -0400 Subject: [PATCH] More modernization --- .gitea/workflows/main.yml | 36 +++++++++++++ .github/workflows/main.yml | 36 ------------- AudioDevice.cs | 104 +++++++++++++++++++++++++++++++++++++ AudioWatcher.cs | 104 ++++++++++++++++++++++++++++++++++--- LightController.cs | 1 + Program.cs | 5 +- WorkIndicator.csproj | 4 +- WorkIndicator.sln | 4 +- 8 files changed, 246 insertions(+), 48 deletions(-) create mode 100644 .gitea/workflows/main.yml delete mode 100644 .github/workflows/main.yml create mode 100644 AudioDevice.cs diff --git a/.gitea/workflows/main.yml b/.gitea/workflows/main.yml new file mode 100644 index 0000000..13fb341 --- /dev/null +++ b/.gitea/workflows/main.yml @@ -0,0 +1,36 @@ +name: Deploy to Gitea Releases + +on: + push: + branches: + - main + + workflow_dispatch: + +jobs: + deploy-to-gitea-releases: + runs-on: ubuntu-latest + + container: + image: ghcr.io/catthehacker/ubuntu:dotnet-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Get next version + uses: reecetech/version-increment@2024.4.3 + id: version + with: + scheme: calver + + - name: Publish Application + run: dotnet publish WorkIndicator.csproj -r win-x86 -c Release -o publish + + - name: Create Velopack Release + run: | + export PATH="$PATH:/root/.dotnet/tools" + dotnet tool install -g vpk + vpk [win] download gitea --channel win-x86 --repoUrl https://gitea.kaczorzoo.net/ckaczor/WorkIndicator + vpk [win] pack --channel win-x64 -u WorkIndicator -v ${{ steps.version.outputs.version }} -p publish --packTitle "Work Indicator" --shortcuts StartMenuRoot --framework net10.0-x64-desktop + vpk [win] upload gitea --channel win-x64 --repoUrl https://gitea.kaczorzoo.net/ckaczor/WorkIndicator --publish --releaseName "${{ steps.version.outputs.version }}" --token ${{ secrets.VPK_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 299c44b..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Deploy to GitHub Releases - -on: - push: - branches: - - main - - workflow_dispatch: - -jobs: - deploy-to-github-releases: - runs-on: windows-latest - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Get next version - uses: reecetech/version-increment@2024.4.3 - id: version - with: - scheme: calver - - - name: Install .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 10.0.x - - - name: Publish Application - run: dotnet publish WorkIndicator.csproj -c Release -o publish - - - name: Create Velopack Release - run: | - dotnet tool install -g vpk - vpk download github --repoUrl https://github.com/ckaczor/WorkIndicator - vpk pack -u WorkIndicator -v ${{ steps.version.outputs.version }} -p publish --packTitle "Work Indicator" --shortcuts StartMenuRoot --framework net10.0-x64-desktop - vpk upload github --repoUrl https://github.com/ckaczor/WorkIndicator --publish --releaseName "${{ steps.version.outputs.version }}" --tag v${{ steps.version.outputs.version }} --token ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/AudioDevice.cs b/AudioDevice.cs new file mode 100644 index 0000000..6c857c4 --- /dev/null +++ b/AudioDevice.cs @@ -0,0 +1,104 @@ +using NAudio.CoreAudioApi; +using NAudio.CoreAudioApi.Interfaces; +using Serilog; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Windows.Threading; + +namespace WorkIndicator; + +internal class AudioDevice : IAudioSessionEventsHandler +{ + private readonly ILogger _logger; + private readonly MMDevice _device; + private readonly Dispatcher _dispatcher; + private readonly Dictionary _sessions = new(); + + public delegate void AudioSessionsChangedDelegate(); + + public event AudioSessionsChangedDelegate AudioSessionsChanged; + + public AudioDevice(MMDevice device) + { + _logger = Log.ForContext(); + + _dispatcher = Dispatcher.CurrentDispatcher; + + _device = device; + + _device.AudioSessionManager.AudioSessionControl.RegisterEventClient(this); + _device.AudioSessionManager.OnSessionCreated += AudioSessionManager_OnSessionCreated; + + _device.AudioSessionManager.RefreshSessions(); + var sessions = _device.AudioSessionManager.Sessions; + + for (var i = 0; i < sessions.Count; i++) + { + SetupSession(sessions[i]); + } + } + + private void AudioSessionManager_OnSessionCreated(object sender, IAudioSessionControl newSession) + { + var session = new AudioSessionControl(newSession); + + _logger.Information("OnSessionCreated: {name}", session.GetSessionIdentifier); + + SetupSession(session); + } + + private void SetupSession(AudioSessionControl session) + { + _logger.Information("SetupSession: {name}", session.GetSessionIdentifier); + + _sessions[session.GetSessionIdentifier] = session; + session.RegisterEventClient(this); + + AudioSessionsChanged?.Invoke(); + } + + public int ActiveSessionCount => _sessions.Values.Count(s => s.State == AudioSessionState.AudioSessionStateActive); + + void IAudioSessionEventsHandler.OnVolumeChanged(float volume, bool isMuted) + { + _dispatcher.Invoke(() => { Debug.WriteLine($"{_device.DeviceFriendlyName}: {volume} {isMuted} {_device.AudioEndpointVolume.Mute}"); }); + } + + void IAudioSessionEventsHandler.OnDisplayNameChanged(string displayName) + { + _logger.Information("OnDisplayNameChanged: {name}", displayName); + } + + void IAudioSessionEventsHandler.OnIconPathChanged(string iconPath) + { + _logger.Information("OnIconPathChanged: {path}", iconPath); + } + + void IAudioSessionEventsHandler.OnChannelVolumeChanged(uint channelCount, IntPtr newVolumes, uint channelIndex) + { + _logger.Information("OnChannelVolumeChanged: {channelCount} {newVolumes} {channelIndex}", channelCount, newVolumes, channelIndex); + } + + void IAudioSessionEventsHandler.OnGroupingParamChanged(ref Guid groupingId) + { + _logger.Information("OnGroupingParamChanged: {groupingId}", groupingId); + } + + void IAudioSessionEventsHandler.OnStateChanged(AudioSessionState state) + { + _logger.Information("OnStateChanged: {state}", state); + + AudioSessionsChanged?.Invoke(); + + if (state == AudioSessionState.AudioSessionStateExpired) + { + } + } + + void IAudioSessionEventsHandler.OnSessionDisconnected(AudioSessionDisconnectReason disconnectReason) + { + _logger.Information("OnSessionDisconnected: {disconnectReason}", disconnectReason); + } +} \ No newline at end of file diff --git a/AudioWatcher.cs b/AudioWatcher.cs index 447716f..68eaac7 100644 --- a/AudioWatcher.cs +++ b/AudioWatcher.cs @@ -1,21 +1,111 @@ -namespace WorkIndicator; +using NAudio.CoreAudioApi; +using NAudio.CoreAudioApi.Interfaces; +using System.Collections.Generic; +using System.Linq; +using Serilog; -internal static class AudioWatcher +namespace WorkIndicator; + +internal class AudioWatcher : IMMNotificationClient { + private readonly ILogger _logger = Log.ForContext(); + private readonly MMDeviceEnumerator _deviceEnumerator = new(); + private readonly Dictionary _inputDevices = new(); + public delegate void MicrophoneInUseChangedDelegate(bool microphoneInUse); - public static event MicrophoneInUseChangedDelegate MicrophoneInUseChanged; + public event MicrophoneInUseChangedDelegate MicrophoneInUseChanged; - public static void Start() + public void Start() { + _logger.Information("Start"); + + _deviceEnumerator.RegisterEndpointNotificationCallback(this); + + _logger.Information("Getting capture devices"); + + var captureDevices = _deviceEnumerator.EnumerateAudioEndPoints(DataFlow.Capture, DeviceState.Active); + + foreach (var captureDevice in captureDevices) + { + _logger.Information("Capture device: {name}", captureDevice.DeviceFriendlyName); + + HandleAddedDevice(captureDevice); + } } - public static void Stop() + private void HandleAddedDevice(MMDevice captureDevice) { + _logger.Information("Device added: {name}", captureDevice.DeviceFriendlyName); + + var audioDevice = new AudioDevice(captureDevice); + audioDevice.AudioSessionsChanged += AudioDevice_AudioSessionsChanged; + + _inputDevices[captureDevice.ID] = audioDevice; + + MicrophoneInUseChanged?.Invoke(MicrophoneInUse()); } - public static bool MicrophoneInUse() + private void AudioDevice_AudioSessionsChanged() { - return false; + MicrophoneInUseChanged?.Invoke(MicrophoneInUse()); + } + + private void HandleRemovedDevice(string deviceId) + { + _logger.Information("Device removed: {id}", deviceId); + + _inputDevices.Remove(deviceId); + + MicrophoneInUseChanged?.Invoke(MicrophoneInUse()); + } + + public void Stop() + { + _logger.Information("Stop"); + + _deviceEnumerator.UnregisterEndpointNotificationCallback(this); + _deviceEnumerator.Dispose(); + } + + public bool MicrophoneInUse() + { + var totalActiveSessionCount = _inputDevices.Sum(i => i.Value.ActiveSessionCount); + + return totalActiveSessionCount > 0; + } + + void IMMNotificationClient.OnDeviceStateChanged(string deviceId, DeviceState newState) + { + _logger.Information("OnDeviceStateChanged: {deviceId} {newState}", deviceId, newState); + } + + void IMMNotificationClient.OnDeviceAdded(string deviceId) + { + _logger.Information("OnDeviceAdded: {deviceId}", deviceId); + + var device = _deviceEnumerator.GetDevice(deviceId); + + if (device.DataFlow is DataFlow.Capture or DataFlow.All) + { + HandleAddedDevice(device); + } + } + + void IMMNotificationClient.OnDeviceRemoved(string deviceId) + { + _logger.Information("OnDeviceRemoved: {deviceId}", deviceId); + + HandleRemovedDevice(deviceId); + } + + void IMMNotificationClient.OnDefaultDeviceChanged(DataFlow flow, Role role, string defaultDeviceId) + { + _logger.Information("OnDefaultDeviceChanged: {defaultDeviceId} {flow} {role}", defaultDeviceId, flow, role); + } + + void IMMNotificationClient.OnPropertyValueChanged(string pwstrDeviceId, PropertyKey key) + { + _logger.Debug("OnPropertyValueChanged: {deviceId} {propertyId}", pwstrDeviceId, key.propertyId); } } \ No newline at end of file diff --git a/LightController.cs b/LightController.cs index cab8a3f..d3ff445 100644 --- a/LightController.cs +++ b/LightController.cs @@ -14,6 +14,7 @@ namespace WorkIndicator public static class LightController { + private static readonly AudioWatcher AudioWatcher = new(); private static StoplightIndicator _stoplightIndicator; private static UsbService _usbService; private static bool _initialized; diff --git a/Program.cs b/Program.cs index 12ea478..613fd34 100644 --- a/Program.cs +++ b/Program.cs @@ -10,7 +10,10 @@ internal static class Program [STAThread] public static void Main(string[] args) { - Log.Logger = new LoggerConfiguration().WriteTo.File("log.txt").CreateLogger(); + Log.Logger = new LoggerConfiguration() + .WriteTo.File("log.txt") + .WriteTo.Debug(outputTemplate: "{Timestamp:u} [{Level}] ({SourceContext}) {Message}{NewLine}{Exception}") + .CreateLogger(); Log.Logger.Information("Start"); diff --git a/WorkIndicator.csproj b/WorkIndicator.csproj index e02e3b9..90e9865 100644 --- a/WorkIndicator.csproj +++ b/WorkIndicator.csproj @@ -8,7 +8,7 @@ WorkIndicator.Program Resources\Application.ico true - AnyCPU;x86 + x86 @@ -22,9 +22,11 @@ + + diff --git a/WorkIndicator.sln b/WorkIndicator.sln index 523c4fe..4eb6772 100644 --- a/WorkIndicator.sln +++ b/WorkIndicator.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.9.12023.133 insiders +VisualStudioVersion = 18.9.12023.133 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WorkIndicator", "WorkIndicator.csproj", "{B4A9FB98-C873-487F-AC4B-9B4356491DE3}" EndProject @@ -14,11 +14,9 @@ Global EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B4A9FB98-C873-487F-AC4B-9B4356491DE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B4A9FB98-C873-487F-AC4B-9B4356491DE3}.Debug|Any CPU.Build.0 = Debug|Any CPU {B4A9FB98-C873-487F-AC4B-9B4356491DE3}.Debug|x86.ActiveCfg = Debug|x86 {B4A9FB98-C873-487F-AC4B-9B4356491DE3}.Debug|x86.Build.0 = Debug|x86 {B4A9FB98-C873-487F-AC4B-9B4356491DE3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B4A9FB98-C873-487F-AC4B-9B4356491DE3}.Release|Any CPU.Build.0 = Release|Any CPU {B4A9FB98-C873-487F-AC4B-9B4356491DE3}.Release|x86.ActiveCfg = Release|x86 {B4A9FB98-C873-487F-AC4B-9B4356491DE3}.Release|x86.Build.0 = Release|x86 EndGlobalSection