forked from vrcx-team/VRCX
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProcessMonitor.cs
205 lines (174 loc) · 7.19 KB
/
ProcessMonitor.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
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.
/// <summary>
/// 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.
/// </summary>
internal class ProcessMonitor
{
private readonly Dictionary<string, MonitoredProcess> monitoredProcesses;
private readonly Timer monitorProcessTimer;
static ProcessMonitor()
{
Instance = new ProcessMonitor();
}
public ProcessMonitor()
{
monitoredProcesses = new Dictionary<string, MonitoredProcess>();
monitorProcessTimer = new Timer();
monitorProcessTimer.Interval = 1000;
monitorProcessTimer.Elapsed += MonitorProcessTimer_Elapsed;
}
public static ProcessMonitor Instance { get; private set; }
/// <summary>
/// Raised when a monitored process is started.
/// </summary>
public event Action<MonitoredProcess> ProcessStarted;
/// <summary>
/// Raised when a monitored process is exited.
/// </summary>
public event Action<MonitoredProcess> 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<MonitoredProcess>();
// 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 || WinApi.HasProcessExited(monitoredProcess.Process.Id))
{
monitoredProcess.ProcessExited();
ProcessExited?.Invoke(monitoredProcess);
}
}
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);
}
}
}
/// <summary>
/// Checks if a process if currently being monitored and if it is running.
/// </summary>
/// <param name="processName">The name of the process to check for.</param>
/// <param name="ensureCheck">If true, will manually check if the given process is running should the the monitored process not be initialized yet.</param>
/// <returns>Whether the given process is monitored and currently running.</returns>
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;
}
/// <summary>
/// Adds a process to be monitored.
/// </summary>
/// <param name="process"></param>
public void AddProcess(Process process)
{
if (monitoredProcesses.ContainsKey(process.ProcessName.ToLower()))
return;
monitoredProcesses.Add(process.ProcessName.ToLower(), new MonitoredProcess(process));
}
/// <summary>
/// Adds a process to be monitored.
/// </summary>
/// <param name="processName"></param>
public void AddProcess(string processName)
{
if (monitoredProcesses.ContainsKey(processName.ToLower()))
{
return;
}
monitoredProcesses.Add(processName, new MonitoredProcess(processName));
}
/// <summary>
/// Removes a process from being monitored.
/// </summary>
/// <param name="processName"></param>
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 != null && !WinApi.HasProcessExited(process.Id))
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;
}
}
}