5 Commits

10 changed files with 468 additions and 50 deletions

View File

@ -109,6 +109,13 @@ public sealed class AppxPackage
var properties = reader.GetProperties(); var properties = reader.GetProperties();
properties.GetStringValue("DisplayName", out var displayName); properties.GetStringValue("DisplayName", out var displayName);
if(displayName.StartsWith("ms-resource:"))
{
var packageFullName = Marshal.PtrToStringUni(info.packageFullName);
displayName = LoadResourceString(fullName, displayName);
}
package.DisplayName = displayName; package.DisplayName = displayName;
properties.GetStringValue("Logo", out var logo); properties.GetStringValue("Logo", out var logo);
@ -158,6 +165,40 @@ public sealed class AppxPackage
ClosePackageInfo(infoRef); ClosePackageInfo(infoRef);
} }
} }
}
static string LoadResourceString(string packageFullName, string resource)
{
if (packageFullName == null)
throw new ArgumentNullException("packageFullName");
if (string.IsNullOrWhiteSpace(resource))
return null;
const string resourceScheme = "ms-resource:";
if (!resource.StartsWith(resourceScheme))
return null;
string part = resource.Substring(resourceScheme.Length);
string url;
if (part.StartsWith("/"))
{
url = resourceScheme + "//" + part;
}
else
{
url = resourceScheme + "///resources/" + part;
}
string source = string.Format("@{{{0}? {1}}}", packageFullName, url);
var sb = new StringBuilder(1024);
int i = SHLoadIndirectString(source, sb, sb.Capacity, IntPtr.Zero);
if (i != 0)
return null;
return sb.ToString();
} }

View File

