using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Timers; namespace VRCX { // I don't think this applies to our use case, but I'm leaving it here for reference. // https://stackoverflow.com/questions/2519673/process-hasexited-returns-true-even-though-process-is-running // "When a process is started, it is assigned a PID. If the User is then prompted with the User Account Control dialog and selects 'Yes', the process is re-started and assigned a new PID." // There's no docs for this, but Process.HasExited also seems to be checked every time the property is accessed, so it's not cached. Which means Process.Refresh() is not needed for our use case. /// /// A class that monitors given processes and raises events when they are started or exited. /// Intended to be used to monitor VRChat and VRChat-related processes. /// internal class ProcessMonitor { private readonly Dictionary monitoredProcesses; private readonly Timer monitorProcessTimer; static ProcessMonitor() { Instance = new ProcessMonitor(); } public ProcessMonitor() { monitoredProcesses = new Dictionary(); monitorProcessTimer = new Timer(); monitorProcessTimer.Interval = 1000; monitorProcessTimer.Elapsed += MonitorProcessTimer_Elapsed; } public static ProcessMonitor Instance { get; private set; } /// /// Raised when a monitored process is started. /// public event Action ProcessStarted; /// /// Raised when a monitored process is exited. /// public event Action ProcessExited; public void Init() { AddProcess("vrchat"); AddProcess("vrserver"); monitorProcessTimer.Start(); } public void Exit() { monitorProcessTimer.Stop(); monitoredProcesses.Values.ToList().ForEach(x => x.ProcessExited()); } private void MonitorProcessTimer_Elapsed(object sender, ElapsedEventArgs e) { var processesNeedingUpdate = new List(); // Check if any of the monitored processes have been opened or closed. foreach (var keyValuePair in monitoredProcesses) { var monitoredProcess = keyValuePair.Value; var process = monitoredProcess.Process; var name = monitoredProcess.ProcessName; if (monitoredProcess.IsRunning) { if (monitoredProcess.Process == null || monitoredProcess.Process.HasExited) { monitoredProcess.ProcessExited(); ProcessExited?.Invoke(monitoredProcess); } monitoredProcess.Process.Refresh(); } else { processesNeedingUpdate.Add(monitoredProcess); } } // We do it this way so we're not constantly polling for processes if we don't actually need to (aka, all processes are already accounted for). if (processesNeedingUpdate.Count == 0) return; var processes = Process.GetProcesses(); foreach (var monitoredProcess in processesNeedingUpdate) { var process = processes.FirstOrDefault(p => string.Equals(p.ProcessName, monitoredProcess.ProcessName, StringComparison.OrdinalIgnoreCase)); if (process != null) { monitoredProcess.ProcessStarted(process); ProcessStarted?.Invoke(monitoredProcess); } } } /// /// Checks if a process if currently being monitored and if it is running. /// /// The name of the process to check for. /// If true, will manually check if the given process is running should the the monitored process not be initialized yet. /// Whether the given process is monitored and currently running. public bool IsProcessRunning(string processName, bool ensureCheck = false) { processName = processName.ToLower(); if (monitoredProcesses.ContainsKey(processName)) { var process = monitoredProcesses[processName]; if (ensureCheck && process.Process == null) return Process.GetProcessesByName(processName).FirstOrDefault() != null; return process.IsRunning; } return false; } /// /// Adds a process to be monitored. /// /// public void AddProcess(Process process) { if (monitoredProcesses.ContainsKey(process.ProcessName.ToLower())) return; monitoredProcesses.Add(process.ProcessName.ToLower(), new MonitoredProcess(process)); } /// /// Adds a process to be monitored. /// /// public void AddProcess(string processName) { if (monitoredProcesses.ContainsKey(processName.ToLower())) { return; } monitoredProcesses.Add(processName, new MonitoredProcess(processName)); } /// /// Removes a process from being monitored. /// /// public void RemoveProcess(string processName) { if (monitoredProcesses.ContainsKey(processName.ToLower())) { monitoredProcesses.Remove(processName); } } } internal class MonitoredProcess { public MonitoredProcess(Process process) { Process = process; ProcessName = process.ProcessName.ToLower(); if (!process.HasExited) IsRunning = true; } public MonitoredProcess(string processName) { ProcessName = processName; IsRunning = false; } public Process Process { get; private set; } public string ProcessName { get; private set; } public bool IsRunning { get; private set; } public bool HasName(string processName) { return ProcessName.Equals(processName, StringComparison.OrdinalIgnoreCase); } public void ProcessExited() { IsRunning = false; Process?.Dispose(); Process = null; } public void ProcessStarted(Process process) { Process = process; ProcessName = process.ProcessName.ToLower(); IsRunning = true; } } }