Files
VRCX/AutoAppLaunchManager.cs
Natsumi bb31ce5736 feat: Add an automatic app launcher (#541)
* refactor: Change OpenImageFolder to use a winapi call

It will open the given file in any existing explorer instances instead of opening a new shell every time, handle longer paths, and work with third-party filesystem viewers with winapi hooks(well, this one).

* feat: Add an automatic app launcher

The launcer will automatically launch apps in the 'startup' folder under the VRCX appdata folder when VRChat has started, and close them when VRChat dies(or is closed).

* refactor: Add new class for monitoring VRC processes

This replaces the old AppApi functionality that would poll all processes twice to grab the status of both VRChat and vrserver.exe every... *checks app.js* 500ms.

It also raises events for when a monitored process is started/closed, mainly for the new AppLauncher feature, which is now using this class instead of monitoring vrchat itself.

* refactor: Add tooltip for launch folder button

* docs: Add some notes on potential issues with Process.HasExited

* Change CheckGameRunning from polling to events

---------

Co-authored-by: Teacup <git@teadev.xyz>
2023-05-10 16:57:25 +12:00

140 lines
4.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace VRCX
{
/// <summary>
/// The class responsible for launching user-defined applications when VRChat opens/closes.
/// </summary>
public class AutoAppLaunchManager
{
public static AutoAppLaunchManager Instance { get; private set; }
public bool Enabled = true;
public readonly string AppShortcutDirectory;
private DateTime startTime = DateTime.Now;
private List<Process> startedProcesses = new List<Process>();
private static readonly byte[] shortcutSignatureBytes = { 0x4C, 0x00, 0x00, 0x00 }; // signature for ShellLinkHeader\
static AutoAppLaunchManager()
{
Instance = new AutoAppLaunchManager();
}
public AutoAppLaunchManager()
{
AppShortcutDirectory = Path.Combine(Program.AppDataDirectory, "startup");
if (!Directory.Exists(AppShortcutDirectory))
{
Directory.CreateDirectory(AppShortcutDirectory);
}
ProcessMonitor.Instance.ProcessStarted += OnProcessStarted;
ProcessMonitor.Instance.ProcessExited += OnProcessExited;
}
private void OnProcessExited(MonitoredProcess monitoredProcess)
{
if (startedProcesses.Count == 0 || !monitoredProcess.HasName("VRChat"))
return;
foreach (var process in startedProcesses)
{
if (!process.HasExited)
process.Kill();
}
startedProcesses.Clear();
}
private void OnProcessStarted(MonitoredProcess monitoredProcess)
{
if (!monitoredProcess.HasName("VRChat") || monitoredProcess.Process.StartTime < startTime)
return;
if (startedProcesses.Count > 0)
{
foreach (var process in startedProcesses)
{
if (!process.HasExited)
process.Kill();
}
startedProcesses.Clear();
}
var shortcutFiles = FindShortcutFiles(AppShortcutDirectory);
if (shortcutFiles.Length > 0)
{
foreach (var file in shortcutFiles)
{
var process = Process.Start(file);
startedProcesses.Add(process);
}
}
}
internal void Init()
{
// What are you lookin at?
}
internal void Exit()
{
Enabled = false;
foreach (var process in startedProcesses)
{
if (!process.HasExited)
process.Kill();
}
}
/// <summary>
/// Finds windows shortcut files in a given folder.
/// </summary>
/// <param name="folderPath">The folder path.</param>
/// <returns>An array of shortcut paths. If none, then empty.</returns>
private static string[] FindShortcutFiles(string folderPath)
{
DirectoryInfo directoryInfo = new DirectoryInfo(folderPath);
FileInfo[] files = directoryInfo.GetFiles();
List<string> ret = new List<string>();
foreach (FileInfo file in files)
{
if (IsShortcutFile(file.FullName))
{
ret.Add(file.FullName);
}
}
return ret.ToArray();
}
/// <summary>
/// Determines whether the specified file path is a shortcut by checking the file header.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <returns><c>true</c> if the given file path is a shortcut, otherwise <c>false</c></returns>
private static bool IsShortcutFile(string filePath)
{
byte[] headerBytes = new byte[4];
using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
if (fileStream.Length >= 4)
{
fileStream.Read(headerBytes, 0, 4);
}
}
return headerBytes.SequenceEqual(shortcutSignatureBytes);
}
}
}