@ -3,6 +3,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
namespace FocusVolumeControl.AudioHelpers; namespace FocusVolumeControl.AudioHelpers;
@ -12,6 +13,7 @@ public class AudioHelper
static object _lock = new object(); static object _lock = new object();
int[] _currentProcesses; int[] _currentProcesses;
int _retryFallbackCount = 0;
public IAudioSession Current { get; private set; } public IAudioSession Current { get; private set; }
@ -37,6 +39,7 @@ public class AudioHelper
public IAudioSession FindSession(List<Process> processes) public IAudioSession FindSession(List<Process> processes)
{ {
//var blah = new AudioPolicyConfigFactoryImplFor21H2();
var results = new ActiveAudioSessionWrapper(); var results = new ActiveAudioSessionWrapper();
Process bestProcessMatch = null; Process bestProcessMatch = null;
@ -49,9 +52,10 @@ public class AudioHelper
deviceCollection.Item(d, out var device); deviceCollection.Item(d, out var device);
Guid iid = typeof(IAudioSessionManager2).GUID; Guid iid = typeof(IAudioSessionManager2).GUID;
device.Activate(ref iid, 0, IntPtr.Zero, out var m); device.Activate(ref iid, CLSCTX.ALL, IntPtr.Zero, out var m);
var manager = (IAudioSessionManager2)m; var manager = (IAudioSessionManager2)m;
device.GetId(out var currentDeviceId);
manager.GetSessionEnumerator(out var sessionEnumerator); manager.GetSessionEnumerator(out var sessionEnumerator);
@ -70,6 +74,16 @@ public class AudioHelper
continue; continue;
} }
/*
blah.GetPersistedDefaultAudioEndpoint(sessionProcessId, DataFlow.Render, Role.Multimedia, out var persistedDeviceId);
persistedDeviceId = UnpackDeviceId(persistedDeviceId);
if(!string.IsNullOrEmpty(persistedDeviceId) && persistedDeviceId != currentDeviceId)
{
continue;
}
*/
var index = processes.FindIndex(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName); var index = processes.FindIndex(x => x.Id == sessionProcessId || x.ProcessName == audioProcess?.ProcessName);
if (index > -1) if (index > -1)
@ -114,10 +128,18 @@ public class AudioHelper
var processes = GetPossibleProcesses(); var processes = GetPossibleProcesses();
var processIds = processes?.Select(x => x.Id).ToArray(); var processIds = processes?.Select(x => x.Id).ToArray();
if (_currentProcesses == null || !_currentProcesses.SequenceEqual(processIds)) //_currentProcesses null - first time getting sessions
//_currentProcesses not equal to processIds - changed the active process
//_retryFallbackCount - some processes like chrome or minecraft will start their audio process when they first try to do some sound stuff
if (_currentProcesses == null || !_currentProcesses.SequenceEqual(processIds) || _retryFallbackCount == 5)
{ {
_retryFallbackCount = 0;
Current = FindSession(processes); Current = FindSession(processes);
} }
else if(Current is SystemSoundsAudioSession || Current is SystemVolumeAudioSession)
{
_retryFallbackCount++;
}
if (Current == null) if (Current == null)
{ {
@ -214,7 +236,7 @@ public class AudioHelper
deviceCollection.Item(d, out var device); deviceCollection.Item(d, out var device);
Guid iid = typeof(IAudioSessionManager2).GUID; Guid iid = typeof(IAudioSessionManager2).GUID;
device.Activate(ref iid, 0, IntPtr.Zero, out var m); device.Activate(ref iid, CLSCTX.ALL, IntPtr.Zero, out var m);
var manager = (IAudioSessionManager2)m; var manager = (IAudioSessionManager2)m;
@ -245,7 +267,7 @@ public class AudioHelper
Guid iid = typeof(IAudioSessionManager2).GUID; Guid iid = typeof(IAudioSessionManager2).GUID;
device.Activate(ref iid, 0, IntPtr.Zero, out var m); device.Activate(ref iid, CLSCTX.ALL, IntPtr.Zero, out var m);
var manager = (IAudioSessionManager2)m; var manager = (IAudioSessionManager2)m;
@ -271,10 +293,130 @@ public class AudioHelper
deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia, out var device); deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia, out var device);
Guid iid = typeof(IAudioEndpointVolume).GUID; Guid iid = typeof(IAudioEndpointVolume).GUID;
device.Activate(ref iid, 0, IntPtr.Zero, out var o); device.Activate(ref iid, CLSCTX.ALL, IntPtr.Zero, out var o);
var endpointVolume = (IAudioEndpointVolume)o; var endpointVolume = (IAudioEndpointVolume)o;
return new SystemVolumeAudioSession(endpointVolume); return new SystemVolumeAudioSession(endpointVolume);
} }
class AudioPolicyConfigFactoryImplFor21H2
{
private readonly IAudioPolicyConfigFactoryVariantFor21H2 _factory;
internal AudioPolicyConfigFactoryImplFor21H2()
{
var iid = typeof(IAudioPolicyConfigFactoryVariantFor21H2).GUID;
Combase.RoGetActivationFactory("Windows.Media.Internal.AudioPolicyConfig", ref iid, out object factory);
_factory = (IAudioPolicyConfigFactoryVariantFor21H2)factory;
}
public uint ClearAllPersistedApplicationDefaultEndpoints()
{
return _factory.ClearAllPersistedApplicationDefaultEndpoints();
}
public uint GetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, out string deviceId)
{
return _factory.GetPersistedDefaultAudioEndpoint(processId, flow, role, out deviceId);
}
public uint SetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, IntPtr deviceId)
{
return _factory.SetPersistedDefaultAudioEndpoint(processId, flow, role, deviceId);
}
}
[Guid("ab3d4648-e242-459f-b02f-541c70306324")]
[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface IAudioPolicyConfigFactoryVariantFor21H2
{
int __incomplete__add_CtxVolumeChange();
int __incomplete__remove_CtxVolumeChanged();
int __incomplete__add_RingerVibrateStateChanged();
int __incomplete__remove_RingerVibrateStateChange();
int __incomplete__SetVolumeGroupGainForId();
int __incomplete__GetVolumeGroupGainForId();
int __incomplete__GetActiveVolumeGroupForEndpointId();
int __incomplete__GetVolumeGroupsForEndpoint();
int __incomplete__GetCurrentVolumeContext();
int __incomplete__SetVolumeGroupMuteForId();
int __incomplete__GetVolumeGroupMuteForId();
int __incomplete__SetRingerVibrateState();
int __incomplete__GetRingerVibrateState();
int __incomplete__SetPreferredChatApplication();
int __incomplete__ResetPreferredChatApplication();
int __incomplete__GetPreferredChatApplication();
int __incomplete__GetCurrentChatApplications();
int __incomplete__add_ChatContextChanged();
int __incomplete__remove_ChatContextChanged();
[PreserveSig]
uint SetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, IntPtr deviceId);
[PreserveSig]
uint GetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, [Out, MarshalAs(UnmanagedType.HString)] out string deviceId);
[PreserveSig]
uint ClearAllPersistedApplicationDefaultEndpoints();
}
[Guid("2a59116d-6c4f-45e0-a74f-707e3fef9258")]
[InterfaceType(ComInterfaceType.InterfaceIsIInspectable)]
public interface IAudioPolicyConfigFactoryVariantForDownlevel
{
int __incomplete__add_CtxVolumeChange();
int __incomplete__remove_CtxVolumeChanged();
int __incomplete__add_RingerVibrateStateChanged();
int __incomplete__remove_RingerVibrateStateChange();
int __incomplete__SetVolumeGroupGainForId();
int __incomplete__GetVolumeGroupGainForId();
int __incomplete__GetActiveVolumeGroupForEndpointId();
int __incomplete__GetVolumeGroupsForEndpoint();
int __incomplete__GetCurrentVolumeContext();
int __incomplete__SetVolumeGroupMuteForId();
int __incomplete__GetVolumeGroupMuteForId();
int __incomplete__SetRingerVibrateState();
int __incomplete__GetRingerVibrateState();
int __incomplete__SetPreferredChatApplication();
int __incomplete__ResetPreferredChatApplication();
int __incomplete__GetPreferredChatApplication();
int __incomplete__GetCurrentChatApplications();
int __incomplete__add_ChatContextChanged();
int __incomplete__remove_ChatContextChanged();
[PreserveSig]
uint SetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, IntPtr deviceId);
[PreserveSig]
uint GetPersistedDefaultAudioEndpoint(int processId, DataFlow flow, Role role, [Out, MarshalAs(UnmanagedType.HString)] out string deviceId);
[PreserveSig]
uint ClearAllPersistedApplicationDefaultEndpoints();
}
static class Combase
{
[DllImport("combase.dll", PreserveSig = false)]
public static extern void RoGetActivationFactory(
[MarshalAs(UnmanagedType.HString)] string activatableClassId,
[In] ref Guid iid,
[Out, MarshalAs(UnmanagedType.IInspectable)] out Object factory);
[DllImport("combase.dll", PreserveSig = false)]
public static extern void WindowsCreateString(
[MarshalAs(UnmanagedType.LPWStr)] string src,
[In] uint length,
[Out] out IntPtr hstring);
}
private const string DEVINTERFACE_AUDIO_RENDER = "#{e6327cad-dcec-4949-ae8a-991e976a79d2}";
private const string DEVINTERFACE_AUDIO_CAPTURE = "#{2eef81be-33fa-4800-9670-1cd474972c3f}";
private const string MMDEVAPI_TOKEN = @"\\?\SWD#MMDEVAPI#";
private string UnpackDeviceId(string deviceId)
{
if (deviceId.StartsWith(MMDEVAPI_TOKEN)) deviceId = deviceId.Remove(0, MMDEVAPI_TOKEN.Length);
if (deviceId.EndsWith(DEVINTERFACE_AUDIO_RENDER)) deviceId = deviceId.Remove(deviceId.Length - DEVINTERFACE_AUDIO_RENDER.Length);
if (deviceId.EndsWith(DEVINTERFACE_AUDIO_CAPTURE)) deviceId = deviceId.Remove(deviceId.Length - DEVINTERFACE_AUDIO_CAPTURE.Length);
return deviceId;
}
} }

