First partially working process launching/monitoring update

This commit is contained in:
Terry MacDonald 2021-11-07 11:04:53 +13:00
parent c06ba48923
commit 10506a6537
9 changed files with 434 additions and 297 deletions

View File

@ -556,7 +556,7 @@ namespace DisplayMagician.GameLibraries
return true; return true;
} }
public override Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal) /*public override Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal)
{ {
string address = $@"com.epicgames.launcher://apps/{game.Id}?action=launch&silent=true"; string address = $@"com.epicgames.launcher://apps/{game.Id}?action=launch&silent=true";
if (String.IsNullOrWhiteSpace(gameArguments)) if (String.IsNullOrWhiteSpace(gameArguments))
@ -566,7 +566,18 @@ namespace DisplayMagician.GameLibraries
Process gameProcess = Process.Start(address); Process gameProcess = Process.Start(address);
gameProcess.PriorityClass = processPriority; gameProcess.PriorityClass = processPriority;
return gameProcess; return gameProcess;
}*/
public override List<Process> StartGame(Game game, string gameArguments = "", ProcessPriority processPriority = ProcessPriority.Normal)
{
string address = $@"com.epicgames.launcher://apps/{game.Id}?action=launch&silent=true";
if (!String.IsNullOrWhiteSpace(gameArguments))
{
address += @"/" + gameArguments;
}
//Process gameProcess = Process.Start(address);
List<Process> gameProcesses = ProcessUtils.StartProcess(address, null, processPriority);
return gameProcesses;
} }
#endregion #endregion

View File

@ -552,21 +552,18 @@ namespace DisplayMagician.GameLibraries
} }
public override Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal) public override List<Process> StartGame(Game game, string gameArguments = "", ProcessPriority processPriority = ProcessPriority.Normal)
{ {
string args = $@"/command=runGame /gameId={game.Id} /path=""{game.Directory}"""; string args = $@"/command=runGame /gameId={game.Id} /path=""{game.Directory}""";
if (String.IsNullOrWhiteSpace(gameArguments)) if (String.IsNullOrWhiteSpace(gameArguments))
{ {
args += gameArguments; args += gameArguments;
} }
Process gameProcess = null; List<Process> gameProcesses = ProcessUtils.StartProcess(_gogExe, args, processPriority);
ProcessUtils.PROCESS_INFORMATION processInfo; return gameProcesses;
if (ProcessUtils.CreateProcessWithPriority(_gogExe, args, processPriority, out processInfo))
{
gameProcess = Process.GetProcessById(processInfo.dwProcessId);
}
return gameProcess;
} }
#endregion #endregion
} }

View File

@ -108,7 +108,7 @@ namespace DisplayMagician.GameLibraries
return false; return false;
} }
public virtual Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal) public virtual List<Process> StartGame(Game game, string gameArguments = "", ProcessPriority processPriority = ProcessPriority.Normal)
{ {
return null; return null;
} }

View File

@ -725,7 +725,7 @@ namespace DisplayMagician.GameLibraries
return true; return true;
} }
public override Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal) /*public override Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal)
{ {
string address = $"origin2://game/launch?offerIds={game.Id}"; string address = $"origin2://game/launch?offerIds={game.Id}";
if (String.IsNullOrWhiteSpace(gameArguments)) if (String.IsNullOrWhiteSpace(gameArguments))
@ -735,6 +735,17 @@ namespace DisplayMagician.GameLibraries
Process gameProcess = Process.Start(address); Process gameProcess = Process.Start(address);
gameProcess.PriorityClass = processPriority; gameProcess.PriorityClass = processPriority;
return gameProcess; return gameProcess;
}*/
public override List<Process> StartGame(Game game, string gameArguments = "", ProcessPriority processPriority = ProcessPriority.Normal)
{
string address = $"origin2://game/launch?offerIds={game.Id}";
if (!String.IsNullOrWhiteSpace(gameArguments))
{
address += @"/" + gameArguments;
}
//Process gameProcess = Process.Start(address);
List<Process> gameProcesses = ProcessUtils.StartProcess(address, null, processPriority);
return gameProcesses;
} }

View File

@ -808,16 +808,16 @@ namespace DisplayMagician.GameLibraries
} }
public override Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal) public override List<Process> StartGame(Game game, string gameArguments = "", ProcessPriority processPriority = ProcessPriority.Normal)
{ {
string address = $@"steam://rungameid/{game.Id}"; string address = $@"steam://rungameid/{game.Id}";
if (!String.IsNullOrWhiteSpace(gameArguments)) if (!String.IsNullOrWhiteSpace(gameArguments))
{ {
address += @"//" + gameArguments; address += @"//" + gameArguments;
} }
Process gameProcess = Process.Start(address); //Process gameProcess = Process.Start(address);
gameProcess.PriorityClass = processPriority; List<Process> gameProcesses = ProcessUtils.StartProcess(address,null,processPriority);
return gameProcess; return gameProcesses;
} }
#endregion #endregion

