Refactor how icons work, and made java icons work a little better
This commit is contained in:
parent
8eebf1af47
commit
f26c3a3060
@ -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")
|
||||||
|
{
|
||||||
|
var windowHandle = process.MainWindowHandle;
|
||||||
|
var lazyIcon = new Lazy<Bitmap?>(() => JavaIconExtractor.GetWindowBigIcon(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 { }
|
||||||
|
@ -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();
|
||||||
{
|
|
||||||
return GetIconFromIconPath();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return GetIconFromExecutablePath();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return "Images/encoderIcon";
|
return IconWrapper.FallbackIconData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
107
src/FocusVolumeControl/AudioSessions/IconWrapper.cs
Normal file
107
src/FocusVolumeControl/AudioSessions/IconWrapper.cs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
using BarRaider.SdTools;
|
||||||
|
using BitFaster.Caching.Lru;
|
||||||
|
using FocusVolumeControl.UI;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
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, Lazy<Bitmap?> lazyIcon)
|
||||||
|
{
|
||||||
|
_data = _iconCache.GetOrAdd(name, (key) =>
|
||||||
|
{
|
||||||
|
var icon = lazyIcon.Value;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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" />
|
||||||
|
79
src/FocusVolumeControl/UI/JavaIconExtractor.cs
Normal file
79
src/FocusVolumeControl/UI/JavaIconExtractor.cs
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
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 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);
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -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}");
|
||||||
@ -112,9 +112,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}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user