Compare commits
11 Commits
v1.2.0-pre
...
playWithAp
Author | SHA1 | Date | |
---|---|---|---|
f1d7aeed5d | |||
0f7f1fffcd | |||
aa905fe443 | |||
160bedd461 | |||
68d5154756 | |||
8eebf1af47 | |||
6de76da8ad | |||
48161b5c2e | |||
d1df235af0 | |||
6aaa32cf92 | |||
4ca0ad021f |
@ -109,6 +109,13 @@ public sealed class AppxPackage
|
||||
var properties = reader.GetProperties();
|
||||
|
||||
properties.GetStringValue("DisplayName", out var displayName);
|
||||
|
||||
if(displayName.StartsWith("ms-resource:"))
|
||||
{
|
||||
var packageFullName = Marshal.PtrToStringUni(info.packageFullName);
|
||||
displayName = LoadResourceString(fullName, displayName);
|
||||
|
||||
}
|
||||
package.DisplayName = displayName;
|
||||
|
||||
properties.GetStringValue("Logo", out var logo);
|
||||
@ -158,6 +165,40 @@ public sealed class AppxPackage
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,12 +1,9 @@
|
||||
using BarRaider.SdTools;
|
||||
using FocusVolumeControl.AudioSessions;
|
||||
using FocusVolumeControl.AudioSessions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace FocusVolumeControl.AudioHelpers;
|
||||
|
||||
@ -16,6 +13,7 @@ public class AudioHelper
|
||||
|
||||
static object _lock = new object();
|
||||
int[] _currentProcesses;
|
||||
int _retryFallbackCount = 0;
|
||||
|
||||
public IAudioSession Current { get; private set; }
|
||||
|
||||
@ -27,8 +25,21 @@ public class AudioHelper
|
||||
}
|
||||
}
|
||||
|
||||
private Process GetProcessById(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Process.GetProcessById(id);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public IAudioSession FindSession(List<Process> processes)
|
||||
{
|
||||
//var blah = new AudioPolicyConfigFactoryImplFor21H2();
|
||||
var results = new ActiveAudioSessionWrapper();
|
||||
Process bestProcessMatch = null;
|
||||
|
||||
@ -41,9 +52,10 @@ public class AudioHelper
|
||||
deviceCollection.Item(d, out var device);
|
||||
|
||||
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;
|
||||
|
||||
device.GetId(out var currentDeviceId);
|
||||
|
||||
manager.GetSessionEnumerator(out var sessionEnumerator);
|
||||
|
||||
@ -55,7 +67,22 @@ public class AudioHelper
|
||||
sessionEnumerator.GetSession(i, out var session);
|
||||
|
||||
session.GetProcessId(out var sessionProcessId);
|
||||
var audioProcess = Process.GetProcessById(sessionProcessId);
|
||||
var audioProcess = GetProcessById(sessionProcessId);
|
||||
|
||||
if(audioProcess == null)
|
||||
{
|
||||
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);
|
||||
|
||||
@ -68,7 +95,14 @@ public class AudioHelper
|
||||
{
|
||||
bestProcessMatch = audioProcess;
|
||||
currentIndex = index;
|
||||
|
||||
if(string.IsNullOrEmpty(results.DisplayName))
|
||||
{
|
||||
session.GetDisplayName(out var displayName);
|
||||
results.DisplayName = displayName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//some apps like discord have multiple volume processes.
|
||||
//and some apps will be on multiple devices
|
||||
@ -92,12 +126,20 @@ public class AudioHelper
|
||||
lock (_lock)
|
||||
{
|
||||
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);
|
||||
}
|
||||
else if(Current is SystemSoundsAudioSession || Current is SystemVolumeAudioSession)
|
||||
{
|
||||
_retryFallbackCount++;
|
||||
}
|
||||
|
||||
if (Current == null)
|
||||
{
|
||||
@ -138,7 +180,7 @@ public class AudioHelper
|
||||
|
||||
if (handle == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
return new List<Process>();
|
||||
}
|
||||
|
||||
var ids = Native.GetProcessesOfChildWindows(handle);
|
||||
@ -150,6 +192,11 @@ public class AudioHelper
|
||||
.Select(x => Process.GetProcessById(x))
|
||||
.ToList();
|
||||
|
||||
if(processes.FirstOrDefault()?.ProcessName == "explorer")
|
||||
{
|
||||
return new List<Process>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
//note. in instances where you launch a game from steam. this ends up mapping the process to both steam and to the game. which is unfortunate
|
||||
@ -189,7 +236,7 @@ public class AudioHelper
|
||||
deviceCollection.Item(d, out var device);
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@ -220,7 +267,7 @@ public class AudioHelper
|
||||
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@ -246,10 +293,130 @@ public class AudioHelper
|
||||
deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia, out var device);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
using BarRaider.SdTools;
|
||||
using FocusVolumeControl.AudioSession;
|
||||
using FocusVolumeControl.AudioSessions;
|
||||
using FocusVolumeControl.UI;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
@ -10,12 +13,12 @@ namespace FocusVolumeControl.AudioHelpers;
|
||||
|
||||
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
|
||||
var blah = new ActiveAudioSessionWrapper();
|
||||
SetProcessInfo(process, blah);
|
||||
return (blah.DisplayName, blah.IconPath ?? blah.ExecutablePath);
|
||||
return blah.DisplayName;
|
||||
}
|
||||
|
||||
public void SetProcessInfo(Process process, ActiveAudioSessionWrapper results)
|
||||
@ -26,10 +29,13 @@ public class NameAndIconHelper
|
||||
var appx = AppxPackage.FromProcess(process);
|
||||
if (appx == null)
|
||||
{
|
||||
//usingg process.MainModule.FileVersionInfo sometimes throws permission exceptions
|
||||
//using process.MainModule.FileVersionInfo sometimes throws permission exceptions
|
||||
//we get the file version info with a limited query flag to avoid that
|
||||
var fileVersionInfo = GetFileVersionInfo(process);
|
||||
|
||||
//if the display name is already set, then it came from the display name of the audio session
|
||||
if (string.IsNullOrEmpty(results.DisplayName))
|
||||
{
|
||||
results.DisplayName = process.MainWindowTitle;
|
||||
|
||||
if (string.IsNullOrEmpty(results.DisplayName))
|
||||
@ -40,13 +46,29 @@ public class NameAndIconHelper
|
||||
results.DisplayName = process.ProcessName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
results.DisplayName = appx.DisplayName;
|
||||
results.IconPath = Path.Combine(appx.Path, appx.Logo);
|
||||
results.IconWrapper = new AppxIcon(Path.Combine(appx.Path, appx.Logo));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
@ -4,44 +4,31 @@ using System.Linq;
|
||||
using BarRaider.SdTools;
|
||||
using System.Drawing;
|
||||
using System.Runtime.InteropServices;
|
||||
using FocusVolumeControl.UI;
|
||||
using BitFaster.Caching.Lru;
|
||||
using FocusVolumeControl.AudioSession;
|
||||
|
||||
namespace FocusVolumeControl.AudioSessions;
|
||||
|
||||
public sealed class ActiveAudioSessionWrapper : IAudioSession
|
||||
{
|
||||
public string DisplayName { get; set; }
|
||||
public string ExecutablePath { get; set; }
|
||||
public string IconPath { get; set; }
|
||||
private List<IAudioSessionControl2> Sessions { get; } = new List<IAudioSessionControl2>();
|
||||
private IEnumerable<ISimpleAudioVolume> Volume => Sessions.Cast<ISimpleAudioVolume>();
|
||||
|
||||
string _icon;
|
||||
public IconWrapper? IconWrapper { get; set; }
|
||||
|
||||
public string GetIcon()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_icon))
|
||||
{
|
||||
try
|
||||
{
|
||||
if(!string.IsNullOrEmpty(IconPath))
|
||||
{
|
||||
var tmp = (Bitmap)Bitmap.FromFile(IconPath);
|
||||
tmp.MakeTransparent();
|
||||
_icon = Tools.ImageToBase64(tmp, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
var tmp = Icon.ExtractAssociatedIcon(ExecutablePath);
|
||||
_icon = Tools.ImageToBase64(tmp.ToBitmap(), true);
|
||||
}
|
||||
return IconWrapper?.GetIconData() ?? IconWrapper.FallbackIconData;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_icon = "Images/encoderIcon";
|
||||
return IconWrapper.FallbackIconData;
|
||||
}
|
||||
}
|
||||
return _icon;
|
||||
}
|
||||
|
||||
public bool Any()
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
Render,
|
||||
@ -35,6 +61,13 @@ public enum DeviceState : uint
|
||||
MaskAll = 0xFu
|
||||
}
|
||||
|
||||
public enum AudioSessionState
|
||||
{
|
||||
AudioSessionStateInactive = 0,
|
||||
AudioSessionStateActive = 1,
|
||||
AudioSessionStateExpired = 2
|
||||
}
|
||||
|
||||
|
||||
[Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E")]
|
||||
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
@ -62,7 +95,13 @@ public interface IMMDeviceEnumerator
|
||||
public interface IMMDevice
|
||||
{
|
||||
[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)]
|
||||
@ -104,9 +143,9 @@ public interface ISimpleAudioVolume
|
||||
[Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IAudioSessionControl2
|
||||
{
|
||||
// IAudioSessionControl
|
||||
//elgato seems to use this to determine whether the icon should be black and white
|
||||
[PreserveSig]
|
||||
int NotImpl0();
|
||||
int GetState(out AudioSessionState audioSessionState);
|
||||
|
||||
[PreserveSig]
|
||||
int GetDisplayName([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||
@ -134,7 +173,7 @@ public interface IAudioSessionControl2
|
||||
|
||||
// IAudioSessionControl2
|
||||
[PreserveSig]
|
||||
int GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||
uint GetSessionIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||
|
||||
[PreserveSig]
|
||||
int GetSessionInstanceIdentifier([MarshalAs(UnmanagedType.LPWStr)] out string pRetVal);
|
||||
|
103
src/FocusVolumeControl/AudioSessions/IconWrapper.cs
Normal file
103
src/FocusVolumeControl/AudioSessions/IconWrapper.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -33,7 +33,7 @@ public class DialAction : EncoderBase
|
||||
}
|
||||
|
||||
PluginSettings settings;
|
||||
AudioHelper _audioHelper = new AudioHelper();
|
||||
static AudioHelper _audioHelper = new AudioHelper();
|
||||
UIState _previousState;
|
||||
|
||||
public DialAction(ISDConnection connection, InitialPayload payload) : base(connection, payload)
|
||||
|
@ -55,6 +55,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="AudioHelpers\AppxPackage.cs" />
|
||||
<Compile Include="AudioSessions\IconWrapper.cs" />
|
||||
<Compile Include="AudioSessions\ActiveAudioSessionWrapper.cs" />
|
||||
<Compile Include="AudioHelpers\AudioHelper.cs" />
|
||||
<Compile Include="AudioSessions\CoreAudio.cs" />
|
||||
@ -65,11 +66,13 @@
|
||||
<Compile Include="AudioSessions\IAudioSession.cs" />
|
||||
<Compile Include="FallbackBehavior.cs" />
|
||||
<Compile Include="AudioHelpers\NameAndIconHelper.cs" />
|
||||
<Compile Include="UI\IconExtraction.cs" />
|
||||
<Compile Include="UI\ISDConnectionExtensions.cs" />
|
||||
<Compile Include="Native.cs" />
|
||||
<Compile Include="AudioHelpers\ParentProcessUtilities.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="UI\JavaIconExtractor.cs" />
|
||||
<Compile Include="UI\UIState.cs" />
|
||||
<Compile Include="UI\ValueWithOpacity.cs" />
|
||||
<Compile Include="WindowChangedEventLoop.cs" />
|
||||
@ -96,6 +99,9 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BitFaster.Caching">
|
||||
<Version>2.2.1</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="IsExternalInit">
|
||||
<Version>1.0.3</Version>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
@ -6,8 +6,10 @@ internal class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
#if DEBUG
|
||||
// Uncomment this line of code to allow for debugging
|
||||
//while (!System.Diagnostics.Debugger.IsAttached) { System.Threading.Thread.Sleep(100); }
|
||||
#endif
|
||||
|
||||
SDWrapper.Run(args);
|
||||
}
|
||||
|
267
src/FocusVolumeControl/UI/IconExtraction.cs
Normal file
267
src/FocusVolumeControl/UI/IconExtraction.cs
Normal file
@ -0,0 +1,267 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace FocusVolumeControl.UI
|
||||
{
|
||||
internal class IconExtraction
|
||||
{
|
||||
public static Bitmap GetIcon(string path)
|
||||
{
|
||||
var index = GetIconIndex(path);
|
||||
var handle = GetIconHandle(index);
|
||||
|
||||
using var icon = (Icon)Icon.FromHandle(handle).Clone();
|
||||
|
||||
Shell32.DestroyIcon(handle);
|
||||
return icon.ToBitmap();
|
||||
}
|
||||
|
||||
|
||||
static int GetIconIndex(string pszFile)
|
||||
{
|
||||
SHFILEINFO sfi = new SHFILEINFO();
|
||||
Shell32.SHGetFileInfo(pszFile, 0, ref sfi, (uint)Marshal.SizeOf(sfi), (uint)(SHGFI.SysIconIndex | SHGFI.LargeIcon | SHGFI.UseFileAttributes));
|
||||
return sfi.iIcon;
|
||||
}
|
||||
|
||||
// 256*256
|
||||
static IntPtr GetIconHandle(int iImage)
|
||||
{
|
||||
IImageList spiml = null;
|
||||
Guid guil = new Guid(IID_IImageList2);//or IID_IImageList
|
||||
|
||||
Shell32.SHGetImageList(Shell32.SHIL_EXTRALARGE, ref guil, ref spiml);
|
||||
IntPtr hIcon = IntPtr.Zero;
|
||||
spiml.GetIcon(iImage, Shell32.ILD_TRANSPARENT | Shell32.ILD_IMAGE, ref hIcon);
|
||||
|
||||
return hIcon;
|
||||
}
|
||||
|
||||
const string IID_IImageList = "46EB5926-582E-4017-9FDF-E8998DAA0950";
|
||||
const string IID_IImageList2 = "192B9D83-50FC-457B-90A0-2B82A8B5DAE1";
|
||||
|
||||
public static class Shell32
|
||||
{
|
||||
|
||||
public const int SHIL_LARGE = 0x0;
|
||||
public const int SHIL_SMALL = 0x1;
|
||||
public const int SHIL_EXTRALARGE = 0x2;
|
||||
public const int SHIL_SYSSMALL = 0x3;
|
||||
public const int SHIL_JUMBO = 0x4;
|
||||
public const int SHIL_LAST = 0x4;
|
||||
|
||||
public const int ILD_TRANSPARENT = 0x00000001;
|
||||
public const int ILD_IMAGE = 0x00000020;
|
||||
|
||||
[DllImport("shell32.dll", EntryPoint = "#727")]
|
||||
public extern static int SHGetImageList(int iImageList, ref Guid riid, ref IImageList ppv);
|
||||
|
||||
[DllImport("user32.dll", EntryPoint = "DestroyIcon", SetLastError = true)]
|
||||
public static extern int DestroyIcon(IntPtr hIcon);
|
||||
|
||||
//[DllImport("shell32.dll")]
|
||||
//public static extern uint SHGetIDListFromObject([MarshalAs(UnmanagedType.IUnknown)] object iUnknown, out IntPtr ppidl);
|
||||
|
||||
[DllImport("Shell32.dll")]
|
||||
public static extern IntPtr SHGetFileInfo(string pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, uint cbFileInfo, uint uFlags);
|
||||
}
|
||||
|
||||
[Flags]
|
||||
enum SHGFI : uint
|
||||
{
|
||||
/// <summary>get icon</summary>
|
||||
Icon = 0x000000100,
|
||||
/// <summary>get display name</summary>
|
||||
DisplayName = 0x000000200,
|
||||
/// <summary>get type name</summary>
|
||||
TypeName = 0x000000400,
|
||||
/// <summary>get attributes</summary>
|
||||
Attributes = 0x000000800,
|
||||
/// <summary>get icon location</summary>
|
||||
IconLocation = 0x000001000,
|
||||
/// <summary>return exe type</summary>
|
||||
ExeType = 0x000002000,
|
||||
/// <summary>get system icon index</summary>
|
||||
SysIconIndex = 0x000004000,
|
||||
/// <summary>put a link overlay on icon</summary>
|
||||
LinkOverlay = 0x000008000,
|
||||
/// <summary>show icon in selected state</summary>
|
||||
Selected = 0x000010000,
|
||||
/// <summary>get only specified attributes</summary>
|
||||
Attr_Specified = 0x000020000,
|
||||
/// <summary>get large icon</summary>
|
||||
LargeIcon = 0x000000000,
|
||||
/// <summary>get small icon</summary>
|
||||
SmallIcon = 0x000000001,
|
||||
/// <summary>get open icon</summary>
|
||||
OpenIcon = 0x000000002,
|
||||
/// <summary>get shell size icon</summary>
|
||||
ShellIconSize = 0x000000004,
|
||||
/// <summary>pszPath is a pidl</summary>
|
||||
PIDL = 0x000000008,
|
||||
/// <summary>use passed dwFileAttribute</summary>
|
||||
UseFileAttributes = 0x000000010,
|
||||
/// <summary>apply the appropriate overlays</summary>
|
||||
AddOverlays = 0x000000020,
|
||||
/// <summary>Get the index of the overlay in the upper 8 bits of the iIcon</summary>
|
||||
OverlayIndex = 0x000000040,
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SHFILEINFO
|
||||
{
|
||||
public const int NAMESIZE = 80;
|
||||
public IntPtr hIcon;
|
||||
public int iIcon;
|
||||
public uint dwAttributes;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string szDisplayName;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
|
||||
public string szTypeName;
|
||||
};
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RECT
|
||||
{
|
||||
public int left, top, right, bottom;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct POINT
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct IMAGELISTDRAWPARAMS
|
||||
{
|
||||
public int cbSize;
|
||||
public IntPtr himl;
|
||||
public int i;
|
||||
public IntPtr hdcDst;
|
||||
public int x;
|
||||
public int y;
|
||||
public int cx;
|
||||
public int cy;
|
||||
public int xBitmap; // x offest from the upperleft of bitmap
|
||||
public int yBitmap; // y offset from the upperleft of bitmap
|
||||
public int rgbBk;
|
||||
public int rgbFg;
|
||||
public int fStyle;
|
||||
public int dwRop;
|
||||
public int fState;
|
||||
public int Frame;
|
||||
public int crEffect;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct IMAGEINFO
|
||||
{
|
||||
public IntPtr hbmImage;
|
||||
public IntPtr hbmMask;
|
||||
public int Unused1;
|
||||
public int Unused2;
|
||||
public RECT rcImage;
|
||||
}
|
||||
[ComImportAttribute()]
|
||||
[GuidAttribute("46EB5926-582E-4017-9FDF-E8998DAA0950")]
|
||||
[InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IImageList
|
||||
{
|
||||
[PreserveSig]
|
||||
int Add(IntPtr hbmImage, IntPtr hbmMask, ref int pi);
|
||||
|
||||
[PreserveSig]
|
||||
int ReplaceIcon(int i, IntPtr hicon, ref int pi);
|
||||
|
||||
[PreserveSig]
|
||||
int SetOverlayImage(int iImage, int iOverlay);
|
||||
|
||||
[PreserveSig]
|
||||
int Replace(int i, IntPtr hbmImage, IntPtr hbmMask);
|
||||
|
||||
[PreserveSig]
|
||||
int AddMasked(IntPtr hbmImage, int crMask, ref int pi);
|
||||
|
||||
[PreserveSig]
|
||||
int Draw(ref IMAGELISTDRAWPARAMS pimldp);
|
||||
|
||||
[PreserveSig]
|
||||
int Remove(int i);
|
||||
|
||||
[PreserveSig]
|
||||
int GetIcon(int i, int flags, ref IntPtr picon);
|
||||
|
||||
[PreserveSig]
|
||||
int GetImageInfo(int i, ref IMAGEINFO pImageInfo);
|
||||
|
||||
[PreserveSig]
|
||||
int Copy(int iDst, IImageList punkSrc, int iSrc, int uFlags);
|
||||
|
||||
[PreserveSig]
|
||||
int Merge(int i1, IImageList punk2, int i2, int dx, int dy, ref Guid riid, ref IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int Clone(ref Guid riid, ref IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int GetImageRect(int i, ref RECT prc);
|
||||
|
||||
[PreserveSig]
|
||||
int GetIconSize(ref int cx, ref int cy);
|
||||
|
||||
[PreserveSig]
|
||||
int SetIconSize(int cx, int cy);
|
||||
|
||||
[PreserveSig]
|
||||
int GetImageCount(ref int pi);
|
||||
|
||||
[PreserveSig]
|
||||
int SetImageCount(int uNewCount);
|
||||
|
||||
[PreserveSig]
|
||||
int SetBkColor(int clrBk, ref int pclr);
|
||||
|
||||
[PreserveSig]
|
||||
int GetBkColor(ref int pclr);
|
||||
|
||||
[PreserveSig]
|
||||
int BeginDrag(int iTrack, int dxHotspot, int dyHotspot);
|
||||
|
||||
[PreserveSig]
|
||||
int EndDrag();
|
||||
|
||||
[PreserveSig]
|
||||
int DragEnter(IntPtr hwndLock, int x, int y);
|
||||
|
||||
[PreserveSig]
|
||||
int DragLeave(IntPtr hwndLock);
|
||||
|
||||
[PreserveSig]
|
||||
int DragMove(int x, int y);
|
||||
|
||||
[PreserveSig]
|
||||
int SetDragCursorImage(ref IImageList punk, int iDrag, int dxHotspot, int dyHotspot);
|
||||
|
||||
[PreserveSig]
|
||||
int DragShowNolock(int fShow);
|
||||
|
||||
[PreserveSig]
|
||||
int GetDragImage(ref POINT ppt, ref POINT pptHotspot, ref Guid riid, ref IntPtr ppv);
|
||||
|
||||
[PreserveSig]
|
||||
int GetItemFlags(int i, ref int dwFlags);
|
||||
|
||||
[PreserveSig]
|
||||
int GetOverlayImage(int iOverlay, ref int piIndex);
|
||||
};
|
||||
|
||||
}
|
||||
}
|
96
src/FocusVolumeControl/UI/JavaIconExtractor.cs
Normal file
96
src/FocusVolumeControl/UI/JavaIconExtractor.cs
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -39,12 +39,24 @@ namespace FocusVolumeControl
|
||||
|
||||
public event Action WindowChanged;
|
||||
|
||||
private void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
||||
CancellationTokenSource? _cancellationTokenSource = null;
|
||||
|
||||
private async void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
//debounce the window changed events by 100 ms because if you click mouse over an application on the start bar
|
||||
//and then click on the preview window, it will quickly go from current -> fallback -> new app
|
||||
//which can often result in it getting stuck on the fallback app
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
await Task.Delay(100, _cancellationTokenSource.Token);
|
||||
WindowChanged?.Invoke();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
//ignored
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.LogMessage(TracingLevel.ERROR, $"Unexpected Error in EventHandler:\n {ex}");
|
||||
|
@ -57,7 +57,7 @@ public partial class MainWindow : Window
|
||||
|
||||
foreach (var p in processes)
|
||||
{
|
||||
var (displayName, _) = (new NameAndIconHelper()).GetProcessInfo(p);
|
||||
var displayName = (new NameAndIconHelper()).GetProcessInfo(p);
|
||||
|
||||
sb.AppendLine($"pid: {p.Id}");
|
||||
sb.AppendLine($"\tprocessName: {p.ProcessName}");
|
||||
@ -92,14 +92,16 @@ public partial class MainWindow : Window
|
||||
deviceEnumerator.EnumAudioEndpoints(DataFlow.Render, DeviceState.Active, out var deviceCollection);
|
||||
deviceCollection.GetCount(out var num);
|
||||
|
||||
|
||||
for (int i = 0; i < num; i++)
|
||||
{
|
||||
deviceCollection.Item(i, out var device);
|
||||
//todo: put the device name in the output
|
||||
sb.AppendLine("----");
|
||||
sb.AppendLine($"----");
|
||||
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@ -112,9 +114,10 @@ public partial class MainWindow : Window
|
||||
sessionEnumerator.GetSession(s, out var session);
|
||||
|
||||
session.GetProcessId(out var processId);
|
||||
session.GetIconPath(out var path);
|
||||
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}");
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user