View File

@ -1,7 +1,10 @@
using BarRaider.SdTools; using BarRaider.SdTools;
using FocusVolumeControl.AudioSession;
using FocusVolumeControl.AudioSessions; using FocusVolumeControl.AudioSessions;
using FocusVolumeControl.UI;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Drawing;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -10,12 +13,12 @@ namespace FocusVolumeControl.AudioHelpers;
public class NameAndIconHelper public class NameAndIconHelper
{ {
public (string name, string icon) GetProcessInfo(Process process) public string GetProcessInfo(Process process)
{ {
//i know this is dumb, but its only used by the sound browser, not real prod code //i know this is dumb, but its only used by the sound browser, not real prod code
var blah = new ActiveAudioSessionWrapper(); var blah = new ActiveAudioSessionWrapper();
SetProcessInfo(process, blah); SetProcessInfo(process, blah);
return (blah.DisplayName, blah.IconPath ?? blah.ExecutablePath); return blah.DisplayName;
} }
public void SetProcessInfo(Process process, ActiveAudioSessionWrapper results) public void SetProcessInfo(Process process, ActiveAudioSessionWrapper results)
@ -45,12 +48,27 @@ public class NameAndIconHelper
} }
} }
results.ExecutablePath = fileVersionInfo?.FileName; //for java apps (minecraft), the process will just have a java icon
//and there's not just a file that you can get the real icon from
//so you have to send some messages to the apps to get the icons.
//but they will only be 32x32 (or smaller) so we only want to use this logic for java
//because these will be lower resolution than the normal way of getting icons
if (process.ProcessName == "javaw" || process.ProcessName == "java" || process.ProcessName == "dotnet")
{
var windowHandle = process.MainWindowHandle;
var lazyIcon = () => JavaIconExtractor.GetWindowBigIconWithRetry(windowHandle);
results.IconWrapper = new RawIcon(windowHandle.ToString(), lazyIcon);
}
else
{
results.IconWrapper = new NormalIcon(fileVersionInfo?.FileName);
}
} }
else else
{ {
results.DisplayName = appx.DisplayName; results.DisplayName = appx.DisplayName;
results.IconPath = Path.Combine(appx.Path, appx.Logo); results.IconWrapper = new AppxIcon(Path.Combine(appx.Path, appx.Logo));
} }
} }
catch { } catch { }