View File

@ -887,7 +887,7 @@ namespace DisplayMagician.GameLibraries
return true; return true;
} }
public override Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal) /*public override Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal)
{ {
string address = $@"uplay://launch/{game.Id}"; string address = $@"uplay://launch/{game.Id}";
if (String.IsNullOrWhiteSpace(gameArguments)) if (String.IsNullOrWhiteSpace(gameArguments))
@ -902,6 +902,21 @@ namespace DisplayMagician.GameLibraries
gameProcess.PriorityClass = processPriority; gameProcess.PriorityClass = processPriority;
return gameProcess; return gameProcess;
}*/
public override List<Process> StartGame(Game game, string gameArguments = "", ProcessPriority processPriority = ProcessPriority.Normal)
{
string address = $@"uplay://launch/{game.Id}";
if (String.IsNullOrWhiteSpace(gameArguments))
{
address += @"/" + gameArguments;
}
else
{
address += "/0";
}
List<Process> gameProcesses = ProcessUtils.StartProcess(address, null, processPriority);
return gameProcesses;
} }
#endregion #endregion

View File

@ -1,7 +1,11 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Management;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace DisplayMagician namespace DisplayMagician
@ -9,8 +13,10 @@ namespace DisplayMagician
public class ProcessUtils public class ProcessUtils
{ {
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
[Flags] [Flags]
public enum PROCESS_CREATION_FLAGS : uint public enum PROCESS_CREATION_FLAGS : UInt32
{ {
ZERO_FLAG = 0x00000000, ZERO_FLAG = 0x00000000,
CREATE_BREAKAWAY_FROM_JOB = 0x01000000, CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
@ -181,6 +187,135 @@ namespace DisplayMagician
return wantedPriorityClass; return wantedPriorityClass;
} }
public static List<Process> StartProcess(string executable, string arguments, ProcessPriority processPriority, int startTimeout = 1)
{
List<Process> runningProcesses = new List<Process>();
Process process = null;
PROCESS_INFORMATION processInfo;
try
{
if (CreateProcessWithPriority(executable, arguments, ProcessUtils.TranslatePriorityToClass(processPriority), out processInfo))
{
if (processInfo.dwProcessId > 0)
{
process = Process.GetProcessById(processInfo.dwProcessId);
}
else
{
logger.Warn($"ProcessUtils/StartProcess: CreateProcessWithPriority returned a process with PID 0 when trying to start process {executable}. This indicates that the process was not started, so we'll try it a different way.");
// Start the process using built in process library
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = executable;
psi.Arguments = arguments;
psi.WorkingDirectory = Path.GetDirectoryName(executable);
process = Process.Start(psi);
processInfo.hProcess = process.Handle;
processInfo.dwProcessId = process.Id;
processInfo.dwThreadId = process.Threads[0].Id;
if (!process.HasExited)
{
// Change priority if we can (not always possible in this mode :(
try
{
// If this process is a protected process, then this will fail!
process.PriorityClass = ProcessUtils.TranslatePriorityToClass(processPriority);
}
catch (Exception ex)
{
// We would need need higher rights for this processto set the priority
// https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
// At this stage I am not writing this, as it is a lot of work for a niche issue.
}
runningProcesses.Add(process);
}
}
}
else
{
// Start the process using built in process library
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = executable;
psi.Arguments = arguments;
psi.WorkingDirectory = Path.GetDirectoryName(executable);
process = Process.Start(psi);
processInfo.hProcess = process.Handle;
processInfo.dwProcessId = process.Id;
if (!process.HasExited)
{
processInfo.dwThreadId = process.Threads[0].Id;
// Change priority if we can (not always possible in this mode :(
try
{
// If this process is a protected process, then this will fail!
process.PriorityClass = ProcessUtils.TranslatePriorityToClass(processPriority);
}
catch(Exception ex)
{
// We would need need higher rights for this processto set the priority
// https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights
// At this stage I am not writing this, as it is a lot of work for a niche issue.
}
runningProcesses.Add(process);
}
}
}
catch (Exception ex)
{
if (!process.HasExited)
{
// Start the process using built in process library
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = executable;
psi.Arguments = arguments;
psi.WorkingDirectory = Path.GetDirectoryName(executable);
process = Process.Start(psi);
processInfo.hProcess = process.Handle;
processInfo.dwProcessId = process.Id;
processInfo.dwThreadId = process.Threads[0].Id;
//pInfo.dwThreadId = process.Threads[0].Id;
// Change priority
if (!process.HasExited)
{
runningProcesses.Add(process);
}
}
}
// Check the launched exe hasn't exited within 2 secs
for (int secs = 0; secs <= (startTimeout * 1000); secs += 500)
{
// If we have no more processes left then we're done!
if (process.HasExited)
{
logger.Trace($"ProcessUtils/StartProcess: {executable} has exited early! It's likely to be a launcher! Trying to detect it's children.");
// As the original process has left the building, we'll overwrite it with the children processes
runningProcesses = GetChildProcesses(process);
break;
}
// Send a message to windows so that it doesn't think
// we're locked and try to kill us
System.Threading.Thread.CurrentThread.Join(0);
Thread.Sleep(500);
}
return runningProcesses;
}
public static List<Process> GetChildProcesses(Process process)
{
List<Process> children = new List<Process>();
ManagementObjectSearcher mos = new ManagementObjectSearcher($"Select * From Win32_Process Where ParentProcessID={process.Id}");
foreach (ManagementObject mo in mos.Get())
{
children.Add(Process.GetProcessById(Convert.ToInt32(mo["ProcessID"])));
}
return children;
}
public static bool CreateProcessWithPriority(string exeName, string cmdLine, ProcessPriorityClass priorityClass, out PROCESS_INFORMATION processInfo) public static bool CreateProcessWithPriority(string exeName, string cmdLine, ProcessPriorityClass priorityClass, out PROCESS_INFORMATION processInfo)
{ {
PROCESS_CREATION_FLAGS processFlags = TranslatePriorityClassToFlags(priorityClass); PROCESS_CREATION_FLAGS processFlags = TranslatePriorityClassToFlags(priorityClass);
@ -296,6 +431,135 @@ namespace DisplayMagician
} }
} }
public static bool ProcessExited(Process process)
{
try
{
Process processToTest = Process.GetProcessById(process.Id);
if (processToTest.HasExited)
{
logger.Trace($"ProcessUtils/ProcessExited: {process.Id} has exited and is not running. This means the process has finished!");
return true;
}
else
{
logger.Trace($"ProcessUtils/ProcessExited: {process.Id} is still running as is has not exited yet.");
return false;
}
}
catch (ArgumentException ex)
{
logger.Trace($"ProcessUtils/ProcessExited: {process.Id} is not running, and the process ID has expired. This means the process has finished!");
return true;
}
catch (InvalidOperationException ex)
{
logger.Warn($"ProcessUtils/ProcessExited: {process.Id} was not started by this process object. This likely means the process has finished!");
return true;
}
catch (Exception ex)
{
logger.Trace($"ProcessUtils/ProcessExited: Exception when checking if {process.Id} is still running, so assuming the process has finished!");
return true;
}
}
public static bool ProcessExited(List<Process> processes)
{
int processClosedCount = 0;
foreach (Process p in processes)
{
if (ProcessExited(p))
{
processClosedCount++;
}
}
if (processClosedCount == processes.Count)
{
logger.Trace($"ProcessUtils/ProcessExited2: All processes being monitored have exited, so no processes still running!");
return true;
}
else
{
logger.Trace($"ProcessUtils/ProcessExited2: {processClosedCount} processes out of {processes.Count} processes have exited. At least one process is still running!");
return false;
}
}
public static bool StopProcess(Process processToStop)
{
try
{
// Stop the process
processToStop.CloseMainWindow();
if (!processToStop.WaitForExit(5000))
{
logger.Trace($"ProcessUtils/StopProcess: Process {processToStop.StartInfo.FileName} wouldn't stop cleanly. Forcing program close.");
processToStop.Kill();
if (!processToStop.WaitForExit(5000))
{
logger.Error($"ProcessUtils/StopProcess: Process {processToStop.StartInfo.FileName} couldn't be killed! It seems like something is actively preventing us from stopping the process");
return false;
}
logger.Trace($"ProcessUtils/StopProcess: Process {processToStop.StartInfo.FileName} was successfully killed.");
}
processToStop.Close();
return true;
}
catch (Win32Exception ex)
{
logger.Warn(ex, $"ProcessUtils/StopProcess: Win32Exception! Couldn't access the wait status for a named process we're trying to stop. So now just killing the process.");
processToStop.Kill();
if (!processToStop.WaitForExit(5000))
{
logger.Error($"ProcessUtils/StopProcess: Win32Exception! Process {processToStop.StartInfo.FileName} couldn't be killed! It seems like something is actively preventing us from stopping the process");
return false;
}
logger.Trace($"ProcessUtils/StopProcess: Win32Exception! Process {processToStop.StartInfo.FileName} was successfully killed.");
processToStop.Close();
return true;
}
catch (InvalidOperationException ex)
{
logger.Error(ex, $"ProcessUtils/StopProcess: Couldn't kill the named process as the process appears to have closed already.");
}
catch (SystemException ex)
{
logger.Error(ex, $"ProcessUtils/StopProcess: Couldn't WaitForExit the named process as there is no process associated with the Process object (or cannot get the ID from the named process handle).");
}
catch (AggregateException ae)
{
logger.Error(ae, $"ProcessUtils/StopProcess: Got an AggregateException.");
}
return false;
}
public static bool StopProcess(List<Process> processes)
{
// Stop the programs in the reverse order we started them
foreach (Process processToStop in processes)
{
// Stop the process if it hasn't stopped already
try
{
if (!processToStop.HasExited)
{
logger.Debug($"ShortcutRepository/RunShortcut: Stopping process {processToStop.StartInfo.FileName}");
if (ProcessUtils.StopProcess(processToStop))
{
logger.Debug($"ShortcutRepository/RunShortcut: Successfully stopped process {processToStop.StartInfo.FileName}");
}
}
}
catch (Exception ex)
{
logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception while checking if processToStop has already exited");
}
}
return true;
}
} }
} }

