Skip to content

Commit

Permalink
Merge pull request #39 from A-tG/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
A-tG authored Dec 12, 2024
2 parents c4aae79 + 559b2e1 commit bcd1a32
Show file tree
Hide file tree
Showing 18 changed files with 171 additions and 140 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ Windows 10 or newer is required. May work with older Windows versions (no older
It might also not work with some OpenGL games, even if in borderless mode. Basically if "Xbox Game Bar" or standard Volume/Media pop-up is not showing on top of the game, neither will this OSD.

# Installation and Usage
The program is portable, no installation is required, but you need [.NET Desktop runtime](https://dotnet.microsoft.com/download/dotnet/7.0/runtime) (click '*Download x64*' under '*Run desktop apps*') **or alternatively** you may install the runtime via Command Prompt with the following command:
The program is portable, no installation is required, but you need [.NET Desktop runtime](https://dotnet.microsoft.com/download/dotnet/8.0/runtime) (click '*Download x64*' under '*Run desktop apps*') **or alternatively** you may install the runtime via Command Prompt with the following command:

`winget install Microsoft.DotNet.DesktopRuntime.7`
`winget install Microsoft.DotNet.DesktopRuntime.8`

(winget is bundled in Windows 11 and some modern versions of Windows 10 by default as the "App Installer", if your Windows version does not include it, you may read [here](https://docs.microsoft.com/en-us/windows/package-manager/winget/) for further instructions on how to get it).

Expand All @@ -35,7 +35,7 @@ So this program should be a good "replacement" of the default volume pop-up in W
VoicemeeterFancyOsdHost.exe, hostfxr.dll and DXGI.dll are necessary to display OSD on top of fulscreen games, but if you have weird problems launching VoicemeeterFancyOsdHost.exe you can try to replace hostfxr.dll with different version bundled with Dotnet from `C:\Program Files\dotnet\host\fxr\N.N.N\hostfxr.dll`. Also VoicemeeterFancyOsdHost.exe is a renamed ApplicationFrameHost.exe from `C:\Windows\System32` and can be replaced too if there are some problems with it. DXGI.dll is compiled and helps to launch program itself by Host.exe. If even VoicemeeterFancyOsd.exe is not working deletion of these is also an option but you lose that functionaluty with fullscreen games of course.

# Build instructions
[.NET 7.0](https://dotnet.microsoft.com/download) WPF project
[.NET 8.0](https://dotnet.microsoft.com/download) WPF project

Start/build in debug mode to get acces to Debug Window in tray context menu.

Expand Down
41 changes: 26 additions & 15 deletions VoicemeeterOsdProgram/AppLifeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.IO;
using System.IO.Pipes;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Windows.Threading;

namespace VoicemeeterOsdProgram;
Expand All @@ -14,17 +16,18 @@ public static class AppLifeManager
private static Mutex m_mutex = new(true, Program.UniqueName);
private static bool? m_isLareadyRunning;
private static Dispatcher m_dispatcher;
private static Channel<string[]> m_argsChannel = Channel.CreateUnbounded<string[]>(new() {
SingleReader = true,
SingleWriter = true
});

public static string[] appArgs = Array.Empty<string>();

public static bool IsAlreadyRunning
{
get
{
if (m_isLareadyRunning is null)
{
m_isLareadyRunning = !m_mutex.WaitOne(0, false);
}
m_isLareadyRunning ??= !m_mutex.WaitOne(0, false);
return m_isLareadyRunning.Value;
}
}
Expand All @@ -51,17 +54,25 @@ public static void CloseDuplicates()
string programName = Path.GetFileNameWithoutExtension(exeFile.Name);
var procs = Process.GetProcessesByName(programName);
RequestKillDuplicateProcesses(procs);
foreach (var p in procs)
{
p.Dispose();
}
}
}

public static void StartArgsPipeServer()
{
Thread pipeServerThread = new(CreatePipeServer)
_ = ProcessArgsLoop();
_ = PipeServerLoop();
}

private static async Task ProcessArgsLoop()
{
await foreach (var a in m_argsChannel.Reader.ReadAllAsync())
{
IsBackground = true,
};
pipeServerThread.SetApartmentState(ApartmentState.STA);
pipeServerThread.Start();
await m_dispatcher.Invoke(async () => await ArgsHandler.HandleAsync(a));
}
}

private static void SendArgsToFirstInstance(string[] args)
Expand Down Expand Up @@ -92,22 +103,22 @@ private static void RequestKillDuplicateProcesses(Process[] procs)
}
}

private static void CreatePipeServer()
private static async Task PipeServerLoop(CancellationToken ct = default)
{
using NamedPipeServerStream server = new(Program.UniqueName, PipeDirection.In);
await using NamedPipeServerStream server = new(Program.UniqueName, PipeDirection.In);
using StreamReader reader = new(server);
while (true)
while (!ct.IsCancellationRequested)
{
server.WaitForConnection();
await server.WaitForConnectionAsync();
try
{
List<string> args = new();
string arg;
while (!string.IsNullOrEmpty(arg = reader.ReadLine()))
while (!string.IsNullOrEmpty(arg = await reader.ReadLineAsync()))
{
args.Add(arg);
}
m_dispatcher.Invoke(async () => await ArgsHandler.HandleAsync(args.ToArray()));
m_argsChannel.Writer.TryWrite([..args]);
}
catch { }
server.Disconnect();
Expand Down
5 changes: 5 additions & 0 deletions VoicemeeterOsdProgram/ArgsHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Controls;
using VoicemeeterOsdProgram.Core;
using VoicemeeterOsdProgram.Factories;
using VoicemeeterOsdProgram.Options;
using VoicemeeterOsdProgram.UiControls;
Expand All @@ -21,6 +22,7 @@ public static class Args
public const string SetOption = "-set-option";
public const string Exit = "-exit";
public const string Help = "-help";
public const string OpenSettings = "-open-settings";
}

private static Dialog m_helpDialog;
Expand Down Expand Up @@ -77,6 +79,9 @@ private static async Task<bool> HandleArgAsync(string[] args, int i)
case Args.Help:
ShowHelpWindow();
return true;
case Args.OpenSettings:
TrayIconManager.OpenSettings();
break;
default:
m_logger?.LogError($"Unknown command line argument: {arg}");
return false;
Expand Down
4 changes: 2 additions & 2 deletions VoicemeeterOsdProgram/Core/OsdWindowManager.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using AtgDev.Voicemeeter.Types;
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
using VoicemeeterOsdProgram.Core.Types;
Expand Down Expand Up @@ -48,8 +49,7 @@ static OsdWindowManager()
Activatable = false,
TopMost = true,
IsClickThrough = true,
ZBandID = GetTopMostZBandID(),
Logger = Globals.Logger
ZBandID = GetTopMostZBandID()
};
win.CreateWindow();
m_window = win;
Expand Down
2 changes: 2 additions & 0 deletions VoicemeeterOsdProgram/Core/TrayIconManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ public static void Init() { }

public static void OpenUpdater() => TrayIcon.OpenUpdater();

public static void OpenSettings() => TrayIcon.OpenSettingsWindow();

public static void Destroy() => TrayIcon.NotifyIcon?.Dispose();
}
13 changes: 13 additions & 0 deletions VoicemeeterOsdProgram/Core/Types/VmParamsMemory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System.Runtime.InteropServices;

namespace VoicemeeterOsdProgram.Core.Types;

unsafe static internal class VmParamsMemory
{
internal static byte* ParamNamesBuffer { get; private set; }

internal static void Allocate(nuint len)
{
ParamNamesBuffer = (byte*)NativeMemory.Realloc(ParamNamesBuffer, len);
}
}
24 changes: 7 additions & 17 deletions VoicemeeterOsdProgram/Core/Types/VoicemeeterNumParam.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,13 @@

namespace VoicemeeterOsdProgram.Core.Types;

public class VoicemeeterNumParam : VoicemeeterParameterBase<float>
public class VoicemeeterNumParam(RemoteApiWrapper api, string command) : VoicemeeterParameterBase<float>(api, command)
{
public VoicemeeterNumParam(RemoteApiExtender api, string command) : base(api, command) { }
unsafe public override int GetParameter(out float val) => IsOptimized ?
m_api.GetParameter((IntPtr)NameBuffer, out val) :
m_api.GetParameter(Name, out val);

unsafe public override int GetParameter(out float val)
{
fixed (byte* command = m_nameBuffer)
{
return m_api.GetParameter((IntPtr)command, out val);
}
}

unsafe public override int SetParameter(float value)
{
fixed (byte* command = m_nameBuffer)
{
return m_api.SetParameter((IntPtr)command, value);
}
}
unsafe public override int SetParameter(float value) => IsOptimized ?
m_api.SetParameter((IntPtr)NameBuffer, value) :
m_api.SetParameter(Name, value);
}
42 changes: 17 additions & 25 deletions VoicemeeterOsdProgram/Core/Types/VoicemeeterParameterBase.T.cs
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
using AtgDev.Voicemeeter;
using System;
using System.Linq;

namespace VoicemeeterOsdProgram.Core.Types;

public abstract class VoicemeeterParameterBase<T> : VoicemeeterParameterBase
public abstract class VoicemeeterParameterBase<T>(RemoteApiWrapper api, string command) : VoicemeeterParameterBase(api, command)
{
protected T m_value; // can be null, initialize in derived class

public VoicemeeterParameterBase(RemoteApiExtender api, string command) : base(api, command) { }

public T Value
{
get => m_value;
protected set
{
if (!m_value.Equals(value))
{
OnReadValueChanged(m_value, value);
}
if (m_value.Equals(value)) return;

m_value = value;
OnReadValueChanged(m_value, value);
}
}

Expand All @@ -30,31 +28,25 @@ public override void ReadIsNotifyChanges(bool isNotify)
{
if (!IsEnabled) return;

if ((m_api is null) || string.IsNullOrEmpty(m_name)) return;
if (GetParameter(out T val) != ResultCodes.Ok) return;

var res = GetParameter(out T val);
if (res == 0)
var oldVal = m_value;
if (isNotify)
{
var oldVal = m_value;
if (isNotify)
{
Value = val;
}
else
{
m_value = val;
}
OnValueRead(oldVal, val);
Value = val;
}
else
{
m_value = val;
}
OnValueRead(oldVal, val);
}

public void Write(T value)
{
if (!IsEnabled) return;

if ((m_api is null) || string.IsNullOrEmpty(m_name)) return;

if (SetParameter(value) == 0)
if (SetParameter(value) == ResultCodes.Ok)
{
m_value = value;
}
Expand All @@ -64,15 +56,15 @@ public override void ClearEvents()
{
if (ReadValueChanged is not null)
{
foreach (EventHandler<ValOldNew<T>> del in ReadValueChanged.GetInvocationList())
foreach (var del in ReadValueChanged.GetInvocationList().Cast<EventHandler<ValOldNew<T>>>())
{
ReadValueChanged -= del;
}
}

if (ValueRead is not null)
{
foreach (EventHandler<ValOldNew<T>> del in ValueRead.GetInvocationList())
foreach (var del in ValueRead.GetInvocationList().Cast<EventHandler<ValOldNew<T>>>())
{
ValueRead -= del;
}
Expand Down
52 changes: 25 additions & 27 deletions VoicemeeterOsdProgram/Core/Types/VoicemeeterParameterBase.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,48 @@
using AtgDev.Voicemeeter;
using System.Text;
using System;

namespace VoicemeeterOsdProgram.Core.Types;

public abstract class VoicemeeterParameterBase
{
protected readonly string m_name;
protected readonly RemoteApiExtender m_api;
protected readonly RemoteApiWrapper m_api;

protected readonly byte[] m_nameBuffer;
public string Name { get; private set; }

public VoicemeeterParameterBase(RemoteApiExtender api, string command)
public bool IsEnabled { get; set; } = true;

internal int NameIndex { get; private set; } = 0;
internal int NameLength { get; private set; } = 0;
internal unsafe byte* NamesBuffer { get; private set; }
internal unsafe byte* NameBuffer { get; private set; }
internal bool IsOptimized { get; private set; } = false;

public VoicemeeterParameterBase(RemoteApiWrapper api, string command)
{
m_api = api;
m_name = command;
ArgumentNullException.ThrowIfNull(api);
ArgumentException.ThrowIfNullOrWhiteSpace(command);

m_nameBuffer = GetNullTermAsciiBuffFromString(command);
m_api = api;
Name = command;

Read();
}

public string Name { get => m_name; }

public bool IsEnabled { get; set; } = true;

public void ReadNotifyChanges()
{
ReadIsNotifyChanges(true);
}
public void ReadNotifyChanges() => ReadIsNotifyChanges(true);

public void Read()
{
ReadIsNotifyChanges(false);
}
public void Read() => ReadIsNotifyChanges(false);

public abstract void ReadIsNotifyChanges(bool isNotify);

public abstract void ClearEvents();

protected byte[] GetNullTermAsciiBuffFromString(string str)

internal unsafe void SetupNameBufferP(byte* buff, int index, int len)
{
var len = str.Length;
var buff = new byte[str.Length + 1];
Encoding.ASCII.GetBytes(str, 0, len, buff, 0);
buff[^1] = 0;
return buff;
NamesBuffer = buff;
NameIndex = index;
NameLength = len;
NameBuffer = &buff[index];
IsOptimized = true;
}

}
2 changes: 1 addition & 1 deletion VoicemeeterOsdProgram/Core/Types/VoicemeeterProperties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace VoicemeeterOsdProgram.Core.Types;

public struct VoicemeeterProperties
public readonly struct VoicemeeterProperties
{
public readonly int hardInputs;
public readonly int virtInputs;
Expand Down
Loading

0 comments on commit bcd1a32

Please sign in to comment.