using System.Runtime.InteropServices; namespace MusicBridge.Agent; // Obtain the current default output for each operation, including after device changes. internal static class SystemVolume { public static void Set(float level) => WithEndpoint(endpoint => { if (!float.IsFinite(level) || level < 0 || level > 1) throw new ArgumentOutOfRangeException(nameof(level)); var context = Guid.Empty; endpoint.SetMasterVolumeLevelScalar(level, ref context); return true; }); public static (float Level, bool Muted) Read() => WithEndpoint(endpoint => { endpoint.GetMasterVolumeLevelScalar(out var level); endpoint.GetMute(out var muted); return (level, muted); }); public static void Change(float delta) => WithEndpoint(endpoint => { endpoint.GetMasterVolumeLevelScalar(out var level); var context = Guid.Empty; endpoint.SetMasterVolumeLevelScalar(Math.Clamp(level + delta, 0f, 1f), ref context); return true; }); public static void ToggleMute() => WithEndpoint(endpoint => { endpoint.GetMute(out var muted); var context = Guid.Empty; endpoint.SetMute(!muted, ref context); return true; }); private static T WithEndpoint(Func action) { var enumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator(); IMMDevice? device = null; object? volume = null; try { enumerator.GetDefaultAudioEndpoint(0, 1, out device); // Render, Multimedia var iid = typeof(IAudioEndpointVolume).GUID; device.Activate(ref iid, 23, IntPtr.Zero, out volume); return action((IAudioEndpointVolume)volume); } finally { if (volume is not null) Marshal.ReleaseComObject(volume); if (device is not null) Marshal.ReleaseComObject(device); Marshal.ReleaseComObject(enumerator); } } [ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")] private class MMDeviceEnumerator { } // COM declarations must preserve the native vtable order, including unused methods. // HRESULT failures are translated to exceptions by the COM interop runtime. [ComImport, Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IMMDeviceEnumerator { void EnumAudioEndpoints(int flow, uint mask, out IntPtr devices); void GetDefaultAudioEndpoint(int flow, int role, out IMMDevice device); } [ComImport, Guid("D666063F-1587-4E43-81F1-B948E807363F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IMMDevice { void Activate(ref Guid iid, uint context, IntPtr activation, [MarshalAs(UnmanagedType.IUnknown)] out object instance); } [ComImport, Guid("5CDF2C82-841E-4546-9722-0CF74078229A"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] private interface IAudioEndpointVolume { void RegisterControlChangeNotify(IntPtr callback); void UnregisterControlChangeNotify(IntPtr callback); void GetChannelCount(out uint count); void SetMasterVolumeLevel(float level, ref Guid context); void SetMasterVolumeLevelScalar(float level, ref Guid context); void GetMasterVolumeLevel(out float level); void GetMasterVolumeLevelScalar(out float level); void SetChannelVolumeLevel(uint channel, float level, ref Guid context); void SetChannelVolumeLevelScalar(uint channel, float level, ref Guid context); void GetChannelVolumeLevel(uint channel, out float level); void GetChannelVolumeLevelScalar(uint channel, out float level); void SetMute([MarshalAs(UnmanagedType.Bool)] bool muted, ref Guid context); void GetMute([MarshalAs(UnmanagedType.Bool)] out bool muted); } }