View File

@ -26,8 +26,8 @@ using System.Resources;
[assembly: Guid("e4ceaf5e-ad01-4695-b179-31168eb74c48")] [assembly: Guid("e4ceaf5e-ad01-4695-b179-31168eb74c48")]
// Version information // Version information
[assembly: AssemblyVersion("2.1.0.174")] [assembly: AssemblyVersion("2.1.0.179")]
[assembly: AssemblyFileVersion("2.1.0.174")] [assembly: AssemblyFileVersion("2.1.0.179")]
[assembly: NeutralResourcesLanguageAttribute( "en" )] [assembly: NeutralResourcesLanguageAttribute( "en" )]
[assembly: CLSCompliant(true)] [assembly: CLSCompliant(true)]

View File

@ -892,72 +892,27 @@ namespace DisplayMagician
// Start the executable // Start the executable
logger.Info($"ShortcutRepository/RunShortcut: Starting Start Program process {processToStart.Executable}"); logger.Info($"ShortcutRepository/RunShortcut: Starting Start Program process {processToStart.Executable}");
Process process = null; //Process process = null;
List<Process> processesCreated = new List<Process>();
try try
{ {
//ProcessUtils.ScanProcesses(); processesCreated = ProcessUtils.StartProcess(processToStart.Executable, processToStart.Arguments, processToStart.ProcessPriority);
ProcessUtils.PROCESS_INFORMATION processInfo;
if (ProcessUtils.CreateProcessWithPriority(processToStart.Executable, processToStart.Arguments, ProcessUtils.TranslatePriorityToClass(processToStart.ProcessPriority), out processInfo))
{
if (processInfo.dwProcessId > 0)
{
process = Process.GetProcessById(processInfo.dwProcessId);
}
else
{
logger.Warn($"ShortcutRepository/RunShortcut: CreateProcessWithPriority returned a process with PID 0 when trying to start process {processToStart.Executable}. This indicates that the process was not started.");
}
}
else
{
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = processToStart.Executable;
psi.Arguments = processToStart.Arguments;
psi.WorkingDirectory = Path.GetDirectoryName(processToStart.Executable);
process = Process.Start(psi);
processInfo.hProcess = process.Handle;
processInfo.dwProcessId = process.Id;
processInfo.dwThreadId = process.Threads[0].Id;
//pInfo.dwThreadId = process.Threads[0].Id;
Task.Delay(500);
if (!process.HasExited)
{
process.PriorityClass = ProcessUtils.TranslatePriorityToClass(processToStart.ProcessPriority);
}
}
/*if (processToStart.ExecutableArgumentsRequired)
{
process = System.Diagnostics.Process.Start(processToStart.Executable, processToStart.Arguments);
}
else
{
process = System.Diagnostics.Process.Start(processToStart.Executable);
}*/
/*try
{
// Attempt to set the process priority to whatever the user wanted
logger.Trace($"ShortcutRepository/RunShortcut: Setting the start program process priority of start program we started to {shortcutToUse.ProcessPriority.ToString("G")}");
process.PriorityClass = TranslatePriorityClass(processToStart.ProcessPriority);
}
catch (Exception ex)
{
logger.Warn(ex, $"ShortcutRepository/RunShortcut: Exception setting the start program process priority of start program we started to {shortcutToUse.ProcessPriority.ToString("G")}");
}*/
// Record the program we started so we can close it later // Record the program we started so we can close it later
if (processToStart.CloseOnFinish) if (processToStart.CloseOnFinish)
{ {
logger.Debug($"ShortcutRepository/RunShortcut: We need to stop {processToStart.Executable} after the main game or executable is closed."); foreach (Process p in processesCreated)
startProgramsToStop.Add(process); {
logger.Debug($"ShortcutRepository/RunShortcut: We need to stop {p.StartInfo.FileName} after the main game or executable is closed.");
}
startProgramsToStop.AddRange(processesCreated);
} }
else else
{ {
logger.Debug($"ShortcutRepository/RunShortcut: No need to stop {processToStart.Executable} after the main game or executable is closed, so we'll just leave it running"); foreach (Process p in processesCreated)
{
logger.Debug($"ShortcutRepository/RunShortcut: No need to stop {p.StartInfo.FileName} after the main game or executable is closed, so we'll just leave it running");
}
} }
} }
catch (Win32Exception ex) catch (Win32Exception ex)
@ -1043,44 +998,65 @@ namespace DisplayMagician
} }
// Now start the main game, and wait if we have to // Now start the main game/exe, and wait if we have to
if (shortcutToUse.Category.Equals(ShortcutCategory.Application)) if (shortcutToUse.Category.Equals(ShortcutCategory.Application))
{ {
logger.Info($"ShortcutRepository/RunShortcut: Starting the main executable that we wanted to run, and that we're going to monitor and watch"); // Store the process to monitor for later
// Start the executable //IPCService.GetInstance().HoldProcessId = processesToMonitor.FirstOrDefault()?.Id ?? 0;
//IPCService.GetInstance().Status = InstanceStatus.OnHold;
// Add a status notification icon in the status area
string notificationText = $"DisplayMagician: Running {shortcutToUse.ExecutableNameAndPath}...";
if (notificationText.Length >= 64)
{
string thingToRun = shortcutToUse.ExecutableNameAndPath.Substring(0, 34);
notifyIcon.Text = $"DisplayMagician: Running {thingToRun}...";
}
Application.DoEvents();
string processToMonitorName;
if (shortcutToUse.ProcessNameToMonitorUsesExecutable)
{
processToMonitorName = shortcutToUse.ExecutableNameAndPath;
}
else
{
processToMonitorName = shortcutToUse.DifferentExecutableToMonitor;
}
logger.Debug($"ShortcutRepository/RunShortcut: Creating the Windows Toast to notify the user we're going to wait for the executable {shortcutToUse.ExecutableNameAndPath} to close.");
// Now we want to tell the user we're running an application!
// Construct the Windows toast content
ToastContentBuilder tcBuilder = new ToastContentBuilder()
.AddToastActivationInfo("notify=runningApplication", ToastActivationType.Foreground)
.AddText($"Running {shortcutToUse.ExecutableNameAndPath}", hintMaxLines: 1)
.AddText($"Waiting for all {processToMonitorName } processes to exit...")
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
//.AddButton("Stop", ToastActivationType.Background, "notify=runningGame&action=stop");
ToastContent toastContent = tcBuilder.Content;
// Make sure to use Windows.Data.Xml.Dom
var doc = new XmlDocument();
doc.LoadXml(toastContent.GetContent());
// And create the toast notification
var toast = new ToastNotification(doc);
toast.SuppressPopup = false;
// Remove any other Notifications from us
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
// And then show this notification
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
logger.Info($"ShortcutRepository/RunShortcut: Starting the main executable that we wanted to run, and that we're going to monitor and watch");
// Start the main executable
List<Process> processesCreated = new List<Process>();
try try
{ {
Process process = null; processesCreated = ProcessUtils.StartProcess(shortcutToUse.ExecutableNameAndPath, shortcutToUse.ExecutableArguments, shortcutToUse.ProcessPriority);
ProcessUtils.PROCESS_INFORMATION processInfo;
if (ProcessUtils.CreateProcessWithPriority(shortcutToUse.ExecutableNameAndPath, shortcutToUse.ExecutableArguments, ProcessUtils.TranslatePriorityToClass(shortcutToUse.ProcessPriority), out processInfo)) // Record the program we started so we can close it later
foreach (Process p in processesCreated)
{ {
process = Process.GetProcessById(processInfo.dwProcessId); logger.Debug($"ShortcutRepository/RunShortcut: {p.StartInfo.FileName} was launched when we started the main application {shortcutToUse.ExecutableNameAndPath}.");
Task.Delay(500);
if (process != null)
{
if (process.HasExited)
{
// Then we need to find what processes are running now with a parent of processInfo.process
logger.Error($"ShortcutRepository/RunShortcut: Main executable process {shortcutToUse.ExecutableNameAndPath} has exited after a short delay. It is likely to be a launcher process. We're going to look for it's children.");
}
}
else
{
logger.Error($"ShortcutRepository/RunShortcut: Main executable process {shortcutToUse.ExecutableNameAndPath} didn't start for some reason. Process still = null.");
}
}
else
{
logger.Error($"ShortcutRepository/RunShortcut: CreateProcessWithPriority couldn't create Main executable process {shortcutToUse.ExecutableNameAndPath}. Going to try to start it the old way without priority.");
if (shortcutToUse.ExecutableArgumentsRequired)
{
process = Process.Start(shortcutToUse.ExecutableNameAndPath, shortcutToUse.ExecutableArguments);
}
else
{
process = Process.Start(shortcutToUse.ExecutableNameAndPath);
}
} }
} }
@ -1101,110 +1077,44 @@ namespace DisplayMagician
logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception starting main executable process {shortcutToUse.ExecutableNameAndPath}. Method call is invalid for the current state."); logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception starting main executable process {shortcutToUse.ExecutableNameAndPath}. Method call is invalid for the current state.");
} }
// Figure out what we want to look for // Wait an extra few seconds to give the application time to settle down
string processNameToLookFor; //Thread.Sleep(2000);
// Now we need to decide what we are monitoring. If the user has supplied an alternative process to monitor, then we monitor that instead!
bool foundSomethingToMonitor = false;
List<Process> processesToMonitor = new List<Process>();
if (shortcutToUse.ProcessNameToMonitorUsesExecutable) if (shortcutToUse.ProcessNameToMonitorUsesExecutable)
{ {
// If we are monitoring the same executable we started, then lets do get that name ready processesToMonitor = processesCreated;
processNameToLookFor = System.IO.Path.GetFileNameWithoutExtension(shortcutToUse.ExecutableNameAndPath); logger.Debug($"ShortcutRepository/RunShortcut: {processesToMonitor.Count} '{processToMonitorName}' created processes to monitor are running");
foundSomethingToMonitor = true;
} }
else else
{ {
// If we are monitoring a different executable, then lets do get that name ready instead // We use the a user supplied executable as the thing we're monitoring instead!
processNameToLookFor = System.IO.Path.GetFileNameWithoutExtension(shortcutToUse.DifferentExecutableToMonitor);
}
logger.Debug($"ShortcutRepository/RunShortcut: Looking for processes with the name {processNameToLookFor} so that we can monitor them and know when they are closed.");
// Now look for the thing we're supposed to monitor
// and wait until it starts up
List<Process> processesToMonitor = new List<Process>();
for (int secs = 0; secs <= (shortcutToUse.StartTimeout * 1000); secs += 500)
{
// Look for the processes with the ProcessName we sorted out earlier
processesToMonitor = Process.GetProcessesByName(processNameToLookFor).ToList();
// If we have found one or more processes then we should be good to go
// so let's break
if (processesToMonitor.Count > 0)
{
logger.Debug($"ShortcutRepository/RunShortcut: Found {processesToMonitor.Count} '{processNameToLookFor}' processes to monitor");
try try
{ {
foreach (Process monitoredProcess in processesToMonitor) processesToMonitor.AddRange(Process.GetProcessesByName(shortcutToUse.DifferentExecutableToMonitor));
logger.Trace($"ShortcutRepository/RunShortcut: {processesToMonitor.Count} '{shortcutToUse.DifferentExecutableToMonitor}' user specified processes to monitor are running");
foundSomethingToMonitor = true;
}
catch (Exception ex)
{ {
logger.Trace($"ShortcutRepository/RunShortcut: Setting priority of monitored executable process {processNameToLookFor} to {shortcutToUse.ProcessPriority.ToString("G")}"); logger.Error($"ShortcutRepository/RunShortcut: Exception while trying to find the user supplied executable to monitor: {shortcutToUse.DifferentExecutableToMonitor}.");
monitoredProcess.PriorityClass = TranslatePriorityClassToClass(shortcutToUse.ProcessPriority); foundSomethingToMonitor = false;
} }
} }
catch(Exception ex)
{
logger.Warn(ex, $"ShortcutRepository/RunShortcut: Exception Setting priority of monitored executable process {processNameToLookFor} to {shortcutToUse.ProcessPriority.ToString("G")}");
}
break;
}
// Let's wait a little while if we couldn't find
// any processes yet
Thread.Sleep(500);
}
// make sure we have things to monitor and alert if not
if (processesToMonitor.Count == 0)
{
logger.Error($"ShortcutRepository/RunShortcut: No '{processNameToLookFor}' processes found before waiting timeout. DisplayMagician was unable to find any processes before the {shortcutToUse.StartTimeout} second timeout");
}
// Store the process to monitor for later
//IPCService.GetInstance().HoldProcessId = processesToMonitor.FirstOrDefault()?.Id ?? 0;
//IPCService.GetInstance().Status = InstanceStatus.OnHold;
// Add a status notification icon in the status area
string notificationText = $"DisplayMagician: Running {shortcutToUse.ExecutableNameAndPath}...";
if (notificationText.Length >= 64)
{
string thingToRun = shortcutToUse.ExecutableNameAndPath.Substring(0, 34);
notifyIcon.Text = $"DisplayMagician: Running {thingToRun}...";
}
Application.DoEvents();
logger.Debug($"ShortcutRepository/RunShortcut: Creating the Windows Toast to notify the user we're going to wait for the executable {shortcutToUse.ExecutableNameAndPath} to close.");
// Now we want to tell the user we're running an application!
// Construct the Windows toast content
ToastContentBuilder tcBuilder = new ToastContentBuilder()
.AddToastActivationInfo("notify=runningApplication", ToastActivationType.Foreground)
.AddText($"Running {processNameToLookFor}", hintMaxLines: 1)
.AddText($"Waiting for all {processNameToLookFor} windows to exit...")
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"),false,true);
//.AddButton("Stop", ToastActivationType.Background, "notify=runningGame&action=stop");
ToastContent toastContent = tcBuilder.Content;
// Make sure to use Windows.Data.Xml.Dom
var doc = new XmlDocument();
doc.LoadXml(toastContent.GetContent());
// And create the toast notification
var toast = new ToastNotification(doc);
toast.SuppressPopup = false;
// Remove any other Notifications from us
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
// And then show this notification
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
// Wait an extra few seconds to give the application time to settle down
Thread.Sleep(2000);
// if we have things to monitor, then we should start to wait for them // if we have things to monitor, then we should start to wait for them
logger.Debug($"ShortcutRepository/RunShortcut: Waiting for application {processNameToLookFor} to exit."); logger.Debug($"ShortcutRepository/RunShortcut: Waiting for application {shortcutToUse.ExecutableNameAndPath} to exit.");
if (processesToMonitor.Count > 0) if (foundSomethingToMonitor && processesToMonitor.Count > 0)
{ {
logger.Debug($"ShortcutRepository/RunShortcut: {processesToMonitor.Count} '{processNameToLookFor}' processes are still running");
while (true) while (true)
{ {
processesToMonitor = Process.GetProcessesByName(processNameToLookFor).ToList();
// If we have no more processes left then we're done! // If we have no more processes left then we're done!
if (processesToMonitor.Count == 0) if (ProcessUtils.ProcessExited(processesToMonitor))
{ {
logger.Debug($"ShortcutRepository/RunShortcut: No more '{processNameToLookFor}' processes are still running"); logger.Debug($"ShortcutRepository/RunShortcut: No more processes to monitor are still running. It, and all it's child processes have exited!");
break; break;
} }
@ -1214,15 +1124,14 @@ namespace DisplayMagician
Thread.Sleep(1000); Thread.Sleep(1000);
} }
} }
logger.Info($"ShortcutRepository/RunShortcut: Executable {processNameToLookFor} has exited.");
logger.Debug($"ShortcutRepository/RunShortcut: Creating a Windows Toast to notify the user that the executable {shortcutToUse.ExecutableNameAndPath} has closed."); logger.Debug($"ShortcutRepository/RunShortcut: Creating a Windows Toast to notify the user that the executable {shortcutToUse.ExecutableNameAndPath} has closed.");
// Tell the user that the application has closed // Tell the user that the application has closed
// Construct the toast content // Construct the toast content
tcBuilder = new ToastContentBuilder() tcBuilder = new ToastContentBuilder()
.AddToastActivationInfo("notify=stopDetected", ToastActivationType.Foreground) .AddToastActivationInfo("notify=stopDetected", ToastActivationType.Foreground)
.AddText($"{processNameToLookFor} was closed", hintMaxLines: 1) .AddText($"{shortcutToUse.ExecutableNameAndPath} was closed", hintMaxLines: 1)
.AddText($"All {processNameToLookFor} processes were shutdown and changes were reverted.") .AddText($"All {processToMonitorName} processes were shutdown and changes were reverted.")
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true); .AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
toastContent = tcBuilder.Content; toastContent = tcBuilder.Content;
// Make sure to use Windows.Data.Xml.Dom // Make sure to use Windows.Data.Xml.Dom
@ -1279,6 +1188,25 @@ namespace DisplayMagician
if (gameToRun != null) if (gameToRun != null)
{ {
string processToMonitorName;
if (shortcutToUse.MonitorDifferentGameExe)
{
processToMonitorName = shortcutToUse.DifferentGameExeToMonitor;
}
else
{
processToMonitorName = gameToRun.ExePath;
}
// Add a status notification icon in the status area
string notificationText = $"DisplayMagician: Running {gameLibraryToUse.GameLibraryName}...";
if (notificationText.Length >= 64)
{
string thingToRun = gameLibraryToUse.GameLibraryName.Substring(0, 34);
notifyIcon.Text = $"DisplayMagician: Running {thingToRun}...";
}
Application.DoEvents();
// Now we want to tell the user we're start a game // Now we want to tell the user we're start a game
// Construct the Windows toast content // Construct the Windows toast content
ToastContentBuilder tcBuilder = new ToastContentBuilder() ToastContentBuilder tcBuilder = new ToastContentBuilder()
@ -1298,10 +1226,8 @@ namespace DisplayMagician
// And then show this notification // And then show this notification
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast); DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
Process gameProcess; List<Process> gameProcesses;
//string gameRunCmd = gameLibraryToUse.GetRunCmd(gameToRun, shortcutToUse.GameArguments); gameProcesses = gameLibraryToUse.StartGame(gameToRun, shortcutToUse.GameArguments, shortcutToUse.ProcessPriority);
//gameProcess = Process.Start(gameRunCmd);
gameProcess = gameLibraryToUse.StartGame(gameToRun, shortcutToUse.GameArguments, ProcessUtils.TranslatePriorityToClass(shortcutToUse.ProcessPriority));
// Delay 500ms // Delay 500ms
Thread.Sleep(500); Thread.Sleep(500);
@ -1416,7 +1342,9 @@ namespace DisplayMagician
} }
string notificationText = $"DisplayMagician: Running {gameToRun.Name}..."; // Now we actually start looking for and monitoring the game!
notificationText = $"DisplayMagician: Running {gameToRun.Name}...";
if (notificationText.Length >= 64) if (notificationText.Length >= 64)
{ {
string thingToRun = gameToRun.Name.Substring(0, 34); string thingToRun = gameToRun.Name.Substring(0, 34);
@ -1457,7 +1385,6 @@ namespace DisplayMagician
{ {
logger.Warn(ex, $"ShortcutRepository/RunShortcut: Setting priority of alternative game monitored process {altGameProcessToMonitor} to {shortcutToUse.ProcessPriority.ToString("G")}"); logger.Warn(ex, $"ShortcutRepository/RunShortcut: Setting priority of alternative game monitored process {altGameProcessToMonitor} to {shortcutToUse.ProcessPriority.ToString("G")}");
} }
break; break;
} }
@ -1852,96 +1779,8 @@ namespace DisplayMagician
{ {
logger.Debug($"ShortcutRepository/RunShortcut: We started {startProgramsToStart.Count} programs before the main executable or game, and now we want to stop {startProgramsToStop.Count } of them"); logger.Debug($"ShortcutRepository/RunShortcut: We started {startProgramsToStart.Count} programs before the main executable or game, and now we want to stop {startProgramsToStop.Count } of them");
// Prepare the processInfos we need for finding child processes. // Shutdown the processes
//ProcessUtils.ScanProcesses(); ProcessUtils.StopProcess(startProgramsToStop);
// Stop the programs in the reverse order we started them
foreach (Process processToStop in startProgramsToStop.Reverse<Process>())
{
bool stoppedMainProcess = false;
// Stop the process if it hasn't stopped already
try
{
if (!processToStop.HasExited)
{
logger.Debug($"ShortcutRepository/RunShortcut: Stopping process {processToStop.StartInfo.FileName}");
/*if (ProcessUtils.StopProcess(processToStop))
{
logger.Debug($"ShortcutRepository/RunShortcut: Successfully stopped process {processToStop.StartInfo.FileName}");
stoppedMainProcess = true;
}*/
}
}
catch (Exception ex)
{
logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception while checking if processToStop has already exited");
}
// Next, check whether it had any other processes it started itself
// (copes with loader processes that perform the initial start, then run the main exe)
// If so, we need to go through and find and close all subprocesses
/*try
{
List<Process> childProcesses = ProcessUtils.FindChildProcesses(processToStop);
if (childProcesses.Count > 0)
{
foreach (Process childProcessToStop in childProcesses)
{
if (processToStop.HasExited)
{
// if there were no child processes, and the only process has already exited (e.g. the user exited it themselves)
// then stop trying to stop the process, and instead log the fact it already stopped.
Console.WriteLine($"Stopping child process {childProcessToStop.StartInfo.FileName} but was already stopped by user or another process.");
logger.Warn($"ShortcutRepository/RunShortcut: Stopping child process {childProcessToStop.StartInfo.FileName} but was already stopped by user or another process.");
continue;
}
Console.WriteLine($"Stopping child process {childProcessToStop.StartInfo.FileName} of parent process {processToStop.StartInfo.FileName}");
logger.Debug($"ShortcutRepository/RunShortcut: Stopping child process {childProcessToStop.StartInfo.FileName} of parent process {processToStop.StartInfo.FileName}");
ProcessUtils.StopProcess(childProcessToStop);
}
}
}
catch (Exception ex)
{
logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception while checking if processToStop has any child processes");
}*/
// if the only main process has already exited (e.g. the user exited it themselves)
// then we try to stop any processes with the same name as the application we started
// Look for the processes with the ProcessName we sorted out earlier
// Basically, if we haven't stopped all the children processes, then this is the last gasp
try
{
if (!stoppedMainProcess)
{
string processName = Path.GetFileNameWithoutExtension(processToStop.StartInfo.FileName);
List<Process> namedProcessesToStop = Process.GetProcessesByName(processName).ToList();
// If we have found one or more processes then we should be good to go
if (namedProcessesToStop.Count > 0)
{
/*logger.Warn($"ShortcutRepository/RunShortcut: We couldn't find any children processes so we've looked for named processes with the name '{processToStop.StartInfo.FileName}' and we found {namedProcessesToStop.Count}. Closing them.");
foreach (Process namedProcessToStop in namedProcessesToStop)
{
ProcessUtils.StopProcess(namedProcessToStop);
}*/
}
else
{
// then give up trying to stop the process, and instead log the fact it already stopped.
Console.WriteLine($"Stopping only process {processToStop.StartInfo.FileName} but was already stopped by user or another process.");
logger.Debug($"ShortcutRepository/RunShortcut: Stopping only process {processToStop.StartInfo.FileName} but was already stopped by user or another process.");
}
continue;
}
}
catch (Exception ex)
{
logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception while looking for other processes similar to processToStop for us to stop.");
}
}
} }
// Change Audio Device back (if one specified) // Change Audio Device back (if one specified)