Updated
This commit is contained in:
@@ -15,6 +15,16 @@ class Program
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var devicesTest = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output);
|
||||||
|
var micrphonesTest = await volumeMixer.GetMicrophonesAsync();
|
||||||
|
Console.WriteLine($"Found {devicesTest.Count} playback devices");
|
||||||
|
Console.WriteLine($"Found {micrphonesTest.Count} microphones");
|
||||||
|
await Task.Delay(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Get all audio PLAYBACK devices
|
// Get all audio PLAYBACK devices
|
||||||
var devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output);
|
var devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output);
|
||||||
Console.WriteLine($"Found {devices.Count} playback devices:");
|
Console.WriteLine($"Found {devices.Count} playback devices:");
|
||||||
|
|||||||
@@ -18,9 +18,9 @@
|
|||||||
<PackageTags>EonaCat, Audio, Volume, Mixer .NET Standard, Jeroen, Saey</PackageTags>
|
<PackageTags>EonaCat, Audio, Volume, Mixer .NET Standard, Jeroen, Saey</PackageTags>
|
||||||
<PackageReleaseNotes></PackageReleaseNotes>
|
<PackageReleaseNotes></PackageReleaseNotes>
|
||||||
<Description>EonaCat VolumeMixer</Description>
|
<Description>EonaCat VolumeMixer</Description>
|
||||||
<Version>1.0.5</Version>
|
<Version>1.0.6</Version>
|
||||||
<AssemblyVersion>1.0.0.5</AssemblyVersion>
|
<AssemblyVersion>1.0.0.6</AssemblyVersion>
|
||||||
<FileVersion>1.0.0.5</FileVersion>
|
<FileVersion>1.0.0.6</FileVersion>
|
||||||
<PackageIcon>icon.png</PackageIcon>
|
<PackageIcon>icon.png</PackageIcon>
|
||||||
<RepositoryUrl>https://git.saey.me/EonaCat/EonaCat.VolumeMixer</RepositoryUrl>
|
<RepositoryUrl>https://git.saey.me/EonaCat/EonaCat.VolumeMixer</RepositoryUrl>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
|
|||||||
@@ -31,12 +31,32 @@ namespace EonaCat.VolumeMixer.Helpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ReleaseComObject(object obj)
|
public static void ReleaseComObject(object comObj)
|
||||||
{
|
{
|
||||||
if (obj != null && Marshal.IsComObject(obj))
|
if (comObj == null)
|
||||||
{
|
{
|
||||||
Marshal.ReleaseComObject(obj);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (Marshal.ReleaseComObject(comObj) > 0)
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Marshal.FinalReleaseComObject(comObj);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using EonaCat.VolumeMixer.Models;
|
using EonaCat.VolumeMixer.Helpers;
|
||||||
|
using EonaCat.VolumeMixer.Models;
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
@@ -13,8 +14,8 @@ namespace EonaCat.VolumeMixer.Interfaces
|
|||||||
{
|
{
|
||||||
int GetCount(out uint cProps);
|
int GetCount(out uint cProps);
|
||||||
int GetAt(uint iProp, out PropertyKey pkey);
|
int GetAt(uint iProp, out PropertyKey pkey);
|
||||||
int GetValue(ref PropertyKey key, out PropVariant pv);
|
int GetValue(ref PropertyKey key, out PROPVARIANT pv);
|
||||||
int SetValue(ref PropertyKey key, ref PropVariant propvar);
|
int SetValue(ref PropertyKey key, ref PROPVARIANT propvar);
|
||||||
int Commit();
|
int Commit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,17 +8,16 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace EonaCat.VolumeMixer.Managers
|
namespace EonaCat.VolumeMixer.Managers
|
||||||
{
|
{
|
||||||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
|
||||||
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
|
||||||
public class VolumeMixerManager : IDisposable
|
public class VolumeMixerManager : IDisposable
|
||||||
{
|
{
|
||||||
private const int VT_UI4 = 0x13;
|
private IMultiMediaDeviceEnumerator _deviceEnumerator;
|
||||||
private readonly IMultiMediaDeviceEnumerator _deviceEnumerator;
|
private bool _isDisposed;
|
||||||
private bool _isDisposed = false;
|
|
||||||
private readonly object _syncLock = new();
|
private readonly object _syncLock = new();
|
||||||
|
public event EventHandler<Exception> OnException;
|
||||||
|
|
||||||
private static readonly PropertyKey PKEY_Device_FriendlyName = new PropertyKey(new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"), 14);
|
private static PropertyKey PKEY_Device_FriendlyName = new(new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"), 14);
|
||||||
private static readonly PropertyKey PKEY_AudioEndpoint_FormFactor = new PropertyKey
|
|
||||||
|
private static PropertyKey PKEY_AudioEndpoint_FormFactor = new PropertyKey
|
||||||
{
|
{
|
||||||
Fmtid = new Guid("1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E"),
|
Fmtid = new Guid("1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E"),
|
||||||
Pid = 0
|
Pid = 0
|
||||||
@@ -32,7 +31,8 @@ namespace EonaCat.VolumeMixer.Managers
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Failed to initialize audio device enumerator. Make sure you're running on Windows Vista or later.", ex);
|
throw new InvalidOperationException(
|
||||||
|
"Failed to initialize audio device enumerator. Make sure you're running on Windows Vista or later.", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,76 +41,87 @@ namespace EonaCat.VolumeMixer.Managers
|
|||||||
return await Task.Run(() =>
|
return await Task.Run(() =>
|
||||||
{
|
{
|
||||||
var devices = new List<AudioDevice>();
|
var devices = new List<AudioDevice>();
|
||||||
|
if (_isDisposed || _deviceEnumerator == null) return devices;
|
||||||
if (_deviceEnumerator == null)
|
|
||||||
{
|
|
||||||
return devices;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
|
IMultiMediaDeviceCollection collection = null;
|
||||||
|
IMultiMediaDevice defaultDevice = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out var deviceCollection);
|
int hr = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out collection);
|
||||||
if (result != 0 || deviceCollection == null)
|
if (hr != 0 || collection == null) return devices;
|
||||||
{
|
|
||||||
return devices;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = deviceCollection.GetCount(out var count);
|
hr = collection.GetCount(out var count);
|
||||||
if (result != 0)
|
if (hr != 0) return devices;
|
||||||
{
|
|
||||||
ComHelper.ReleaseComObject(deviceCollection);
|
|
||||||
return devices;
|
|
||||||
}
|
|
||||||
|
|
||||||
string defaultId = "";
|
// Get default device
|
||||||
|
string defaultId = string.Empty;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out var defaultDevice);
|
hr = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out defaultDevice);
|
||||||
if (result == 0 && defaultDevice != null)
|
if (hr == 0 && defaultDevice != null)
|
||||||
{
|
{
|
||||||
defaultDevice.GetId(out defaultId);
|
defaultDevice.GetId(out defaultId);
|
||||||
ComHelper.ReleaseComObject(defaultDevice);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
defaultId = "";
|
OnException?.Invoke(this, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Enumerate devices
|
||||||
for (uint i = 0; i < count; i++)
|
for (uint i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
|
IMultiMediaDevice device = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = deviceCollection.Item(i, out var device);
|
if (collection.Item(i, out device) != 0 || device == null) continue;
|
||||||
if (result == 0 && device != null)
|
|
||||||
{
|
if (device.GetId(out var id) != 0 || string.IsNullOrEmpty(id)) continue;
|
||||||
result = device.GetId(out var id);
|
|
||||||
if (result == 0 && !string.IsNullOrEmpty(id))
|
string name = GetDeviceName(device);
|
||||||
{
|
DeviceType type = GetDeviceType(device);
|
||||||
var name = GetDeviceName(device);
|
|
||||||
var type = GetDeviceType(device);
|
|
||||||
bool isDefault = id == defaultId;
|
bool isDefault = id == defaultId;
|
||||||
|
|
||||||
|
// Increase ref count before passing to AudioDevice
|
||||||
|
Marshal.AddRef(Marshal.GetIUnknownForObject(device));
|
||||||
|
|
||||||
devices.Add(new AudioDevice(device, id, name, isDefault, dataFlow, type));
|
devices.Add(new AudioDevice(device, id, name, isDefault, dataFlow, type));
|
||||||
|
device = null;
|
||||||
}
|
}
|
||||||
else
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ComHelper.ReleaseComObject(device);
|
OnException?.Invoke(this, ex);
|
||||||
}
|
}
|
||||||
}
|
finally
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
// Do nothing
|
if (device != null)
|
||||||
|
{
|
||||||
|
Marshal.ReleaseComObject(device);
|
||||||
|
device = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
OnException?.Invoke(this, ex);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (collection != null)
|
||||||
|
{
|
||||||
|
Marshal.ReleaseComObject(collection);
|
||||||
|
collection = null;
|
||||||
|
}
|
||||||
|
|
||||||
ComHelper.ReleaseComObject(deviceCollection);
|
if (defaultDevice != null)
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
{
|
||||||
// Do nothing
|
Marshal.ReleaseComObject(defaultDevice);
|
||||||
|
defaultDevice = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,36 +131,28 @@ namespace EonaCat.VolumeMixer.Managers
|
|||||||
|
|
||||||
public async Task<AudioDevice> GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output)
|
public async Task<AudioDevice> GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output)
|
||||||
{
|
{
|
||||||
|
if (_isDisposed || _deviceEnumerator == null) return null;
|
||||||
|
|
||||||
return await Task.Run(() =>
|
return await Task.Run(() =>
|
||||||
{
|
{
|
||||||
if (_deviceEnumerator == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
|
IMultiMediaDevice device = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out var device);
|
int hr = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out device);
|
||||||
if (result == 0 && device != null)
|
if (hr == 0 && device != null)
|
||||||
{
|
{
|
||||||
result = device.GetId(out var id);
|
if (device.GetId(out var id) == 0 && !string.IsNullOrEmpty(id))
|
||||||
if (result == 0 && !string.IsNullOrEmpty(id))
|
|
||||||
{
|
{
|
||||||
var name = GetDeviceName(device);
|
string name = GetDeviceName(device);
|
||||||
var type = GetDeviceType(device);
|
DeviceType type = GetDeviceType(device);
|
||||||
return new AudioDevice(device, id, name, true, dataFlow, type);
|
return new AudioDevice(device, id, name, true, dataFlow, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
ComHelper.ReleaseComObject(device);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex) { OnException?.Invoke(this, ex); }
|
||||||
{
|
finally { SafeRelease(ref device); }
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -157,26 +160,24 @@ namespace EonaCat.VolumeMixer.Managers
|
|||||||
|
|
||||||
private string GetDeviceName(IMultiMediaDevice device)
|
private string GetDeviceName(IMultiMediaDevice device)
|
||||||
{
|
{
|
||||||
|
IPropertyStore store = null;
|
||||||
|
PROPVARIANT prop = default;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = device.OpenPropertyStore(0, out var propertyStore);
|
if (device.OpenPropertyStore(0, out store) != 0 || store == null) return "Unknown Device";
|
||||||
if (result == 0 && propertyStore != null)
|
if (store.GetValue(ref PKEY_Device_FriendlyName, out prop) == 0 &&
|
||||||
|
prop.vt == VarEnumConstants.VT_LPWSTR && prop.pointerValue != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
var propertyKey = PKEY_Device_FriendlyName;
|
string name = Marshal.PtrToStringUni(prop.pointerValue);
|
||||||
result = propertyStore.GetValue(ref propertyKey, out var propVariant);
|
return string.IsNullOrEmpty(name) ? "Unknown Device" : name;
|
||||||
if (result == 0 && propVariant.data1 != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
string name = Marshal.PtrToStringUni(propVariant.data1);
|
|
||||||
ComHelper.ReleaseComObject(propertyStore);
|
|
||||||
return !string.IsNullOrEmpty(name) ? name : "Unknown Device";
|
|
||||||
}
|
|
||||||
|
|
||||||
ComHelper.ReleaseComObject(propertyStore);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex) { OnException?.Invoke(this, ex); }
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
// Do nothing
|
try { PropVariantHelper.PropVariantClear(ref prop); } catch { }
|
||||||
|
SafeRelease(ref store);
|
||||||
}
|
}
|
||||||
|
|
||||||
return "Unknown Device";
|
return "Unknown Device";
|
||||||
@@ -184,63 +185,43 @@ namespace EonaCat.VolumeMixer.Managers
|
|||||||
|
|
||||||
private DeviceType GetDeviceType(IMultiMediaDevice device)
|
private DeviceType GetDeviceType(IMultiMediaDevice device)
|
||||||
{
|
{
|
||||||
|
IPropertyStore store = null;
|
||||||
|
PROPVARIANT prop = default;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int result = device.OpenPropertyStore(0, out var propertyStore);
|
if (device.OpenPropertyStore(0, out store) != 0 || store == null) return DeviceType.Unknown;
|
||||||
if (result == 0 && propertyStore != null)
|
if (store.GetValue(ref PKEY_AudioEndpoint_FormFactor, out prop) == 0 && prop.vt == VarEnumConstants.VT_UI4)
|
||||||
{
|
{
|
||||||
try
|
return prop.uintValue switch
|
||||||
{
|
{
|
||||||
var propertyKey = PKEY_AudioEndpoint_FormFactor;
|
|
||||||
result = propertyStore.GetValue(ref propertyKey, out var propVariant);
|
|
||||||
|
|
||||||
// 0x13 == VT_UI4
|
|
||||||
if (result == 0 && propVariant.vt == 0x13)
|
|
||||||
{
|
|
||||||
int formFactor = propVariant.data1.ToInt32();
|
|
||||||
|
|
||||||
return formFactor switch
|
|
||||||
{
|
|
||||||
0 => DeviceType.Unknown,
|
|
||||||
1 => DeviceType.Speakers,
|
1 => DeviceType.Speakers,
|
||||||
2 => DeviceType.LineLevel,
|
2 => DeviceType.LineLevel,
|
||||||
3 => DeviceType.Headphones,
|
3 => DeviceType.Headphones,
|
||||||
4 => DeviceType.Microphone,
|
4 => DeviceType.Microphone,
|
||||||
5 => DeviceType.Headset,
|
5 => DeviceType.Headset,
|
||||||
6 => DeviceType.Handset,
|
6 => DeviceType.Handset,
|
||||||
7 => DeviceType.UnknownDigitalPassthrough,
|
|
||||||
8 => DeviceType.SPDIF,
|
8 => DeviceType.SPDIF,
|
||||||
9 => DeviceType.DigitalAudioDisplayDevice,
|
9 => DeviceType.DigitalAudioDisplayDevice,
|
||||||
10 => DeviceType.UnknownFormFactor,
|
|
||||||
11 => DeviceType.FMRadio,
|
|
||||||
12 => DeviceType.VideoPhone,
|
|
||||||
13 => DeviceType.RCA,
|
|
||||||
14 => DeviceType.Bluetooth,
|
14 => DeviceType.Bluetooth,
|
||||||
15 => DeviceType.SPDIFOut,
|
15 => DeviceType.SPDIFOut,
|
||||||
16 => DeviceType.HDMI,
|
16 => DeviceType.HDMI,
|
||||||
17 => DeviceType.DisplayAudio,
|
17 => DeviceType.DisplayAudio,
|
||||||
18 => DeviceType.UnknownFormFactor2,
|
|
||||||
19 => DeviceType.Other,
|
19 => DeviceType.Other,
|
||||||
_ => DeviceType.Unknown,
|
_ => DeviceType.Unknown
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex) { OnException?.Invoke(this, ex); }
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ComHelper.ReleaseComObject(propertyStore);
|
try { PropVariantHelper.PropVariantClear(ref prop); } catch { }
|
||||||
}
|
SafeRelease(ref store);
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Do nothing
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return DeviceType.Unknown;
|
return DeviceType.Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> SetSystemVolumeAsync(float volume)
|
public async Task<bool> SetSystemVolumeAsync(float volume)
|
||||||
{
|
{
|
||||||
if (volume < 0f || volume > 1f)
|
if (volume < 0f || volume > 1f)
|
||||||
@@ -430,34 +411,6 @@ namespace EonaCat.VolumeMixer.Managers
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<AudioSession>> GetAllActiveSessionsAsync()
|
|
||||||
{
|
|
||||||
return await Task.Run(async () =>
|
|
||||||
{
|
|
||||||
var allSessions = new List<AudioSession>();
|
|
||||||
|
|
||||||
List<AudioDevice> devices = await GetAudioDevicesAsync(DataFlow.Output);
|
|
||||||
|
|
||||||
foreach (var device in devices)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
allSessions.AddRange(await device.GetAudioSessionsAsync());
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
device.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allSessions;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<List<AudioDevice>> GetMicrophonesAsync()
|
public async Task<List<AudioDevice>> GetMicrophonesAsync()
|
||||||
{
|
{
|
||||||
return await GetAudioDevicesAsync(DataFlow.Input);
|
return await GetAudioDevicesAsync(DataFlow.Input);
|
||||||
@@ -472,12 +425,19 @@ namespace EonaCat.VolumeMixer.Managers
|
|||||||
{
|
{
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
if (!_isDisposed)
|
if (_isDisposed) return;
|
||||||
{
|
|
||||||
ComHelper.ReleaseComObject(_deviceEnumerator);
|
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
SafeRelease(ref _deviceEnumerator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SafeRelease<T>(ref T comObj) where T : class
|
||||||
|
{
|
||||||
|
if (comObj != null)
|
||||||
|
{
|
||||||
|
try { Marshal.FinalReleaseComObject(comObj); } catch { }
|
||||||
|
comObj = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,267 +8,212 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace EonaCat.VolumeMixer.Models
|
namespace EonaCat.VolumeMixer.Models
|
||||||
{
|
{
|
||||||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
|
||||||
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
|
||||||
public class AudioDevice : IDisposable
|
public class AudioDevice : IDisposable
|
||||||
{
|
{
|
||||||
internal Guid AudioController2Guid = new Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d");
|
internal Guid AudioController2Guid = new("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d");
|
||||||
private readonly IMultiMediaDevice _device;
|
private IMultiMediaDevice _device;
|
||||||
private IAudioEndpointVolume _endpointVolume;
|
private IAudioEndpointVolume _endpointVolume;
|
||||||
private IAudioSessionManager _sessionManager;
|
private IAudioSessionManager _sessionManager;
|
||||||
private bool _isDisposed = false;
|
private bool _isDisposed;
|
||||||
private readonly object _syncLock = new object();
|
private readonly object _syncLock = new();
|
||||||
|
|
||||||
public string Id { get; private set; }
|
public string Id { get; }
|
||||||
public string Name { get; private set; }
|
public string Name { get; }
|
||||||
public DeviceType DeviceType { get; private set; }
|
public DeviceType DeviceType { get; }
|
||||||
public bool IsDefault { get; private set; }
|
public bool IsDefault { get; }
|
||||||
public DataFlow DataFlow { get; private set; }
|
public DataFlow DataFlow { get; }
|
||||||
|
|
||||||
|
public event EventHandler<Exception> OnException;
|
||||||
|
|
||||||
internal AudioDevice(IMultiMediaDevice device, string id, string name, bool isDefault, DataFlow dataFlow, DeviceType deviceType)
|
internal AudioDevice(IMultiMediaDevice device, string id, string name, bool isDefault, DataFlow dataFlow, DeviceType deviceType)
|
||||||
{
|
{
|
||||||
_device = device;
|
_device = device ?? throw new ArgumentNullException(nameof(device));
|
||||||
Id = id;
|
Id = id;
|
||||||
Name = name;
|
Name = name;
|
||||||
IsDefault = isDefault;
|
IsDefault = isDefault;
|
||||||
DataFlow = dataFlow;
|
DataFlow = dataFlow;
|
||||||
DeviceType = deviceType;
|
DeviceType = deviceType;
|
||||||
|
|
||||||
InitializeEndpointVolume();
|
InitializeEndpointVolume();
|
||||||
InitializeSessionManager();
|
InitializeSessionManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeEndpointVolume()
|
private void InitializeEndpointVolume()
|
||||||
{
|
{
|
||||||
|
IntPtr ptr = IntPtr.Zero;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var audioEndpointGuid = typeof(IAudioEndpointVolume).GUID;
|
var guid = typeof(IAudioEndpointVolume).GUID;
|
||||||
var result = _device.Activate(ref audioEndpointGuid, 0, IntPtr.Zero, out var ptr);
|
int hr = _device.Activate(ref guid, 0, IntPtr.Zero, out ptr);
|
||||||
if (result == 0 && ptr != IntPtr.Zero)
|
if (hr == 0 && ptr != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
_endpointVolume = ComHelper.GetInterface<IAudioEndpointVolume>(ptr);
|
_endpointVolume = (IAudioEndpointVolume)Marshal.GetObjectForIUnknown(ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Failed to initialize endpoint volume: {ex}");
|
Debug.WriteLine($"InitializeEndpointVolume failed: {ex}");
|
||||||
_endpointVolume = null;
|
_endpointVolume = null;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (ptr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.Release(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeSessionManager()
|
private void InitializeSessionManager()
|
||||||
{
|
{
|
||||||
|
IntPtr ptr = IntPtr.Zero;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var audioSessionGuid = typeof(IAudioSessionManager).GUID;
|
var guid = typeof(IAudioSessionManager).GUID;
|
||||||
var result = _device.Activate(ref audioSessionGuid, 0, IntPtr.Zero, out var ptr);
|
int hr = _device.Activate(ref guid, 0, IntPtr.Zero, out ptr);
|
||||||
if (result == 0 && ptr != IntPtr.Zero)
|
if (hr == 0 && ptr != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
_sessionManager = ComHelper.GetInterface<IAudioSessionManager>(ptr);
|
_sessionManager = (IAudioSessionManager)Marshal.GetObjectForIUnknown(ptr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Failed to initialize session manager: {ex}");
|
Debug.WriteLine($"InitializeSessionManager failed: {ex}");
|
||||||
_sessionManager = null;
|
_sessionManager = null;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (ptr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
Marshal.Release(ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<float> GetMasterVolumeAsync()
|
public Task<float> GetMasterVolumeAsync()
|
||||||
{
|
|
||||||
return await Task.Run(() =>
|
|
||||||
{
|
{
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
if (_isDisposed || _endpointVolume == null)
|
if (_isDisposed || _endpointVolume == null)
|
||||||
{
|
{
|
||||||
return 0f;
|
return Task.FromResult(0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = _endpointVolume.GetMasterVolumeLevelScalar(out var volume);
|
int hr = _endpointVolume.GetMasterVolumeLevelScalar(out float volume);
|
||||||
return result == 0 ? volume : 0f;
|
return Task.FromResult(hr == 0 ? volume : 0f);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch { return Task.FromResult(0f); }
|
||||||
{
|
|
||||||
Debug.WriteLine($"Error getting master volume: {ex}");
|
|
||||||
return 0f;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> SetMasterVolumeAsync(float volume, int maxRetries = 5, int delayMs = 20)
|
public async Task<bool> SetMasterVolumeAsync(float volume)
|
||||||
{
|
{
|
||||||
if (_isDisposed || _endpointVolume == null || volume < 0f || volume > 1f)
|
if (_isDisposed || _endpointVolume == null || volume < 0f || volume > 1f) return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var guid = Guid.Empty;
|
var guid = Guid.Empty;
|
||||||
|
|
||||||
for (int attempt = 0; attempt <= maxRetries; attempt++)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int result;
|
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
result = _endpointVolume.SetMasterVolumeLevelScalar(volume, ref guid);
|
return _endpointVolume.SetMasterVolumeLevelScalar(volume, ref guid) == 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == 0)
|
public Task<bool> GetMasterMuteAsync()
|
||||||
{
|
|
||||||
await Task.Delay(delayMs).ConfigureAwait(false);
|
|
||||||
var currentVolume = await GetMasterVolumeAsync().ConfigureAwait(false);
|
|
||||||
if (Math.Abs(currentVolume - volume) < 0.01f)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Debug.WriteLine($"Volume set failed on attempt {attempt + 1}: {ex}");
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.Delay(delayMs).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> GetMasterMuteAsync()
|
|
||||||
{
|
|
||||||
return await Task.Run(() =>
|
|
||||||
{
|
{
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
if (_isDisposed || _endpointVolume == null)
|
if (_isDisposed || _endpointVolume == null) return Task.FromResult(false);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = _endpointVolume.GetMute(out var mute);
|
int hr = _endpointVolume.GetMute(out bool mute);
|
||||||
return result == 0 && mute;
|
return Task.FromResult(hr == 0 && mute);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch { return Task.FromResult(false); }
|
||||||
{
|
|
||||||
Debug.WriteLine($"Error getting mute: {ex}");
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> SetMasterMuteAsync(bool mute)
|
public Task<bool> SetMasterMuteAsync(bool mute)
|
||||||
{
|
{
|
||||||
if (_isDisposed || _endpointVolume == null)
|
if (_isDisposed || _endpointVolume == null) return Task.FromResult(false);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var guid = Guid.Empty;
|
var guid = Guid.Empty;
|
||||||
return await Task.Run(() => _endpointVolume.SetMute(mute, ref guid) == 0).ConfigureAwait(false);
|
lock (_syncLock)
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Error setting mute: {ex}");
|
return Task.FromResult(_endpointVolume.SetMute(mute, ref guid) == 0);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<AudioSession>> GetAudioSessionsAsync()
|
public async Task<List<AudioSession>> GetAudioSessionsAsync()
|
||||||
{
|
{
|
||||||
return await Task.Run(() =>
|
var sessions = new List<AudioSession>();
|
||||||
{
|
if (_isDisposed || _sessionManager == null) return sessions;
|
||||||
|
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
var sessions = new List<AudioSession>();
|
try
|
||||||
|
|
||||||
if (_isDisposed || _sessionManager == null)
|
|
||||||
{
|
{
|
||||||
return sessions;
|
int hr = _sessionManager.GetSessionEnumerator(out var sessionEnum);
|
||||||
}
|
if (hr != 0 || sessionEnum == null) return sessions;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var result = _sessionManager.GetSessionEnumerator(out var sessionEnum);
|
hr = sessionEnum.GetCount(out int count);
|
||||||
if (result != 0 || sessionEnum == null)
|
if (hr != 0) return sessions;
|
||||||
{
|
|
||||||
return sessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = sessionEnum.GetCount(out var count);
|
|
||||||
if (result != 0)
|
|
||||||
{
|
|
||||||
return sessions;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
result = sessionEnum.GetSession(i, out var sessionControl);
|
hr = sessionEnum.GetSession(i, out var sessionControl);
|
||||||
if (result == 0 && sessionControl != null)
|
if (hr == 0 && sessionControl != null)
|
||||||
{
|
{
|
||||||
var sessionControl2 = GetSessionControl2(sessionControl);
|
var sessionControl2 = GetSessionControl2(sessionControl);
|
||||||
if (sessionControl2 != null)
|
if (sessionControl2 != null)
|
||||||
{
|
{
|
||||||
sessions.Add(new AudioSession(sessionControl2, _sessionManager));
|
sessions.Add(new AudioSession(sessionControl2, _sessionManager));
|
||||||
Marshal.ReleaseComObject(sessionControl2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release the original sessionControl COM object
|
|
||||||
Marshal.ReleaseComObject(sessionControl);
|
Marshal.ReleaseComObject(sessionControl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex) { OnException?.Invoke(this, ex); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
// Do nothing
|
Marshal.ReleaseComObject(sessionEnum);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
return sessions;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private IAudioSessionControlExtended GetSessionControl2(object sessionControl)
|
|
||||||
{
|
|
||||||
if (sessionControl == null)
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var unknownPtr = Marshal.GetIUnknownForObject(sessionControl);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IntPtr sessionControl2Ptr;
|
|
||||||
int result = Marshal.QueryInterface(unknownPtr, ref AudioController2Guid, out sessionControl2Ptr);
|
|
||||||
if (result == 0 && sessionControl2Ptr != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
return (IAudioSessionControlExtended)Marshal.GetObjectForIUnknown(sessionControl2Ptr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Debug.WriteLine($"Error querying sessionControl2: {ex}");
|
OnException?.Invoke(this, ex);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
Marshal.Release(unknownPtr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return sessions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IAudioSessionControlExtended GetSessionControl2(object sessionControl)
|
||||||
|
{
|
||||||
|
if (sessionControl == null) return null;
|
||||||
|
|
||||||
|
IntPtr unknownPtr = IntPtr.Zero;
|
||||||
|
IntPtr sessionControl2Ptr = IntPtr.Zero;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
unknownPtr = Marshal.GetIUnknownForObject(sessionControl);
|
||||||
|
int hr = Marshal.QueryInterface(unknownPtr, ref AudioController2Guid, out sessionControl2Ptr);
|
||||||
|
if (hr == 0 && sessionControl2Ptr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return (IAudioSessionControlExtended)Marshal.GetObjectForIUnknown(sessionControl2Ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) { Debug.WriteLine($"GetSessionControl2 failed: {ex}"); }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (sessionControl2Ptr != IntPtr.Zero) Marshal.Release(sessionControl2Ptr);
|
||||||
|
if (unknownPtr != IntPtr.Zero) Marshal.Release(unknownPtr);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,17 +273,21 @@ namespace EonaCat.VolumeMixer.Models
|
|||||||
{
|
{
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
if (_isDisposed)
|
if (_isDisposed) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ComHelper.ReleaseComObject(_endpointVolume);
|
|
||||||
ComHelper.ReleaseComObject(_sessionManager);
|
|
||||||
ComHelper.ReleaseComObject(_device);
|
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
SafeRelease(ref _endpointVolume);
|
||||||
|
SafeRelease(ref _sessionManager);
|
||||||
|
SafeRelease(ref _device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SafeRelease<T>(ref T comObj) where T : class
|
||||||
|
{
|
||||||
|
if (comObj != null)
|
||||||
|
{
|
||||||
|
try { Marshal.FinalReleaseComObject(comObj); } catch { }
|
||||||
|
comObj = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ namespace EonaCat.VolumeMixer.Models
|
|||||||
{
|
{
|
||||||
public class AudioSession : IDisposable
|
public class AudioSession : IDisposable
|
||||||
{
|
{
|
||||||
private readonly IAudioSessionControlExtended _sessionControl;
|
private IAudioSessionControlExtended _sessionControl;
|
||||||
private IAudioVolume _audioVolume;
|
private IAudioVolume _audioVolume;
|
||||||
private bool _isDisposed = false;
|
private bool _isDisposed;
|
||||||
private readonly object _syncLock = new();
|
private readonly object _syncLock = new();
|
||||||
|
|
||||||
public string DisplayName { get; private set; }
|
public string DisplayName { get; private set; }
|
||||||
@@ -19,7 +19,7 @@ namespace EonaCat.VolumeMixer.Models
|
|||||||
public uint ProcessId { get; private set; }
|
public uint ProcessId { get; private set; }
|
||||||
public AudioSessionState State { get; private set; }
|
public AudioSessionState State { get; private set; }
|
||||||
|
|
||||||
internal AudioSession(IAudioSessionControlExtended sessionControl, IAudioSessionManager sessionManager)
|
internal AudioSession(IAudioSessionControlExtended sessionControl, IAudioSessionManager manager)
|
||||||
{
|
{
|
||||||
_sessionControl = sessionControl ?? throw new ArgumentNullException(nameof(sessionControl));
|
_sessionControl = sessionControl ?? throw new ArgumentNullException(nameof(sessionControl));
|
||||||
InitializeSimpleAudioVolume();
|
InitializeSimpleAudioVolume();
|
||||||
@@ -30,23 +30,25 @@ namespace EonaCat.VolumeMixer.Models
|
|||||||
{
|
{
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
|
if (_sessionControl == null) return;
|
||||||
|
|
||||||
|
IntPtr unknownPtr = IntPtr.Zero;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var sessionControlUnknown = Marshal.GetIUnknownForObject(_sessionControl);
|
unknownPtr = Marshal.GetIUnknownForObject(_sessionControl);
|
||||||
var iidSimpleAudioVolume = typeof(IAudioVolume).GUID;
|
IntPtr volumePtr = IntPtr.Zero;
|
||||||
IntPtr simpleAudioVolumePtr = IntPtr.Zero;
|
|
||||||
|
|
||||||
int result = Marshal.QueryInterface(sessionControlUnknown, ref iidSimpleAudioVolume, out simpleAudioVolumePtr);
|
var iid = typeof(IAudioVolume).GUID;
|
||||||
if (result == 0 && simpleAudioVolumePtr != IntPtr.Zero)
|
int hr = Marshal.QueryInterface(unknownPtr, ref iid, out volumePtr);
|
||||||
|
if (hr == 0 && volumePtr != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
_audioVolume = (IAudioVolume)Marshal.GetObjectForIUnknown(simpleAudioVolumePtr);
|
_audioVolume = (IAudioVolume)Marshal.GetObjectForIUnknown(volumePtr);
|
||||||
Marshal.Release(simpleAudioVolumePtr);
|
|
||||||
}
|
}
|
||||||
Marshal.Release(sessionControlUnknown);
|
|
||||||
}
|
}
|
||||||
catch
|
catch { _audioVolume = null; }
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
_audioVolume = null;
|
if (unknownPtr != IntPtr.Zero) Marshal.Release(unknownPtr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,19 +255,20 @@ namespace EonaCat.VolumeMixer.Models
|
|||||||
{
|
{
|
||||||
lock (_syncLock)
|
lock (_syncLock)
|
||||||
{
|
{
|
||||||
if (_isDisposed)
|
if (_isDisposed) return;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_audioVolume != null)
|
|
||||||
{
|
|
||||||
Marshal.ReleaseComObject(_audioVolume);
|
|
||||||
_audioVolume = null;
|
|
||||||
}
|
|
||||||
ComHelper.ReleaseComObject(_sessionControl);
|
|
||||||
|
|
||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
|
||||||
|
SafeRelease(ref _audioVolume);
|
||||||
|
SafeRelease(ref _sessionControl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SafeRelease<T>(ref T comObj) where T : class
|
||||||
|
{
|
||||||
|
if (comObj != null)
|
||||||
|
{
|
||||||
|
try { Marshal.FinalReleaseComObject(comObj); } catch { }
|
||||||
|
comObj = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,46 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace EonaCat.VolumeMixer.Models
|
namespace EonaCat.VolumeMixer.Helpers
|
||||||
{
|
{
|
||||||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
||||||
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
internal static class PropVariantHelper
|
||||||
internal struct PropVariant
|
|
||||||
{
|
{
|
||||||
[FieldOffset(0)] public ushort vt;
|
/// <summary>
|
||||||
|
/// Calls the native PropVariantClear function in ole32.dll to free memory allocated inside a PROPVARIANT.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="pvar">Reference to the PROPVARIANT structure to clear.</param>
|
||||||
|
/// <returns>HRESULT — 0 (S_OK) if successful.</returns>
|
||||||
|
[DllImport("ole32.dll", PreserveSig = true)]
|
||||||
|
internal static extern int PropVariantClear(ref PROPVARIANT pvar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Managed definition of the native PROPVARIANT structure used by Windows COM APIs.
|
||||||
|
/// Only includes fields required for strings (VT_LPWSTR) and integers (VT_UI4).
|
||||||
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
|
internal struct PROPVARIANT
|
||||||
|
{
|
||||||
|
[FieldOffset(0)] public ushort vt; // Variant type
|
||||||
[FieldOffset(2)] public ushort wReserved1;
|
[FieldOffset(2)] public ushort wReserved1;
|
||||||
[FieldOffset(4)] public ushort wReserved2;
|
[FieldOffset(4)] public ushort wReserved2;
|
||||||
[FieldOffset(6)] public ushort wReserved3;
|
[FieldOffset(6)] public ushort wReserved3;
|
||||||
[FieldOffset(8)] public IntPtr data1;
|
|
||||||
[FieldOffset(16)] public IntPtr data2;
|
// Union data starts at offset 8
|
||||||
|
[FieldOffset(8)] public IntPtr pointerValue; // For VT_LPWSTR (LPWSTR)
|
||||||
|
[FieldOffset(8)] public uint uintValue; // For VT_UI4
|
||||||
|
[FieldOffset(8)] public int intValue; // For VT_I4
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// VARIANT type constants (from WinNT.h).
|
||||||
|
/// </summary>
|
||||||
|
internal static class VarEnumConstants
|
||||||
|
{
|
||||||
|
public const ushort VT_EMPTY = 0;
|
||||||
|
public const ushort VT_UI4 = 0x13; // Unsigned 4-byte integer
|
||||||
|
public const ushort VT_LPWSTR = 0x1F; // Wide string (LPWSTR)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user