mirror of
https://github.com/terrymacdonald/DisplayMagician.git
synced 2024-08-30 18:32:20 +00:00
DisplayMagician can now handle it if a gamelibrary is removed, or if you use a shortcut config file from later version of DisplayMagician with an earlier version of the DisplayMagician application. It will simply ignore the incompatible shortcut, and it won't be able to be edited or used.
1778 lines
96 KiB
C#
1778 lines
96 KiB
C#
using AudioSwitcher.AudioApi;
|
|
using AudioSwitcher.AudioApi.CoreAudio;
|
|
using DisplayMagician.GameLibraries;
|
|
using DisplayMagician.InterProcess;
|
|
using DisplayMagicianShared;
|
|
using Microsoft.Toolkit.Uwp.Notifications;
|
|
using Newtonsoft.Json;
|
|
using Newtonsoft.Json.Linq;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using System.Windows.Forms;
|
|
using Windows.Data.Xml.Dom;
|
|
using Windows.UI.Notifications;
|
|
|
|
namespace DisplayMagician
|
|
{
|
|
|
|
public static class ShortcutRepository
|
|
{
|
|
#region Class Variables
|
|
// Common items to the class
|
|
private static List<ShortcutItem> _allShortcuts = new List<ShortcutItem>();
|
|
//public static Dictionary<string, bool> _shortcutWarningLookup = new Dictionary<string, bool>();
|
|
//public static Dictionary<string, bool> _shortcutErrorLookup = new Dictionary<string, bool>();
|
|
private static bool _shortcutsLoaded = false;
|
|
// Other constants that are useful
|
|
private static string AppShortcutStoragePath = Path.Combine(Program.AppDataPath, $"Shortcuts");
|
|
private static string _shortcutStorageJsonFileName = Path.Combine(AppShortcutStoragePath, $"Shortcuts_{Version.ToString(2)}.json");
|
|
private static string uuidV4Regex = @"(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$";
|
|
private static CoreAudioController _audioController = null;
|
|
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
|
|
#endregion
|
|
|
|
#region Class Constructors
|
|
static ShortcutRepository()
|
|
{
|
|
|
|
try
|
|
{
|
|
NvAPIWrapper.NVIDIA.Initialize();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Warn(ex, $"ShortcutRepository/ShortcutRepository: Initialising NVIDIA NvAPIWrapper or CoreAudioController caused an exception.");
|
|
}
|
|
|
|
try
|
|
{
|
|
_audioController = new CoreAudioController();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Warn(ex, $"ShortcutRepository/ShortcutRepository: Exception while trying to initialise CoreAudioController. Audio Chipset on your computer is not supported. You will be unable to set audio settings.");
|
|
}
|
|
|
|
// Load the Shortcuts from storage
|
|
LoadShortcuts();
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Class Properties
|
|
public static List<ShortcutItem> AllShortcuts
|
|
{
|
|
get
|
|
{
|
|
if (!_shortcutsLoaded)
|
|
// Load the Shortcuts from storage
|
|
LoadShortcuts();
|
|
|
|
return _allShortcuts;
|
|
}
|
|
}
|
|
|
|
public static int ShortcutCount
|
|
{
|
|
get
|
|
{
|
|
if (!_shortcutsLoaded)
|
|
// Load the Shortcuts from storage
|
|
LoadShortcuts();
|
|
|
|
return _allShortcuts.Count;
|
|
}
|
|
}
|
|
|
|
#pragma warning disable CS3003 // Type is not CLS-compliant
|
|
public static CoreAudioController AudioController
|
|
#pragma warning restore CS3003 // Type is not CLS-compliant
|
|
{
|
|
get
|
|
{
|
|
return _audioController;
|
|
}
|
|
}
|
|
|
|
public static Version Version
|
|
{
|
|
get => new Version(1, 0, 0);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Class Methods
|
|
public static bool AddShortcut(ShortcutItem shortcut)
|
|
{
|
|
logger.Trace($"ShortcutRepository/AddShortcut: Adding shortcut {shortcut.Name} to our shortcut repository");
|
|
|
|
if (!(shortcut is ShortcutItem))
|
|
return false;
|
|
|
|
// Add the shortcut to the list of shortcuts
|
|
_allShortcuts.Add(shortcut);
|
|
|
|
//Doublecheck it's been added
|
|
if (ContainsShortcut(shortcut))
|
|
{
|
|
// Save the shortcuts JSON as it's different
|
|
SaveShortcuts();
|
|
IsValidRefresh();
|
|
return true;
|
|
}
|
|
else
|
|
return false;
|
|
|
|
}
|
|
|
|
public static bool RemoveShortcut(ShortcutItem shortcut)
|
|
{
|
|
logger.Trace($"ShortcutRepository/RemoveShortcut: Removing shortcut {shortcut.Name} if it exists in our shortcut repository");
|
|
|
|
if (!(shortcut is ShortcutItem))
|
|
return false;
|
|
|
|
// Remove the Shortcut Icons from the Cache
|
|
List<ShortcutItem> shortcutsToRemove = _allShortcuts.FindAll(item => item.UUID.Equals(shortcut.UUID, StringComparison.OrdinalIgnoreCase));
|
|
foreach (ShortcutItem shortcutToRemove in shortcutsToRemove)
|
|
{
|
|
try
|
|
{
|
|
logger.Info($"ShortcutRepository/RemoveShortcut: Removing shortcut {shortcutToRemove.Name}");
|
|
File.Delete(shortcutToRemove.SavedShortcutIconCacheFilename);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RemoveShortcut: Exception removing shortcut {shortcutToRemove.Name}");
|
|
}
|
|
}
|
|
|
|
// Remove the shortcut from the list.
|
|
int numRemoved = _allShortcuts.RemoveAll(item => item.UUID.Equals(shortcut.UUID, StringComparison.OrdinalIgnoreCase));
|
|
|
|
if (numRemoved == 1)
|
|
{
|
|
SaveShortcuts();
|
|
IsValidRefresh();
|
|
logger.Trace($"ShortcutRepository/RemoveShortcut: Our shortcut repository does contain a shortcut we were looking for");
|
|
return true;
|
|
}
|
|
else if (numRemoved == 0)
|
|
{
|
|
logger.Trace($"ShortcutRepository/RemoveShortcut: Our shortcut repository doesn't contain a shortcut we were looking for");
|
|
return false;
|
|
}
|
|
|
|
else
|
|
throw new ShortcutRepositoryException();
|
|
}
|
|
|
|
|
|
public static bool RemoveShortcut(string shortcutNameOrUuid)
|
|
{
|
|
|
|
logger.Trace($"ShortcutRepository/RemoveShortcut2: Removing shortcut {shortcutNameOrUuid} if it exists in our shortcut repository");
|
|
|
|
if (String.IsNullOrWhiteSpace(shortcutNameOrUuid))
|
|
{
|
|
logger.Error($"ShortcutRepository/RemoveShortcut2: Shortcut to look for was empty or only whitespace");
|
|
return false;
|
|
}
|
|
List<ShortcutItem> shortcutsToRemove;
|
|
int numRemoved;
|
|
|
|
Match match = Regex.Match(shortcutNameOrUuid, uuidV4Regex, RegexOptions.IgnoreCase);
|
|
if (match.Success)
|
|
{
|
|
shortcutsToRemove = _allShortcuts.FindAll(item => item.UUID.Equals(shortcutNameOrUuid, StringComparison.OrdinalIgnoreCase));
|
|
numRemoved = _allShortcuts.RemoveAll(item => item.UUID.Equals(shortcutNameOrUuid, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
else
|
|
{
|
|
shortcutsToRemove = _allShortcuts.FindAll(item => item.Name.Equals(shortcutNameOrUuid, StringComparison.OrdinalIgnoreCase));
|
|
numRemoved = _allShortcuts.RemoveAll(item => item.Name.Equals(shortcutNameOrUuid, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
// Remove the Shortcut Icons from the Cache
|
|
foreach (ShortcutItem shortcutToRemove in shortcutsToRemove)
|
|
{
|
|
try
|
|
{
|
|
logger.Info($"ShortcutRepository/RemoveShortcut2: Removing shortcut {shortcutToRemove.Name}");
|
|
File.Delete(shortcutToRemove.SavedShortcutIconCacheFilename);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RemoveShortcut2: Exception removing shortcut {shortcutToRemove.Name}");
|
|
}
|
|
}
|
|
|
|
if (numRemoved == 1)
|
|
{
|
|
SaveShortcuts();
|
|
IsValidRefresh();
|
|
logger.Trace($"ShortcutRepository/RemoveShortcut2: Our shortcut repository does contain a shortcut with Name or UUID {shortcutNameOrUuid}");
|
|
return true;
|
|
}
|
|
else if (numRemoved == 0)
|
|
{
|
|
logger.Trace($"ShortcutRepository/RemoveShortcut2: Our shortcut repository doesn't contain a shortcut with Name or UUID {shortcutNameOrUuid}");
|
|
return false;
|
|
}
|
|
else
|
|
throw new ShortcutRepositoryException();
|
|
|
|
}
|
|
|
|
|
|
public static bool ContainsShortcut(ShortcutItem shortcut)
|
|
{
|
|
|
|
logger.Trace($"ShortcutRepository/ContainsShortcut: Checking whether {shortcut.Name} exists in our shortcut repository");
|
|
|
|
if (!(shortcut is ShortcutItem))
|
|
return false;
|
|
|
|
foreach (ShortcutItem testShortcut in _allShortcuts)
|
|
{
|
|
if (testShortcut.UUID.Equals(shortcut.UUID, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
logger.Trace($"ShortcutRepository/ContainsShortcut: {shortcut.Name} does exist in our shortcut repository");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static bool ContainsShortcut(string shortcutNameOrUuid)
|
|
{
|
|
|
|
logger.Trace($"ShortcutRepository/ContainsShortcut2: Checking whether {shortcutNameOrUuid} exists in our shortcut repository");
|
|
|
|
if (String.IsNullOrWhiteSpace(shortcutNameOrUuid))
|
|
{
|
|
logger.Error($"ShortcutRepository/ContainsShortcut2: Shortcut to look for was empty or only whitespace");
|
|
return false;
|
|
}
|
|
|
|
Match match = Regex.Match(shortcutNameOrUuid, uuidV4Regex, RegexOptions.IgnoreCase);
|
|
if (match.Success)
|
|
{
|
|
foreach (ShortcutItem testShortcut in _allShortcuts)
|
|
{
|
|
if (testShortcut.UUID.Equals(shortcutNameOrUuid, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
logger.Trace($"ShortcutRepository/ContainsShortcut2: Shortcut with UUID {shortcutNameOrUuid} does exist in our shortcut repository");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
foreach (ShortcutItem testShortcut in _allShortcuts)
|
|
{
|
|
if (testShortcut.Name.Equals(shortcutNameOrUuid, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
logger.Trace($"ShortcutRepository/ContainsShortcut2: Shortcut with name {shortcutNameOrUuid} does exist in our shortcut repository");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
logger.Trace($"ShortcutRepository/ContainsShortcut2: Shortcut with name {shortcutNameOrUuid} doesn't exist in our shortcut repository");
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
public static ShortcutItem GetShortcut(string shortcutNameOrUuid)
|
|
{
|
|
logger.Trace($"ShortcutRepository/GetShortcut: Finding and returning {shortcutNameOrUuid} if it exists in our shortcut repository");
|
|
|
|
if (String.IsNullOrWhiteSpace(shortcutNameOrUuid))
|
|
{
|
|
logger.Error($"ShortcutRepository/GetShortcut: Shortcut to get was empty or only whitespace");
|
|
return null;
|
|
}
|
|
|
|
Match match = Regex.Match(shortcutNameOrUuid, uuidV4Regex, RegexOptions.IgnoreCase);
|
|
if (match.Success)
|
|
{
|
|
foreach (ShortcutItem testShortcut in _allShortcuts)
|
|
{
|
|
if (testShortcut.UUID.Equals(shortcutNameOrUuid, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
logger.Trace($"ShortcutRepository/GetShortcut: Returning shortcut with UUID {shortcutNameOrUuid}");
|
|
return testShortcut;
|
|
}
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
foreach (ShortcutItem testShortcut in _allShortcuts)
|
|
{
|
|
if (testShortcut.Name.Equals(shortcutNameOrUuid, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
logger.Trace($"ShortcutRepository/GetShortcut: Returning shortcut with Name {shortcutNameOrUuid}");
|
|
return testShortcut;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
logger.Trace($"ShortcutRepository/GetShortcut: No shortcut was found to return with UUID or Name {shortcutNameOrUuid}");
|
|
return null;
|
|
|
|
}
|
|
|
|
#pragma warning disable CS3001 // Argument type is not CLS-compliant
|
|
public static bool RenameShortcutProfile(ProfileItem newProfile)
|
|
#pragma warning restore CS3001 // Argument type is not CLS-compliant
|
|
{
|
|
logger.Debug($"ShortcutRepository/RenameShortcutProfile: Renaming the profile in any shortcuts containing the old profile name");
|
|
|
|
if (!(newProfile is ProfileItem))
|
|
return false;
|
|
|
|
foreach (ShortcutItem testShortcut in ShortcutRepository.AllShortcuts)
|
|
{
|
|
if (testShortcut.ProfileUUID.Equals(newProfile.UUID, StringComparison.OrdinalIgnoreCase) && testShortcut.AutoName)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RenameShortcutProfile: Renaming {testShortcut.Name} shortcut's profile to {newProfile.Name} since the original profile has just been renamed.");
|
|
testShortcut.ProfileToUse = newProfile;
|
|
testShortcut.AutoSuggestShortcutName();
|
|
}
|
|
}
|
|
|
|
SaveShortcuts();
|
|
IsValidRefresh();
|
|
return true;
|
|
}
|
|
|
|
private static bool LoadShortcuts()
|
|
{
|
|
|
|
logger.Debug($"ShortcutRepository/LoadShortcuts: Loading shortcuts from {_shortcutStorageJsonFileName} into the Shortcut Repository");
|
|
|
|
if (File.Exists(_shortcutStorageJsonFileName))
|
|
{
|
|
string json = "";
|
|
try
|
|
{
|
|
json = File.ReadAllText(_shortcutStorageJsonFileName, Encoding.Unicode);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/LoadShortcuts: Tried to read the JSON file {_shortcutStorageJsonFileName} to memory but File.ReadAllTextthrew an exception.");
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(json))
|
|
{
|
|
// Firstly perform any modifications we need to do to update the JSON structure
|
|
// to handle old versions of the file that need updating. Done with a simple regex replace
|
|
try
|
|
{
|
|
|
|
// Replace any "Enabled": true with "Disabled": false
|
|
json = Regex.Replace(json, @" ""Enabled"": true,", @" ""Disabled"": false,");
|
|
// Replace any "Enabled": false with "Disabled": true
|
|
json = Regex.Replace(json, @" ""Enabled"": false,", @" ""Disabled"": true,");
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
// problem updating JSON
|
|
logger.Error(ex, $"ShortcutRepository/LoadShortcuts: Tried to update the JSON in the {_shortcutStorageJsonFileName} but the Regex Replace threw an exception.");
|
|
}
|
|
|
|
#pragma warning disable IDE0059 // Unnecessary assignment of a value
|
|
List<ShortcutItem> shortcuts = new List<ShortcutItem>();
|
|
#pragma warning restore IDE0059 // Unnecessary assignment of a value
|
|
try
|
|
{
|
|
_allShortcuts = JsonConvert.DeserializeObject<List<ShortcutItem>>(json, new JsonSerializerSettings
|
|
{
|
|
MissingMemberHandling = MissingMemberHandling.Ignore,
|
|
NullValueHandling = NullValueHandling.Ignore,
|
|
DefaultValueHandling = DefaultValueHandling.Include,
|
|
TypeNameHandling = TypeNameHandling.Auto
|
|
});
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/LoadShortcuts: Tried to parse the JSON in the {_shortcutStorageJsonFileName} but the JsonConvert threw an exception.");
|
|
}
|
|
|
|
// Lookup all the Profile Names in the Saved Profiles
|
|
// and link the profiles to the Shortcuts as we only
|
|
// store the profile names to allow users to uodate profiles
|
|
// separately from the shortcuts
|
|
logger.Debug($"ShortcutRepository/LoadShortcuts: Connecting Shortcut profile names to the real profile objects");
|
|
foreach (ShortcutItem updatedShortcut in _allShortcuts)
|
|
{
|
|
bool foundProfile = false;
|
|
foreach (ProfileItem profile in ProfileRepository.AllProfiles)
|
|
{
|
|
|
|
if (profile.UUID.Equals(updatedShortcut.ProfileUUID))
|
|
{
|
|
// And assign the matching Profile if we find it.
|
|
updatedShortcut.ProfileToUse = profile;
|
|
foundProfile = true;
|
|
logger.Debug($"ShortcutRepository/LoadShortcuts: Found the profile with UUID {updatedShortcut.ProfileUUID} and linked it to a profile!");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!foundProfile)
|
|
{
|
|
// We should only get here if there isn't a profile to match to.
|
|
logger.Debug($"ShortcutRepository/LoadShortcuts: Couldn't find the profile with UUID {updatedShortcut.ProfileUUID} so couldn't link it to a profile! We can't use this shortcut.");
|
|
updatedShortcut.ProfileToUse = null;
|
|
}
|
|
}
|
|
|
|
// Sort the shortcuts alphabetically
|
|
_allShortcuts.Sort();
|
|
}
|
|
else
|
|
{
|
|
logger.Debug($"ShortcutRepository/LoadShortcuts: The {_shortcutStorageJsonFileName} shortcut JSON file exists but is empty! So we're going to treat it as if it didn't exist.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Debug($"ShortcutRepository/LoadShortcuts: Couldn't find the {_shortcutStorageJsonFileName} shortcut JSON file that contains the Shortcuts");
|
|
}
|
|
_shortcutsLoaded = true;
|
|
|
|
IsValidRefresh();
|
|
return true;
|
|
}
|
|
|
|
public static bool SaveShortcuts()
|
|
{
|
|
logger.Debug($"ShortcutRepository/SaveShortcuts: Attempting to save the shortcut repository to the {_shortcutStorageJsonFileName}.");
|
|
|
|
if (!Directory.Exists(AppShortcutStoragePath))
|
|
{
|
|
logger.Debug($"ShortcutRepository/SaveShortcuts: Creating the shortcut folder {AppShortcutStoragePath} as it doesn't currently exist.");
|
|
try
|
|
{
|
|
Directory.CreateDirectory(AppShortcutStoragePath);
|
|
}
|
|
catch (UnauthorizedAccessException ex)
|
|
{
|
|
logger.Fatal(ex, $"ShortcutRepository/SaveShortcuts: DisplayMagician doesn't have permissions to create the Shortcuts storage folder {AppShortcutStoragePath}.");
|
|
}
|
|
catch (ArgumentException ex)
|
|
{
|
|
logger.Fatal(ex, $"ShortcutRepository/SaveShortcuts: DisplayMagician can't create the Shortcuts storage folder {AppShortcutStoragePath} due to an invalid argument.");
|
|
}
|
|
catch (PathTooLongException ex)
|
|
{
|
|
logger.Fatal(ex, $"ShortcutRepository/SaveShortcuts: DisplayMagician can't create the Shortcuts storage folder {AppShortcutStoragePath} as the path is too long.");
|
|
}
|
|
catch (DirectoryNotFoundException ex)
|
|
{
|
|
logger.Fatal(ex, $"ShortcutRepository/SaveShortcuts: DisplayMagician can't create the Shortcuts storage folder {AppShortcutStoragePath} as the parent folder isn't there.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Debug($"ShortcutRepository/SaveShortcuts: Shortcut folder {AppShortcutStoragePath} exists.");
|
|
}
|
|
|
|
|
|
try
|
|
{
|
|
logger.Debug($"ShortcutRepository/SaveShortcuts: Converting the objects to JSON format.");
|
|
|
|
var json = JsonConvert.SerializeObject(_allShortcuts, Formatting.Indented, new JsonSerializerSettings
|
|
{
|
|
NullValueHandling = NullValueHandling.Include,
|
|
DefaultValueHandling = DefaultValueHandling.Populate,
|
|
TypeNameHandling = TypeNameHandling.Auto
|
|
|
|
});
|
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(json))
|
|
{
|
|
logger.Debug($"ShortcutRepository/SaveShortcuts: Saving the shortcut repository to the {_shortcutStorageJsonFileName}.");
|
|
|
|
File.WriteAllText(_shortcutStorageJsonFileName, json, Encoding.Unicode);
|
|
return true;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/SaveShortcuts: Unable to save the shortcut repository to the {_shortcutStorageJsonFileName}.");
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static void IsValidRefresh()
|
|
{
|
|
// We need to refresh the cached answer
|
|
// Get the list of connected devices
|
|
|
|
foreach (ShortcutItem loadedShortcut in AllShortcuts)
|
|
{
|
|
loadedShortcut.RefreshValidity();
|
|
}
|
|
}
|
|
|
|
|
|
// ReSharper disable once CyclomaticComplexity
|
|
public static void RunShortcut(ShortcutItem shortcutToUse, NotifyIcon notifyIcon = null)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Running the shortcut {shortcutToUse.Name}.");
|
|
|
|
// Do some validation to make sure the shortcut is sensible
|
|
// And that we have enough to try and action within the shortcut
|
|
// including checking the Profile in the shortcut is possible
|
|
// (in other words check everything in the shortcut is still valid)
|
|
if (!(shortcutToUse is ShortcutItem))
|
|
return;
|
|
|
|
// Check the shortcut is still valid.
|
|
shortcutToUse.RefreshValidity();
|
|
|
|
if (shortcutToUse.IsValid == ShortcutValidity.Error || shortcutToUse.IsValid == ShortcutValidity.Warning)
|
|
{
|
|
logger.Error($"ShortcutRepository/RunShortcut: Cannot run the shortcut {shortcutToUse.Name} as it isn't valid");
|
|
string errorReasons = String.Join(", ", (from error in shortcutToUse.Errors select error.Message));
|
|
MessageBox.Show(
|
|
$"Unable to run the shortcut '{shortcutToUse.Name}': {errorReasons}",
|
|
@"Cannot run the Shortcut",
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Exclamation);
|
|
return;
|
|
}
|
|
|
|
// Remember the profile we are on now
|
|
bool needToChangeProfiles = false;
|
|
ProfileItem rollbackProfile = ProfileRepository.CurrentProfile;
|
|
if (!rollbackProfile.Equals(shortcutToUse.ProfileToUse))
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: We need to change to the {shortcutToUse.ProfileToUse} profile.");
|
|
needToChangeProfiles = true;
|
|
}
|
|
else
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: We're already on the {rollbackProfile.Name} profile so no need to change profiles.");
|
|
}
|
|
// Tell the IPC Service we are busy right now, and keep the previous status for later
|
|
//InstanceStatus rollbackInstanceStatus = IPCService.GetInstance().Status;
|
|
//IPCService.GetInstance().Status = InstanceStatus.Busy;
|
|
|
|
// Only change profiles if we have to
|
|
if (needToChangeProfiles)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Changing to the {rollbackProfile.Name} profile.");
|
|
// Apply the Profile!
|
|
ApplyProfileResult result = Program.ApplyProfile(shortcutToUse.ProfileToUse);
|
|
if (result == ApplyProfileResult.Error)
|
|
{
|
|
Console.WriteLine($"ERROR - Cannot apply '{shortcutToUse.ProfileToUse.Name}' Display Profile");
|
|
logger.Error($"ShortcutRepository/RunShortcut: Cannot apply '{shortcutToUse.ProfileToUse.Name}' Display Profile");
|
|
return;
|
|
}
|
|
else if (result == ApplyProfileResult.Cancelled)
|
|
{
|
|
Console.WriteLine($"ERROR - User cancelled applying '{shortcutToUse.ProfileToUse.Name}' Display Profile");
|
|
logger.Error($"ShortcutRepository/RunShortcut: User cancelled applying '{shortcutToUse.ProfileToUse.Name}' Display Profile");
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
// Get the list of Audio Devices currently connected and active
|
|
bool needToChangeAudioDevice = false;
|
|
CoreAudioDevice rollbackAudioDevice = null;
|
|
double rollbackAudioVolume = 50;
|
|
List<CoreAudioDevice> activeAudioDevices = new List<CoreAudioDevice>();
|
|
bool needToChangeCaptureDevice = false;
|
|
CoreAudioDevice rollbackCaptureDevice = null;
|
|
double rollbackCaptureVolume = 50;
|
|
List<CoreAudioDevice> activeCaptureDevices = new List<CoreAudioDevice>();
|
|
|
|
if (_audioController != null)
|
|
{
|
|
try {
|
|
activeAudioDevices = _audioController.GetPlaybackDevices(DeviceState.Active).ToList();
|
|
if (activeAudioDevices.Count > 0)
|
|
{
|
|
// Change Audio Device (if one specified)
|
|
if (shortcutToUse.ChangeAudioDevice && !shortcutToUse.AudioDevice.Equals(""))
|
|
{
|
|
|
|
// record the old audio device
|
|
rollbackAudioDevice = _audioController.DefaultPlaybackDevice;
|
|
if (rollbackAudioDevice != null)
|
|
{
|
|
rollbackAudioVolume = _audioController.DefaultPlaybackDevice.Volume;
|
|
if (!rollbackAudioDevice.FullName.Equals(shortcutToUse.AudioDevice))
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: We need to change to the {shortcutToUse.AudioDevice} audio device.");
|
|
needToChangeAudioDevice = true;
|
|
}
|
|
|
|
}
|
|
|
|
if (needToChangeAudioDevice)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Changing to the {shortcutToUse.AudioDevice} audio device.");
|
|
|
|
foreach (CoreAudioDevice audioDevice in activeAudioDevices)
|
|
{
|
|
if (audioDevice.FullName.Equals(shortcutToUse.AudioDevice))
|
|
{
|
|
// use the Audio Device
|
|
audioDevice.SetAsDefault();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: We're already using the {shortcutToUse.AudioDevice} audio device so no need to change audio devices.");
|
|
}
|
|
|
|
if (shortcutToUse.SetAudioVolume)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Setting {shortcutToUse.AudioDevice} volume level to {shortcutToUse.AudioVolume}%.");
|
|
Task myTask = new Task(() =>
|
|
{
|
|
_audioController.DefaultPlaybackDevice.SetVolumeAsync(Convert.ToDouble(shortcutToUse.AudioVolume));
|
|
});
|
|
myTask.Start();
|
|
myTask.Wait(2000);
|
|
}
|
|
else
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: We don't need to set the {shortcutToUse.AudioDevice} volume level.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Shortcut does not require changing Audio Device.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Warn($"ShortcutRepository/RunShortcut: No active Audio Devices to use so skipping audio device checks!");
|
|
}
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception accessing or manipulating Audio Devices!");
|
|
}
|
|
|
|
|
|
try
|
|
{
|
|
// Get the list of Audio Devices currently connected
|
|
activeCaptureDevices = _audioController.GetCaptureDevices(DeviceState.Active).ToList();
|
|
if (activeCaptureDevices.Count > 0)
|
|
{
|
|
|
|
// Change capture Device (if one specified)
|
|
if (shortcutToUse.ChangeCaptureDevice && !shortcutToUse.CaptureDevice.Equals(""))
|
|
{
|
|
// record the old microphone device
|
|
rollbackCaptureDevice = _audioController.DefaultCaptureDevice;
|
|
if (rollbackCaptureDevice != null)
|
|
{
|
|
rollbackCaptureVolume = _audioController.DefaultCaptureDevice.Volume;
|
|
if (!rollbackCaptureDevice.FullName.Equals(shortcutToUse.CaptureDevice))
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: We need to change to the {shortcutToUse.CaptureDevice} capture (microphone) device.");
|
|
needToChangeCaptureDevice = true;
|
|
}
|
|
}
|
|
|
|
if (needToChangeCaptureDevice)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Changing to the {shortcutToUse.CaptureDevice} capture (microphone) device.");
|
|
|
|
foreach (CoreAudioDevice captureDevice in activeCaptureDevices)
|
|
{
|
|
if (captureDevice.FullName.Equals(shortcutToUse.CaptureDevice))
|
|
{
|
|
// use the Audio Device
|
|
captureDevice.SetAsDefault();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: We're already using the {shortcutToUse.CaptureDevice} capture (microphone) device so no need to change capture devices.");
|
|
}
|
|
|
|
if (shortcutToUse.SetCaptureVolume)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Setting {shortcutToUse.CaptureDevice} capture (microphone) level to {shortcutToUse.CaptureVolume}%.");
|
|
Task myTask = new Task(() =>
|
|
{
|
|
_audioController.DefaultCaptureDevice.SetVolumeAsync(Convert.ToDouble(shortcutToUse.CaptureVolume));
|
|
});
|
|
myTask.Start();
|
|
myTask.Wait(2000);
|
|
}
|
|
else
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: We don't need to set the {shortcutToUse.CaptureDevice} capture (microphone) volume level.");
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Shortcut does not require changing capture (microphone) device.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Warn($"ShortcutRepository/RunShortcut: No active Capture Devices to use so skipping capture device checks!");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception accessing or manipulating Capture Devices!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Error($"ShortcutRepository/RunShortcut: CoreAudio Controller is null, so we can't set Audio or Capture Devices!");
|
|
}
|
|
|
|
// Set the IP Service status back to what it was
|
|
//IPCService.GetInstance().Status = rollbackInstanceStatus;
|
|
|
|
// Now run the pre-start applications
|
|
List<Process> startProgramsToStop = new List<Process>();
|
|
List<StartProgram> startProgramsToStart = shortcutToUse.StartPrograms.Where(program => program.Disabled == false).Where(program => !String.IsNullOrWhiteSpace(program.Executable)).OrderBy(program => program.Priority).ToList();
|
|
if (startProgramsToStart.Count > 0)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Starting {startProgramsToStart.Count} programs before the main game or executable");
|
|
foreach (StartProgram processToStart in startProgramsToStart)
|
|
{
|
|
// If required, check whether a process is started already
|
|
if (processToStart.DontStartIfAlreadyRunning)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Checking if process {processToStart.Executable} is already running");
|
|
Process[] alreadyRunningProcesses = System.Diagnostics.Process.GetProcessesByName(Path.GetFileNameWithoutExtension(processToStart.Executable));
|
|
if (alreadyRunningProcesses.Length > 0)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Process {processToStart.Executable} is already running, so we won't start a new one, and we won't stop it later");
|
|
continue;
|
|
}
|
|
|
|
}
|
|
|
|
// Start the executable
|
|
logger.Info($"ShortcutRepository/RunShortcut: Starting process {processToStart.Executable}");
|
|
Process process = null;
|
|
try
|
|
{
|
|
if (processToStart.ExecutableArgumentsRequired)
|
|
process = System.Diagnostics.Process.Start(processToStart.Executable, processToStart.Arguments);
|
|
else
|
|
process = System.Diagnostics.Process.Start(processToStart.Executable);
|
|
// Record t
|
|
if (processToStart.CloseOnFinish)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: We need to stop {processToStart.Executable} after the main game or executable is closed.");
|
|
startProgramsToStop.Add(process);
|
|
}
|
|
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");
|
|
}
|
|
}
|
|
catch (Win32Exception ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RunShortcut: Win32Exception starting process {processToStart.Executable}. Windows complained about something while trying to create a new process.");
|
|
}
|
|
catch (ObjectDisposedException ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception starting process {processToStart.Executable}. The object was disposed before we could start the process.");
|
|
}
|
|
catch (FileNotFoundException ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RunShortcut: Win32Exception starting process {processToStart.Executable}. The file wasn't found by DisplayMagician and so we couldn't start it");
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception starting process {processToStart.Executable}. Method call is invalid for the current state.");
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: No programs to start before the main game or executable");
|
|
}
|
|
|
|
// Add a status notification icon in the status area
|
|
// but only if we are going to wait for a process to finish
|
|
string oldNotifyText = "";
|
|
bool temporaryNotifyIcon = false;
|
|
ContextMenuStrip oldContextMenuStrip = null;
|
|
|
|
// If we're running the shortcut from the ShortcutLibrary
|
|
// then we get given the NotifyIcon through the function
|
|
// parameters i.e. if temporaryIcon is false in that case.
|
|
// This means we need to save the state if the temporaryIcon
|
|
// is false.
|
|
// Conversely, if temporaryIcon is true, then we need
|
|
// to create a NotifyIncon as MainForm isn't running to create
|
|
// one for us already!
|
|
if (notifyIcon == null)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: We need to create a temporary system tray icon as we're running from a shortcut");
|
|
temporaryNotifyIcon = true;
|
|
}
|
|
|
|
if (temporaryNotifyIcon)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Create a temporary system tray icon (user clicked a desktop shortcut)");
|
|
|
|
if (!shortcutToUse.Category.Equals(ShortcutCategory.NoGame))
|
|
{
|
|
|
|
try
|
|
{
|
|
notifyIcon = new NotifyIcon
|
|
{
|
|
Icon = Properties.Resources.DisplayMagician,
|
|
Visible = true
|
|
};
|
|
Application.DoEvents();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"ShortcutRepository/RunShortcut exception: Trying to {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
|
|
logger.Error(ex, $"ShortcutRepository/RunShortcut exception setting NotifyIcon");
|
|
// ignored
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Updating existing system tray icon (we're running a shortcut from within the main application window)");
|
|
// If we reach here then we're running the shortcut
|
|
// from the ShortcutLibrary window, so we need to
|
|
// remember what the text was so we can return it to
|
|
// normal after we're done!
|
|
oldNotifyText = notifyIcon.Text;
|
|
oldContextMenuStrip = notifyIcon.ContextMenuStrip;
|
|
notifyIcon.ContextMenuStrip = null;
|
|
Application.DoEvents();
|
|
}
|
|
|
|
|
|
// Now start the main game, 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
|
|
|
|
try
|
|
{
|
|
Process process = null;
|
|
if (shortcutToUse.ExecutableArgumentsRequired)
|
|
process = System.Diagnostics.Process.Start(shortcutToUse.ExecutableNameAndPath, shortcutToUse.ExecutableArguments);
|
|
else
|
|
process = System.Diagnostics.Process.Start(shortcutToUse.ExecutableNameAndPath);
|
|
}
|
|
catch (Win32Exception ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RunShortcut: Win32Exception starting main executable process {shortcutToUse.ExecutableNameAndPath}. Windows complained about something while trying to create a new process.");
|
|
}
|
|
catch (ObjectDisposedException ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception starting main executable process {shortcutToUse.ExecutableNameAndPath}. The object was disposed before we could start the process.");
|
|
}
|
|
catch (FileNotFoundException ex)
|
|
{
|
|
logger.Error(ex, $"ShortcutRepository/RunShortcut: Win32Exception starting main executable process {shortcutToUse.ExecutableNameAndPath}. The file wasn't found by DisplayMagician and so we couldn't start it");
|
|
}
|
|
catch (InvalidOperationException ex)
|
|
{
|
|
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;
|
|
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);
|
|
}
|
|
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<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");
|
|
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, 35);
|
|
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");
|
|
while (true)
|
|
{
|
|
processesToMonitor = Process.GetProcessesByName(processNameToLookFor).ToList();
|
|
|
|
// If we have no more processes left then we're done!
|
|
if (processesToMonitor.Count == 0)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: No more '{processNameToLookFor}' processes are still running");
|
|
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(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.")
|
|
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show it
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
|
|
|
|
}
|
|
else if (shortcutToUse.Category.Equals(ShortcutCategory.Game))
|
|
{
|
|
Game gameToRun = null;
|
|
GameLibrary gameLibraryToUse = null;
|
|
|
|
// If the game is a Steam Game we check for that
|
|
if (shortcutToUse.GameLibrary.Equals(SupportedGameLibraryType.Steam))
|
|
{
|
|
// We now need to get the SteamGame info
|
|
gameLibraryToUse = SteamLibrary.GetLibrary();
|
|
}
|
|
// If the game is a Uplay Uplay Game we check for that
|
|
else if (shortcutToUse.GameLibrary.Equals(SupportedGameLibraryType.Uplay))
|
|
{
|
|
// We now need to get the Uplay Game info
|
|
gameLibraryToUse = UplayLibrary.GetLibrary();
|
|
}
|
|
// If the game is an Origin Game we check for that
|
|
else if (shortcutToUse.GameLibrary.Equals(SupportedGameLibraryType.Origin))
|
|
{
|
|
// We now need to get the Origin Game info
|
|
gameLibraryToUse = OriginLibrary.GetLibrary();
|
|
}
|
|
else if (shortcutToUse.GameLibrary.Equals(SupportedGameLibraryType.Epic))
|
|
{
|
|
// We now need to get the Epic Game info
|
|
gameLibraryToUse = EpicLibrary.GetLibrary();
|
|
}
|
|
else if (shortcutToUse.GameLibrary.Equals(SupportedGameLibraryType.GOG))
|
|
{
|
|
// We now need to get the GOG Game info
|
|
gameLibraryToUse = GogLibrary.GetLibrary();
|
|
}
|
|
gameToRun = gameLibraryToUse.GetGameById(shortcutToUse.GameAppId);
|
|
logger.Info($"ShortcutRepository/RunShortcut: Starting the {gameToRun.Name} {gameLibraryToUse.GameLibraryName} Game, and then we're going to monitor it to wait for it to close.");
|
|
|
|
// If the GameAppID is not null, then we've matched a game! Lets run it.
|
|
if (gameToRun != null)
|
|
{
|
|
|
|
// Now we want to tell the user we're start a game
|
|
// Construct the Windows toast content
|
|
ToastContentBuilder tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo($"notify=starting{gameLibraryToUse.GameLibraryName}", ToastActivationType.Foreground)
|
|
.AddText($"Starting {gameLibraryToUse.GameLibraryName}", hintMaxLines: 1)
|
|
.AddText($"Waiting for {gameLibraryToUse.GameLibraryName} Game Library to start (and update if needed)...")
|
|
.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);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// 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);
|
|
|
|
// Delay 500ms
|
|
Thread.Sleep(500);
|
|
|
|
// Wait for GameLibrary to start
|
|
for (int secs = 0; secs <= (shortcutToUse.StartTimeout * 1000); secs += 500)
|
|
{
|
|
|
|
// If we have found one or more processes then we should be good to go
|
|
// so let's break, and get to the next step....
|
|
if (gameLibraryToUse.IsRunning)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Found at least one GameLibrary process has started");
|
|
break;
|
|
}
|
|
|
|
// Let's wait a little while if we couldn't find
|
|
// any processes yet
|
|
Thread.Sleep(500);
|
|
|
|
}
|
|
|
|
// Check whether GameLibrary is updating (if it supports finding that out!)
|
|
// Note - this is the scaffolding in place for the future. It will allow future ability to
|
|
// detect game library updates if I can find a way of developing them per library in the future.
|
|
if (gameLibraryToUse.IsUpdating)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: GameLibrary {gameLibraryToUse.GameLibraryName} has started updating itself.");
|
|
|
|
// Now we want to tell the user we're updating the game library
|
|
// Construct the Windows toast content
|
|
tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo($"notify=updating{gameLibraryToUse.GameLibraryName}", ToastActivationType.Foreground)
|
|
.AddText($"Updating {gameLibraryToUse.GameLibraryName}", hintMaxLines: 1)
|
|
.AddText($"Waiting for {gameLibraryToUse.GameLibraryName} Game Library to update itself...")
|
|
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
|
|
//.AddButton("Stop", ToastActivationType.Background, "notify=runningGame&action=stop");
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show this notification
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
|
|
// Wait for up to 5 minutes for GameLibrary to update
|
|
for (int secs = 0; secs <= 5000; secs += 500)
|
|
{
|
|
|
|
// If the game library has finished updating then let's break, and get to the next step....
|
|
if (!gameLibraryToUse.IsUpdating)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: GameLibrary {gameLibraryToUse.GameLibraryName} has finished updating.");
|
|
break;
|
|
}
|
|
|
|
// Let's wait a little while while the GameLibrary is updating
|
|
Thread.Sleep(500);
|
|
}
|
|
|
|
}
|
|
|
|
// Delay 5secs
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Pausing to let the game library start the game.");
|
|
Thread.Sleep(5000);
|
|
|
|
// Store the Process ID for later
|
|
//IPCService.GetInstance().HoldProcessId = gameLibraryProcesses.FirstOrDefault()?.Id ?? 0;
|
|
//IPCService.GetInstance().Status = InstanceStatus.OnHold;
|
|
|
|
// Check whether Game itself is updating (if it supports finding that out!)
|
|
// Note - this is the scaffolding in place for the future. It will allow future ability to
|
|
// detect game library updates if I can find a way of developing them per library in the future.
|
|
if (gameToRun.IsUpdating)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Game {gameToRun.Name} is being updated so we'll wait up to 15 mins until it's finished.");
|
|
// Now we want to tell the user we're updating the game
|
|
// Construct the Windows toast content
|
|
tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo($"notify=updating{gameToRun.Name}", ToastActivationType.Foreground)
|
|
.AddText($"Updating {gameToRun.Name}", hintMaxLines: 1)
|
|
.AddText($"Waiting for {gameToRun.Name} Game to update...")
|
|
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
|
|
//.AddButton("Stop", ToastActivationType.Background, "notify=runningGame&action=stop");
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show this notification
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
// Wait for up to 15 minutes for the Game to update
|
|
for (int secs = 0; secs <= 15000; secs += 500)
|
|
{
|
|
|
|
// If the game library has finished updating then let's break, and get to the next step....
|
|
if (!gameToRun.IsUpdating)
|
|
{
|
|
logger.Info($"ShortcutRepository/RunShortcut: Game {gameToRun.Name} has finished updating.");
|
|
break;
|
|
}
|
|
|
|
// Let's wait a little while while the GameLibrary is updating
|
|
Thread.Sleep(500);
|
|
}
|
|
|
|
}
|
|
|
|
// At this point, if the user wants to actually monitor a different process,
|
|
// then we actually need to monitor that instead
|
|
if (shortcutToUse.MonitorDifferentGameExe)
|
|
{
|
|
// If we are monitoring a different executable rather than the game itself, then lets get that name ready instead
|
|
string altGameProcessToMonitor = System.IO.Path.GetFileNameWithoutExtension(shortcutToUse.DifferentGameExeToMonitor);
|
|
|
|
// Add a status notification icon in the status area
|
|
if (gameToRun.Name.Length <= 41)
|
|
notifyIcon.Text = $"DisplayMagician: Running {gameToRun.Name}...";
|
|
else
|
|
notifyIcon.Text = $"DisplayMagician: Running {gameToRun.Name.Substring(0, 41)}...";
|
|
Application.DoEvents();
|
|
|
|
// 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(altGameProcessToMonitor).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} '{altGameProcessToMonitor}' processes to monitor");
|
|
break;
|
|
}
|
|
|
|
// Let's wait a little while if we couldn't find
|
|
// any processes yet
|
|
Thread.Sleep(500);
|
|
}
|
|
// make sure we have an alternative game executable to monitor
|
|
if (processesToMonitor.Count == 0)
|
|
{
|
|
// if we didn't find an alternative game exectuable to monitor, then we need to go for the game executable itself as a fall back
|
|
logger.Error($"ShortcutRepository/RunShortcut: No Alternative Game Executable '{altGameProcessToMonitor}' processes found before waiting timeout. DisplayMagician was unable to find any alternative processes before the {shortcutToUse.StartTimeout} second timeout");
|
|
logger.Info($"ShortcutRepository/RunShortcut: Ignoring monitoring Alternative Game Executable '{altGameProcessToMonitor}' processes. Reverting back to monitoring Game executables '{gameToRun.ProcessName}' instead.");
|
|
|
|
// we wait until the game has started running (*allows for updates to occur)
|
|
for (int secs = 0; secs <= (shortcutToUse.StartTimeout * 1000); secs += 500)
|
|
{
|
|
|
|
if (gameToRun.IsRunning)
|
|
{
|
|
// The game is running! So now we continue processing
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Found the '{gameToRun.Name}' process has started");
|
|
break;
|
|
}
|
|
|
|
// Delay 500ms
|
|
Thread.Sleep(500);
|
|
|
|
}
|
|
|
|
// If the game still isn't running then there is an issue so tell the user and revert things back
|
|
if (!gameToRun.IsRunning)
|
|
{
|
|
logger.Error($"ShortcutRepository/RunShortcut: The Game {gameToRun.Name} didn't start for some reason (or the game uses a starter exe that launches the game itself)! so reverting changes back if needed...");
|
|
logger.Warn($"ShortcutRepository/RunShortcut: We were monitoring {gameToRun.ExePath}. You may need to manually add an alternative game executable to monitor - please run the game manually and check if another executable in {Path.GetDirectoryName(gameToRun.ExePath)} is run, and then monitor that instead.");
|
|
// Now we want to tell the user we couldn't start the game!
|
|
// Construct the Windows toast content
|
|
tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo($"notify=errorRunning{gameLibraryToUse.GameLibraryName}Game", ToastActivationType.Foreground)
|
|
.AddText($"Could not detect {shortcutToUse.GameName} starting", hintMaxLines: 1)
|
|
.AddText($"Could not detect {shortcutToUse.GameName} Game starting, so reverting changes back if needed. You may need to monitor a different game executable.");
|
|
//.AddButton("Stop", ToastActivationType.Background, "notify=runningGame&action=stop");
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show this notification
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
|
|
}
|
|
else
|
|
{
|
|
// The game has started correctly so we continue to monitor it!
|
|
|
|
// Tell the user
|
|
// Now we want to tell the user we're running a game!
|
|
// Construct the Windows toast content
|
|
tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo($"notify=running{gameLibraryToUse.GameLibraryName}Game", ToastActivationType.Foreground)
|
|
.AddText($"Running {shortcutToUse.GameName}", hintMaxLines: 1)
|
|
.AddText($"Waiting for the {gameToRun.ProcessName} game process to exit as {altGameProcessToMonitor} alternative game executable wasn't found...");
|
|
//.AddButton("Stop", ToastActivationType.Background, "notify=runningGame&action=stop");
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show this notification
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
|
|
// This is the main waiting thread!
|
|
// Wait for the game to exit
|
|
logger.Debug($"ShortcutRepository/RunShortcut: waiting for {gameLibraryToUse.GameLibraryName} Game {gameToRun.Name} to exit.");
|
|
while (true)
|
|
{
|
|
if (!gameToRun.IsRunning)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: {gameLibraryToUse.GameLibraryName} Game {gameToRun.Name} is no longer running (IsRunning is false).");
|
|
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(1000);
|
|
}
|
|
logger.Debug($"ShortcutRepository/RunShortcut: {gameLibraryToUse.GameLibraryName} Game {gameToRun.Name} has exited.");
|
|
|
|
// Tell the user that the Game has closed
|
|
// Construct the toast content
|
|
tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo("notify=stopDetected", ToastActivationType.Foreground)
|
|
.AddText($"{shortcutToUse.GameName} was closed", hintMaxLines: 1)
|
|
.AddText($"{shortcutToUse.GameName} game was exited.")
|
|
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show it
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we found alternative game executable processes, so we'll just monitor them
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Waiting for alternative game proocess {altGameProcessToMonitor} to exit.");
|
|
logger.Debug($"ShortcutRepository/RunShortcut: {processesToMonitor.Count} Alternative Game Executable '{altGameProcessToMonitor}' processes are still running");
|
|
|
|
// Now we want to tell the user we're monitoring the alternative executables!
|
|
// Construct the Windows toast content
|
|
tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo($"notify=running{gameLibraryToUse.GameLibraryName}Game", ToastActivationType.Foreground)
|
|
.AddText($"Running {shortcutToUse.GameName}", hintMaxLines: 1)
|
|
.AddText($"Waiting for the {altGameProcessToMonitor} alternative game process to exit...")
|
|
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
|
|
//.AddButton("Stop", ToastActivationType.Background, "notify=runningGame&action=stop");
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show this notification
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
|
|
while (true)
|
|
{
|
|
processesToMonitor = Process.GetProcessesByName(altGameProcessToMonitor).ToList();
|
|
|
|
// If we have no more processes left then we're done!
|
|
if (processesToMonitor.Count == 0)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: No more '{altGameProcessToMonitor}' processes are still running");
|
|
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(1000);
|
|
}
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Alternative Game Executable {altGameProcessToMonitor} has exited.");
|
|
// Tell the user that the Alt Game Executable has closed
|
|
// Construct the toast content
|
|
tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo("notify=stopDetected", ToastActivationType.Foreground)
|
|
.AddText($"{altGameProcessToMonitor} was closed", hintMaxLines: 1)
|
|
.AddText($"{altGameProcessToMonitor} alternative game executable was exited.")
|
|
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show it
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// we are monitoring the game thats actually running (the most common scenario)
|
|
|
|
// Add a status notification icon in the status area
|
|
if (gameToRun.Name.Length <= 41)
|
|
notifyIcon.Text = $"DisplayMagician: Running {gameToRun.Name}...";
|
|
else
|
|
notifyIcon.Text = $"DisplayMagician: Running {gameToRun.Name.Substring(0, 41)}...";
|
|
Application.DoEvents();
|
|
|
|
// Now we want to tell the user we're running a game!
|
|
// Construct the Windows toast content
|
|
tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo($"notify=running{gameLibraryToUse.GameLibraryName}Game", ToastActivationType.Foreground)
|
|
.AddText($"Running {shortcutToUse.GameName}", hintMaxLines: 1)
|
|
.AddText($"Waiting for the {gameLibraryToUse.GameLibraryName} Game {gameToRun.Name} to exit...")
|
|
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
|
|
//.AddButton("Stop", ToastActivationType.Background, "notify=runningGame&action=stop");
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show this notification
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
|
|
|
|
// Now we know the game library app is running then
|
|
// we wait until the game has started running (*allows for updates to occur)
|
|
for (int secs = 0; secs <= (shortcutToUse.StartTimeout * 1000); secs += 500)
|
|
{
|
|
|
|
if (gameToRun.IsRunning)
|
|
{
|
|
// The game is running! So now we continue processing
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Found the '{gameToRun.Name}' process has started");
|
|
break;
|
|
}
|
|
|
|
// Delay 500ms
|
|
Thread.Sleep(500);
|
|
|
|
}
|
|
|
|
// If the game still isn't running then there is an issue so tell the user and revert things back
|
|
if (!gameToRun.IsRunning)
|
|
{
|
|
logger.Error($"ShortcutRepository/RunShortcut: The Game {gameToRun.Name} didn't start for some reason (or the game uses a starter exe that launches the game itself)! so reverting changes back if needed...");
|
|
logger.Warn($"ShortcutRepository/RunShortcut: We were monitoring {gameToRun.ExePath}. You may need to manually add an alternative game executable to monitor - please run the game manually and check if another executable in {Path.GetDirectoryName(gameToRun.ExePath)} is run, and then monitor that instead.");
|
|
// Now we want to tell the user we couldn't start the game!
|
|
// Construct the Windows toast content
|
|
tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo($"notify=errorRunning{gameLibraryToUse.GameLibraryName}Game", ToastActivationType.Foreground)
|
|
.AddText($"Could not detect {shortcutToUse.GameName} starting", hintMaxLines: 1)
|
|
.AddText($"Could not detect {shortcutToUse.GameName} Game starting, so reverting changes back if needed. You may need to monitor a different game executable.");
|
|
//.AddButton("Stop", ToastActivationType.Background, "notify=runningGame&action=stop");
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show this notification
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
|
|
}
|
|
else
|
|
{
|
|
// This is the main waiting thread!
|
|
// Wait for the game to exit
|
|
logger.Debug($"ShortcutRepository/RunShortcut: waiting for {gameLibraryToUse.GameLibraryName} Game {gameToRun.Name} to exit.");
|
|
while (true)
|
|
{
|
|
if (!gameToRun.IsRunning)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: {gameLibraryToUse.GameLibraryName} Game {gameToRun.Name} is no longer running (IsRunning is false).");
|
|
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(1000);
|
|
}
|
|
logger.Debug($"ShortcutRepository/RunShortcut: {gameLibraryToUse.GameLibraryName} Game {gameToRun.Name} has exited.");
|
|
|
|
// Tell the user that the Game has closed
|
|
// Construct the toast content
|
|
tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo("notify=stopDetected", ToastActivationType.Foreground)
|
|
.AddText($"{shortcutToUse.GameName} was closed", hintMaxLines: 1)
|
|
.AddText($"{shortcutToUse.GameName} game was exited.")
|
|
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
|
|
toastContent = tcBuilder.Content;
|
|
// Make sure to use Windows.Data.Xml.Dom
|
|
doc = new XmlDocument();
|
|
doc.LoadXml(toastContent.GetContent());
|
|
// And create the toast notification
|
|
toast = new ToastNotification(doc);
|
|
// Remove any other Notifications from us
|
|
DesktopNotifications.DesktopNotificationManagerCompat.History.Clear();
|
|
// And then show it
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
}
|
|
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Error($"ShortcutRepository/RunShortcut: Error starting the {gameToRun.Name} {gameToRun.GameLibrary} Game as the game wasn't found.");
|
|
}
|
|
}
|
|
|
|
|
|
// Remove the status notification icon from the status area
|
|
// once we've exited the game, but only if its a game or app
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Changing the system tray icon message back to what it was.");
|
|
if (temporaryNotifyIcon)
|
|
{
|
|
|
|
if (!shortcutToUse.Category.Equals(ShortcutCategory.NoGame))
|
|
{
|
|
if (notifyIcon != null)
|
|
{
|
|
notifyIcon.Visible = false;
|
|
notifyIcon.Dispose();
|
|
Application.DoEvents();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we're running the shortcut from the ShortcutLibrary
|
|
// then we want to reset the NotifyIcon back
|
|
notifyIcon.Text = oldNotifyText;
|
|
notifyIcon.ContextMenuStrip = oldContextMenuStrip;
|
|
Application.DoEvents();
|
|
}
|
|
|
|
// Only replace the notification if we're minimised
|
|
if (Program.AppProgramSettings.MinimiseOnStart)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: We're minimised, so we also need to update the Windows notification content");
|
|
// Remind the user that DisplayMagician is running the in background
|
|
// Construct the toast content
|
|
ToastContentBuilder tcBuilder = new ToastContentBuilder()
|
|
.AddToastActivationInfo("notify=minimiseStart&action=open", ToastActivationType.Foreground)
|
|
.AddText("DisplayMagician is minimised", hintMaxLines: 1)
|
|
.AddButton("Open", ToastActivationType.Background, "notify=minimiseStart&action=open")
|
|
.AddAudio(new Uri("ms-winsoundevent:Notification.Default"), false, true);
|
|
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)
|
|
{
|
|
SuppressPopup = true
|
|
};
|
|
|
|
// And then show it
|
|
DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
|
|
}
|
|
|
|
|
|
// Stop the pre-started startPrograms that we'd started earlier
|
|
if (startProgramsToStop.Count > 0)
|
|
{
|
|
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.Initialise();
|
|
|
|
// 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
|
|
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;
|
|
}
|
|
}
|
|
|
|
// 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
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 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
|
|
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;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// Change Audio Device back (if one specified)
|
|
if (activeAudioDevices.Count > 0)
|
|
{
|
|
if (needToChangeAudioDevice && shortcutToUse.AudioPermanence == ShortcutPermanence.Temporary)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Reverting default audio back to {rollbackAudioDevice.Name} audio device");
|
|
// use the Audio Device
|
|
rollbackAudioDevice.SetAsDefault();
|
|
|
|
if (shortcutToUse.SetAudioVolume)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Reverting default audio volume back to {shortcutToUse.SetAudioVolume}% volume");
|
|
Task myTask = new Task(() =>
|
|
{
|
|
rollbackAudioDevice.SetVolumeAsync(Convert.ToDouble(rollbackAudioVolume));
|
|
});
|
|
myTask.Start();
|
|
myTask.Wait(2000);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Shortcut did not require changing Audio Device, so no need to change it back.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: No Audio Devices active, so no need to change them back.");
|
|
}
|
|
|
|
|
|
// Change Capture Device back (if one specified)
|
|
if (activeCaptureDevices.Count > 0)
|
|
{
|
|
if (needToChangeCaptureDevice && shortcutToUse.CapturePermanence == ShortcutPermanence.Temporary)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Reverting default capture (microphone) device back to {rollbackCaptureDevice.Name} capture device");
|
|
// use the Audio Device
|
|
rollbackCaptureDevice.SetAsDefault();
|
|
|
|
if (shortcutToUse.SetCaptureVolume)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Reverting default capture (microphone) volume back to {shortcutToUse.SetAudioVolume}% volume");
|
|
Task myTask = new Task(() =>
|
|
{
|
|
rollbackCaptureDevice.SetVolumeAsync(Convert.ToDouble(rollbackCaptureVolume));
|
|
});
|
|
myTask.Start();
|
|
myTask.Wait(2000);
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Shortcut did not require changing Capture Device, so no need to change it back.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: No Capture Devices active, so no need to change them back.");
|
|
}
|
|
|
|
// Change back to the original profile only if it is different
|
|
// And if we're temporary
|
|
if (needToChangeProfiles && shortcutToUse.DisplayPermanence == ShortcutPermanence.Temporary)
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Rolling back display profile to {rollbackProfile.Name}");
|
|
|
|
ApplyProfileResult result = Program.ApplyProfile(rollbackProfile);
|
|
|
|
if (result == ApplyProfileResult.Error)
|
|
{
|
|
Console.WriteLine($"ERROR - Cannot revert back to '{rollbackProfile.Name}' Display Profile");
|
|
logger.Error($"ShortcutRepository/RunShortcut: Error rolling back display profile to {rollbackProfile.Name}");
|
|
return;
|
|
}
|
|
else if (result == ApplyProfileResult.Cancelled)
|
|
{
|
|
Console.WriteLine($"ERROR - User cancelled revert back to '{rollbackProfile.Name}' Display Profile");
|
|
logger.Error($"ShortcutRepository/RunShortcut: User cancelled rolling back display profile to {rollbackProfile.Name}");
|
|
return;
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
logger.Debug($"ShortcutRepository/RunShortcut: Shortcut did not require changing Display Profile, so no need to change it back.");
|
|
}
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
[global::System.Serializable]
|
|
public class ShortcutRepositoryException : Exception
|
|
{
|
|
public ShortcutRepositoryException() { }
|
|
public ShortcutRepositoryException(string message) : base(message) { }
|
|
public ShortcutRepositoryException(string message, Exception inner) : base(message, inner) { }
|
|
protected ShortcutRepositoryException(
|
|
System.Runtime.Serialization.SerializationInfo info,
|
|
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
|
}
|
|
|
|
}
|
|
|