View File

@ -6,55 +6,27 @@ using System.Drawing;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using FocusVolumeControl.UI; using FocusVolumeControl.UI;
using BitFaster.Caching.Lru; using BitFaster.Caching.Lru;
using FocusVolumeControl.AudioSession;
namespace FocusVolumeControl.AudioSessions; namespace FocusVolumeControl.AudioSessions;
public sealed class ActiveAudioSessionWrapper : IAudioSession public sealed class ActiveAudioSessionWrapper : IAudioSession
{ {
static ConcurrentLru<string, string> _iconCache = new ConcurrentLru<string, string>(10);
public string DisplayName { get; set; } public string DisplayName { get; set; }
public string ExecutablePath { get; set; }
public string IconPath { get; set; }
private List<IAudioSessionControl2> Sessions { get; } = new List<IAudioSessionControl2>(); private List<IAudioSessionControl2> Sessions { get; } = new List<IAudioSessionControl2>();
private IEnumerable<ISimpleAudioVolume> Volume => Sessions.Cast<ISimpleAudioVolume>(); private IEnumerable<ISimpleAudioVolume> Volume => Sessions.Cast<ISimpleAudioVolume>();
string GetIconFromIconPath() public IconWrapper? IconWrapper { get; set; }
{
return _iconCache.GetOrAdd(IconPath, (key) =>
{
var tmp = (Bitmap)Bitmap.FromFile(IconPath);
tmp.MakeTransparent();
return Tools.ImageToBase64(tmp, true);
});
}
string GetIconFromExecutablePath()
{
return _iconCache.GetOrAdd(ExecutablePath, (key) =>
{
var tmp = IconExtraction.GetIcon(ExecutablePath);
//var tmp = Icon.ExtractAssociatedIcon(ExecutablePath);
return Tools.ImageToBase64(tmp, true);
});
}
public string GetIcon() public string GetIcon()
{ {
try try
{ {
if (!string.IsNullOrEmpty(IconPath)) return IconWrapper?.GetIconData() ?? IconWrapper.FallbackIconData;
{
return GetIconFromIconPath();
}
else
{
return GetIconFromExecutablePath();
}
} }
catch catch
{ {
return "Images/encoderIcon"; return IconWrapper.FallbackIconData;
} }
} }

View File

@ -11,6 +11,32 @@ public class MMDeviceEnumerator
{ {
} }
[Flags]
public enum CLSCTX : uint
{
INPROC_SERVER = 0x1,
INPROC_HANDLER = 0x2,
LOCAL_SERVER = 0x4,
INPROC_SERVER16 = 0x8,
REMOTE_SERVER = 0x10,
INPROC_HANDLER16 = 0x20,
RESERVED1 = 0x40,
RESERVED2 = 0x80,
RESERVED3 = 0x100,
RESERVED4 = 0x200,
NO_CODE_DOWNLOAD = 0x400,
RESERVED5 = 0x800,
NO_CUSTOM_MARSHAL = 0x1000,
ENABLE_CODE_DOWNLOAD = 0x2000,
NO_FAILURE_LOG = 0x4000,
DISABLE_AAA = 0x8000,
ENABLE_AAA = 0x10000,
FROM_DEFAULT_CONTEXT = 0x20000,
INPROC = INPROC_SERVER | INPROC_HANDLER,
SERVER = INPROC_SERVER | LOCAL_SERVER | REMOTE_SERVER,
ALL = SERVER | INPROC_HANDLER
}
public enum DataFlow public enum DataFlow
{ {
Render, Render,
@ -35,6 +61,13 @@ public enum DeviceState : uint
MaskAll = 0xFu MaskAll = 0xFu
} }
public enum AudioSessionState
{
AudioSessionStateInactive = 0,
AudioSessionStateActive = 1,
AudioSessionStateExpired = 2
}
[Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")] [Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
@ -62,7 +95,13 @@ public interface IMMDeviceEnumerator
public interface IMMDevice public interface IMMDevice
{ {
[PreserveSig] [PreserveSig]
int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface); int Activate(ref Guid iid, CLSCTX dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
[PreserveSig]
int NotImpl1();
[PreserveSig]
int GetId([Out, MarshalAs(UnmanagedType.LPWStr)] out string ppstrId);
} }
[Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("77AA99A0-1BD6-484F-8BC7-2C654C9A9B6F"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
@ -104,9 +143,9 @@ public interface ISimpleAudioVolume
[Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAudioSessionControl2 public interface IAudioSessionControl2
{ {
// IAudioSessionControl //elgato seems to use this to determine whether the icon should be black and white
[PreserveSig] [PreserveSig]
int NotImpl0(); int GetState(out AudioSessionState audioSessionState);
[PreserveSig] [PreserveSig]
int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
@ -134,7 +173,7 @@ public interface IAudioSessionControl2
// IAudioSessionControl2 // IAudioSessionControl2
[PreserveSig] [PreserveSig]
int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); uint GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
[PreserveSig] [PreserveSig]
int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal); int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);

View File

@ -0,0 +1,103 @@
using BarRaider.SdTools;
using BitFaster.Caching.Lru;
using FocusVolumeControl.UI;
using System;
using System.Drawing;
namespace FocusVolumeControl.AudioSession
{
public abstract class IconWrapper
{
protected static ConcurrentLru<string, string> _iconCache = new ConcurrentLru<string, string>(10);
public abstract string GetIconData();
internal const string FallbackIconData = "Images/encoderIcon";
}
internal class AppxIcon : IconWrapper
{
private readonly string _iconPath;
public AppxIcon(string iconPath)
{
_iconPath = iconPath;
}
public override string GetIconData()
{
if(string.IsNullOrEmpty(_iconPath))
{
return FallbackIconData;
}
return _iconCache.GetOrAdd(_iconPath, (key) =>
{
var tmp = (Bitmap)Bitmap.FromFile(_iconPath);
tmp.MakeTransparent();
return Tools.ImageToBase64(tmp, true);
});
}
}
internal class NormalIcon : IconWrapper
{
private readonly string _iconPath;
public NormalIcon(string iconPath)
{
_iconPath = iconPath;
}
public override string GetIconData()
{
if(string.IsNullOrEmpty(_iconPath))
{
return FallbackIconData;
}
return _iconCache.GetOrAdd(_iconPath, (key) =>
{
var tmp = IconExtraction.GetIcon(_iconPath);
return Tools.ImageToBase64(tmp, true);
});
}
}
internal class RawIcon : IconWrapper
{
private readonly string _data;
public RawIcon(string name, Func<Bitmap?> getIcon)
{
_data = _iconCache.GetOrAdd(name, (key) =>
{
var icon = getIcon();
if (icon == null)
{
return FallbackIconData;
}
if (icon.Height < 48 && icon.Width < 48)
{
using var newImage = new Bitmap(48, 48);
newImage.MakeTransparent();
using var graphics = Graphics.FromImage(newImage);
graphics.DrawImage(icon, 4, 4, 40, 40);
return Tools.ImageToBase64(newImage, true);
}
else
{
return Tools.ImageToBase64(icon, true);
}
});
}
public override string GetIconData() => _data;
}
}

View File

@ -55,6 +55,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="AudioHelpers\AppxPackage.cs" /> <Compile Include="AudioHelpers\AppxPackage.cs" />
<Compile Include="AudioSessions\IconWrapper.cs" />
<Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" /> <Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" />
<Compile Include="AudioHelpers\AudioHelper.cs" /> <Compile Include="AudioHelpers\AudioHelper.cs" />
<Compile Include="AudioSessions\CoreAudio.cs" /> <Compile Include="AudioSessions\CoreAudio.cs" />
@ -71,6 +72,7 @@
<Compile Include="AudioHelpers\ParentProcessUtilities.cs" /> <Compile Include="AudioHelpers\ParentProcessUtilities.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UI\JavaIconExtractor.cs" />
<Compile Include="UI\UIState.cs" /> <Compile Include="UI\UIState.cs" />
<Compile Include="UI\ValueWithOpacity.cs" /> <Compile Include="UI\ValueWithOpacity.cs" />
<Compile Include="WindowChangedEventLoop.cs" /> <Compile Include="WindowChangedEventLoop.cs" />

View File

@ -6,8 +6,10 @@ internal class Program
{ {
static void Main(string[] args) static void Main(string[] args)
{ {
#if DEBUG
// Uncomment this line of code to allow for debugging // Uncomment this line of code to allow for debugging
//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); } //while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); }
#endif
SDWrapper.Run(args); SDWrapper.Run(args);
} }

View File

@ -0,0 +1,96 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FocusVolumeControl.UI
{
internal class JavaIconExtractor
{
const int WM_GETICON = 0x7F;
const int WM_QUERYDRAGICON = 0x0037;
//const int ICON_SMALL = 0; //(16x16)
const int ICON_BIG = 1; //(32x32)
const int SMTO_ABORTIFHUNG = 0x3;
const int GCL_HICON = -14;
[DllImport("User32.dll")]
static extern int SendMessageTimeout(IntPtr hWnd, int uMsg, int wParam, int lParam, int fuFlags, int uTimeout, out int lpdwResult);
[DllImport("User32.dll")]
static extern int GetClassLong(IntPtr hWnd, int index);
[DllImport("user32.dll", EntryPoint = "DestroyIcon", SetLastError = true)]
public static extern int DestroyIcon(IntPtr hIcon);
public static Bitmap? GetWindowBigIconWithRetry(IntPtr hWnd)
{
var retry = 5;
var icon = GetWindowBigIcon(hWnd);
while(icon == null || retry > 0)
{
Thread.Sleep(100);
icon = GetWindowBigIcon(hWnd);
retry--;
}
return icon;
}
/// <summary>
/// Retrieves a big icon (32*32) of a window
/// </summary>
/// <param name="hWnd"></param>
/// <returns></returns>
public static Bitmap? GetWindowBigIcon(IntPtr hWnd)
{
IntPtr hIcon = IntPtr.Zero;
try
{
int result;
SendMessageTimeout(hWnd, WM_GETICON, ICON_BIG, //big icon size
0, SMTO_ABORTIFHUNG, 1000, out result);
hIcon = new IntPtr(result);
if (hIcon == IntPtr.Zero) //some applications don't respond to sendmessage, we have to use GetClassLong in that case
{
result = GetClassLong(hWnd, GCL_HICON); //big icon size
hIcon = new IntPtr(result);
}
if (hIcon == IntPtr.Zero)
{
SendMessageTimeout(hWnd, WM_QUERYDRAGICON, 0, 0, SMTO_ABORTIFHUNG, 1000, out result);
hIcon = new IntPtr(result);
}
if (hIcon == IntPtr.Zero)
{
return null;
}
else
{
using var tmp = (Icon)Icon.FromHandle(hIcon).Clone();
return tmp.ToBitmap();
}
}
catch (Exception)
{
}
finally
{
if(hIcon != IntPtr.Zero)
{
DestroyIcon(hIcon);
}
}
return null;
}
}
}

View File

@ -57,7 +57,7 @@ public partial class MainWindow : Window
foreach (var p in processes) foreach (var p in processes)
{ {
var (displayName, _) = (new NameAndIconHelper()).GetProcessInfo(p); var displayName = (new NameAndIconHelper()).GetProcessInfo(p);
sb.AppendLine($"pid: {p.Id}"); sb.AppendLine($"pid: {p.Id}");
sb.AppendLine($"\tprocessName: {p.ProcessName}"); sb.AppendLine($"\tprocessName: {p.ProcessName}");
@ -92,14 +92,16 @@ public partial class MainWindow : Window
deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection); deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection);
deviceCollection.GetCount(out var num); deviceCollection.GetCount(out var num);
for(int i = 0; i < num; i++)
for (int i = 0; i < num; i++)
{ {
deviceCollection.Item(i, out var device); deviceCollection.Item(i, out var device);
//todo: put the device name in the output //todo: put the device name in the output
sb.AppendLine("----"); sb.AppendLine($"----");
Guid iid = typeof(IAudioSessionManager2).GUID; Guid iid = typeof(IAudioSessionManager2).GUID;
device.Activate(ref iid, 0, IntPtr.Zero, out var m); device.Activate(ref iid, CLSCTX.ALL, IntPtr.Zero, out var m);
var manager = (IAudioSessionManager2)m; var manager = (IAudioSessionManager2)m;
@ -112,9 +114,10 @@ public partial class MainWindow : Window
sessionEnumerator.GetSession(s, out var session); sessionEnumerator.GetSession(s, out var session);
session.GetProcessId(out var processId); session.GetProcessId(out var processId);
session.GetIconPath(out var path);
var audioProcess = Process.GetProcessById(processId); var audioProcess = Process.GetProcessById(processId);
var (displayName, _) = (new NameAndIconHelper()).GetProcessInfo(audioProcess); var displayName = (new NameAndIconHelper()).GetProcessInfo(audioProcess);
sb.AppendLine($"pid: {audioProcess.Id}\t\t processName: {displayName}"); sb.AppendLine($"pid: {audioProcess.Id}\t\t processName: {displayName}");
} }