diff --git a/DisplayMagician/GameLibraries/EpicLibrary.cs b/DisplayMagician/GameLibraries/EpicLibrary.cs index a60f3de..10ce625 100644 --- a/DisplayMagician/GameLibraries/EpicLibrary.cs +++ b/DisplayMagician/GameLibraries/EpicLibrary.cs @@ -556,7 +556,7 @@ namespace DisplayMagician.GameLibraries 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"; if (String.IsNullOrWhiteSpace(gameArguments)) @@ -566,7 +566,18 @@ namespace DisplayMagician.GameLibraries Process gameProcess = Process.Start(address); gameProcess.PriorityClass = processPriority; return gameProcess; + }*/ + public override List 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 gameProcesses = ProcessUtils.StartProcess(address, null, processPriority); + return gameProcesses; } #endregion diff --git a/DisplayMagician/GameLibraries/GOGLibrary.cs b/DisplayMagician/GameLibraries/GOGLibrary.cs index 12fb8e6..8bc8bd9 100644 --- a/DisplayMagician/GameLibraries/GOGLibrary.cs +++ b/DisplayMagician/GameLibraries/GOGLibrary.cs @@ -552,21 +552,18 @@ namespace DisplayMagician.GameLibraries } - public override Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal) + public override List StartGame(Game game, string gameArguments = "", ProcessPriority processPriority = ProcessPriority.Normal) { string args = $@"/command=runGame /gameId={game.Id} /path=""{game.Directory}"""; if (String.IsNullOrWhiteSpace(gameArguments)) { args += gameArguments; } - Process gameProcess = null; - ProcessUtils.PROCESS_INFORMATION processInfo; - if (ProcessUtils.CreateProcessWithPriority(_gogExe, args, processPriority, out processInfo)) - { - gameProcess = Process.GetProcessById(processInfo.dwProcessId); - } - return gameProcess; + List gameProcesses = ProcessUtils.StartProcess(_gogExe, args, processPriority); + return gameProcesses; } + + #endregion } diff --git a/DisplayMagician/GameLibraries/GameLibrary.cs b/DisplayMagician/GameLibraries/GameLibrary.cs index 6de160e..9db6a81 100644 --- a/DisplayMagician/GameLibraries/GameLibrary.cs +++ b/DisplayMagician/GameLibraries/GameLibrary.cs @@ -108,7 +108,7 @@ namespace DisplayMagician.GameLibraries return false; } - public virtual Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal) + public virtual List StartGame(Game game, string gameArguments = "", ProcessPriority processPriority = ProcessPriority.Normal) { return null; } diff --git a/DisplayMagician/GameLibraries/OriginLibrary.cs b/DisplayMagician/GameLibraries/OriginLibrary.cs index 631b765..d729cff 100644 --- a/DisplayMagician/GameLibraries/OriginLibrary.cs +++ b/DisplayMagician/GameLibraries/OriginLibrary.cs @@ -725,7 +725,7 @@ namespace DisplayMagician.GameLibraries 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}"; if (String.IsNullOrWhiteSpace(gameArguments)) @@ -735,6 +735,17 @@ namespace DisplayMagician.GameLibraries Process gameProcess = Process.Start(address); gameProcess.PriorityClass = processPriority; return gameProcess; + }*/ + public override List 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 gameProcesses = ProcessUtils.StartProcess(address, null, processPriority); + return gameProcesses; } diff --git a/DisplayMagician/GameLibraries/SteamLibrary.cs b/DisplayMagician/GameLibraries/SteamLibrary.cs index 92e1d0b..45efef9 100644 --- a/DisplayMagician/GameLibraries/SteamLibrary.cs +++ b/DisplayMagician/GameLibraries/SteamLibrary.cs @@ -808,16 +808,16 @@ namespace DisplayMagician.GameLibraries } - public override Process StartGame(Game game, string gameArguments = "", ProcessPriorityClass processPriority = ProcessPriorityClass.Normal) + public override List StartGame(Game game, string gameArguments = "", ProcessPriority processPriority = ProcessPriority.Normal) { string address = $@"steam://rungameid/{game.Id}"; if (!String.IsNullOrWhiteSpace(gameArguments)) { address += @"//" + gameArguments; } - Process gameProcess = Process.Start(address); - gameProcess.PriorityClass = processPriority; - return gameProcess; + //Process gameProcess = Process.Start(address); + List gameProcesses = ProcessUtils.StartProcess(address,null,processPriority); + return gameProcesses; } #endregion diff --git a/DisplayMagician/GameLibraries/UplayLibrary.cs b/DisplayMagician/GameLibraries/UplayLibrary.cs index c3135d8..5be1a70 100644 --- a/DisplayMagician/GameLibraries/UplayLibrary.cs +++ b/DisplayMagician/GameLibraries/UplayLibrary.cs @@ -887,7 +887,7 @@ namespace DisplayMagician.GameLibraries 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}"; if (String.IsNullOrWhiteSpace(gameArguments)) @@ -902,6 +902,21 @@ namespace DisplayMagician.GameLibraries gameProcess.PriorityClass = processPriority; return gameProcess; + }*/ + + public override List 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 gameProcesses = ProcessUtils.StartProcess(address, null, processPriority); + return gameProcesses; } #endregion diff --git a/DisplayMagician/ProcessUtils.cs b/DisplayMagician/ProcessUtils.cs index 6a09a55..a055651 100644 --- a/DisplayMagician/ProcessUtils.cs +++ b/DisplayMagician/ProcessUtils.cs @@ -1,7 +1,11 @@ using System; +using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Management; using System.Runtime.InteropServices; +using System.Threading; using System.Threading.Tasks; namespace DisplayMagician @@ -9,8 +13,10 @@ namespace DisplayMagician public class ProcessUtils { + private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + [Flags] - public enum PROCESS_CREATION_FLAGS : uint + public enum PROCESS_CREATION_FLAGS : UInt32 { ZERO_FLAG = 0x00000000, CREATE_BREAKAWAY_FROM_JOB = 0x01000000, @@ -181,6 +187,135 @@ namespace DisplayMagician return wantedPriorityClass; } + public static List StartProcess(string executable, string arguments, ProcessPriority processPriority, int startTimeout = 1) + { + List runningProcesses = new List(); + 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 GetChildProcesses(Process process) + { + List children = new List(); + 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) { 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 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 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; + } } } diff --git a/DisplayMagician/Properties/AssemblyInfo.cs b/DisplayMagician/Properties/AssemblyInfo.cs index e4457d3..74a7823 100644 --- a/DisplayMagician/Properties/AssemblyInfo.cs +++ b/DisplayMagician/Properties/AssemblyInfo.cs @@ -26,8 +26,8 @@ using System.Resources; [assembly: Guid("e4ceaf5e-ad01-4695-b179-31168eb74c48")] // Version information -[assembly: AssemblyVersion("2.1.0.174")] -[assembly: AssemblyFileVersion("2.1.0.174")] +[assembly: AssemblyVersion("2.1.0.179")] +[assembly: AssemblyFileVersion("2.1.0.179")] [assembly: NeutralResourcesLanguageAttribute( "en" )] [assembly: CLSCompliant(true)] diff --git a/DisplayMagician/ShortcutRepository.cs b/DisplayMagician/ShortcutRepository.cs index 3da3cfa..a28cf90 100644 --- a/DisplayMagician/ShortcutRepository.cs +++ b/DisplayMagician/ShortcutRepository.cs @@ -892,72 +892,27 @@ namespace DisplayMagician // Start the executable logger.Info($"ShortcutRepository/RunShortcut: Starting Start Program process {processToStart.Executable}"); - Process process = null; + //Process process = null; + List processesCreated = new List(); try { - //ProcessUtils.ScanProcesses(); - 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")}"); - }*/ - + processesCreated = ProcessUtils.StartProcess(processToStart.Executable, processToStart.Arguments, processToStart.ProcessPriority); // Record the program we started so we can close it later if (processToStart.CloseOnFinish) { - logger.Debug($"ShortcutRepository/RunShortcut: We need to stop {processToStart.Executable} after the main game or executable is closed."); - startProgramsToStop.Add(process); + foreach (Process p in processesCreated) + { + logger.Debug($"ShortcutRepository/RunShortcut: We need to stop {p.StartInfo.FileName} after the main game or executable is closed."); + } + startProgramsToStop.AddRange(processesCreated); } 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) @@ -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)) { - logger.Info($"ShortcutRepository/RunShortcut: Starting the main executable that we wanted to run, and that we're going to monitor and watch"); - // Start the executable + // 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(); + + 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 processesCreated = new List(); try { - Process process = null; - ProcessUtils.PROCESS_INFORMATION processInfo; - if (ProcessUtils.CreateProcessWithPriority(shortcutToUse.ExecutableNameAndPath, shortcutToUse.ExecutableArguments, ProcessUtils.TranslatePriorityToClass(shortcutToUse.ProcessPriority), out processInfo)) + processesCreated = ProcessUtils.StartProcess(shortcutToUse.ExecutableNameAndPath, shortcutToUse.ExecutableArguments, shortcutToUse.ProcessPriority); + + // Record the program we started so we can close it later + foreach (Process p in processesCreated) { - process = Process.GetProcessById(processInfo.dwProcessId); - 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); - } + logger.Debug($"ShortcutRepository/RunShortcut: {p.StartInfo.FileName} was launched when we started the main application {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."); } - // Figure out what we want to look for - string processNameToLookFor; + // Wait an extra few seconds to give the application time to settle down + //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 processesToMonitor = new List(); if (shortcutToUse.ProcessNameToMonitorUsesExecutable) { - // If we are monitoring the same executable we started, then lets do get that name ready - processNameToLookFor = System.IO.Path.GetFileNameWithoutExtension(shortcutToUse.ExecutableNameAndPath); + processesToMonitor = processesCreated; + logger.Debug($"ShortcutRepository/RunShortcut: {processesToMonitor.Count} '{processToMonitorName}' created processes to monitor are running"); + foundSomethingToMonitor = true; } else { - // If we are monitoring a different executable, then lets do get that name ready 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 processesToMonitor = new List(); - 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) + // We use the a user supplied executable as the thing we're monitoring instead! + try { - logger.Debug($"ShortcutRepository/RunShortcut: Found {processesToMonitor.Count} '{processNameToLookFor}' processes to monitor"); - - try - { - foreach (Process monitoredProcess in processesToMonitor) - { - logger.Trace($"ShortcutRepository/RunShortcut: Setting priority of monitored executable process {processNameToLookFor} to {shortcutToUse.ProcessPriority.ToString("G")}"); - monitoredProcess.PriorityClass = TranslatePriorityClassToClass(shortcutToUse.ProcessPriority); - } - } - catch(Exception ex) - { - logger.Warn(ex, $"ShortcutRepository/RunShortcut: Exception Setting priority of monitored executable process {processNameToLookFor} to {shortcutToUse.ProcessPriority.ToString("G")}"); - } - - break; + 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.Error($"ShortcutRepository/RunShortcut: Exception while trying to find the user supplied executable to monitor: {shortcutToUse.DifferentExecutableToMonitor}."); + foundSomethingToMonitor = false; } - - // 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 - logger.Debug($"ShortcutRepository/RunShortcut: Waiting for application {processNameToLookFor} to exit."); - if (processesToMonitor.Count > 0) - { - logger.Debug($"ShortcutRepository/RunShortcut: {processesToMonitor.Count} '{processNameToLookFor}' processes are still running"); + logger.Debug($"ShortcutRepository/RunShortcut: Waiting for application {shortcutToUse.ExecutableNameAndPath} to exit."); + if (foundSomethingToMonitor && processesToMonitor.Count > 0) + { while (true) { - processesToMonitor = Process.GetProcessesByName(processNameToLookFor).ToList(); - // 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; } @@ -1214,15 +1124,14 @@ namespace DisplayMagician 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."); // Tell the user that the application has closed // Construct the toast content tcBuilder = new ToastContentBuilder() .AddToastActivationInfo("notify=stopDetected", ToastActivationType.Foreground) - .AddText($"{processNameToLookFor} was closed", hintMaxLines: 1) - .AddText($"All {processNameToLookFor} processes were shutdown and changes were reverted.") + .AddText($"{shortcutToUse.ExecutableNameAndPath} was closed", hintMaxLines: 1) + .AddText($"All {processToMonitorName} processes were shutdown and changes were reverted.") .AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true); toastContent = tcBuilder.Content; // Make sure to use Windows.Data.Xml.Dom @@ -1279,6 +1188,25 @@ namespace DisplayMagician 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 // Construct the Windows toast content ToastContentBuilder tcBuilder = new ToastContentBuilder() @@ -1298,10 +1226,8 @@ namespace DisplayMagician // And then show this notification DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast); - Process gameProcess; - //string gameRunCmd = gameLibraryToUse.GetRunCmd(gameToRun, shortcutToUse.GameArguments); - //gameProcess = Process.Start(gameRunCmd); - gameProcess = gameLibraryToUse.StartGame(gameToRun, shortcutToUse.GameArguments, ProcessUtils.TranslatePriorityToClass(shortcutToUse.ProcessPriority)); + List gameProcesses; + gameProcesses = gameLibraryToUse.StartGame(gameToRun, shortcutToUse.GameArguments, shortcutToUse.ProcessPriority); // Delay 500ms 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) { 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")}"); } - 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"); - // Prepare the processInfos we need for finding child processes. - //ProcessUtils.ScanProcesses(); - - // Stop the programs in the reverse order we started them - foreach (Process processToStop in startProgramsToStop.Reverse()) - { - 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 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 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."); - } - - } + // Shutdown the processes + ProcessUtils.StopProcess(startProgramsToStop); } // Change Audio Device back (if one specified)