mirror of
https://github.com/terrymacdonald/DisplayMagician.git
synced 2024-08-30 18:32:20 +00:00
The RunShortcut commandline option mostly works but it fails to cleanly execute thanks to a partially broken Program.ApplyProfile. The ApplyProfile exception logic is not working properly and that is causing an exception in Program.RunShortcut. Just needs some improved ApplyProfile logic to detect when it does actually (correctly) fail.
432 lines
18 KiB
C#
432 lines
18 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.ComponentModel.DataAnnotations;
|
||
using McMaster.Extensions.CommandLineUtils;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Net;
|
||
using System.Reflection;
|
||
using System.Runtime.CompilerServices;
|
||
using System.Threading;
|
||
using System.Threading.Tasks;
|
||
using System.Windows.Forms;
|
||
using HeliosPlus.InterProcess;
|
||
using HeliosPlus.Resources;
|
||
using HeliosPlus.GameLibraries;
|
||
using HeliosPlus.Shared;
|
||
using HeliosPlus.UIForms;
|
||
using System.Net.NetworkInformation;
|
||
using System.Text.RegularExpressions;
|
||
using System.Drawing;
|
||
using System.Diagnostics.Contracts;
|
||
|
||
namespace HeliosPlus {
|
||
|
||
public enum SupportedGameLibrary
|
||
{
|
||
Unknown,
|
||
Steam,
|
||
Uplay
|
||
}
|
||
|
||
internal static class Program
|
||
{
|
||
|
||
internal static string AppDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "HeliosPlus");
|
||
public static string AppIconPath = Path.Combine(Program.AppDataPath, $"Icons");
|
||
public static string AppProfilePath = Path.Combine(Program.AppDataPath, $"Profiles");
|
||
public static string AppShortcutPath = Path.Combine(Program.AppDataPath, $"Shortcuts");
|
||
public static string AppHeliosPlusIconFilename = Path.Combine(AppIconPath, @"HeliosPlus.ico");
|
||
public static string AppOriginIconFilename = Path.Combine(AppIconPath, @"Origin.ico");
|
||
public static string AppSteamIconFilename = Path.Combine(AppIconPath, @"Steam.ico");
|
||
public static string AppUplayIconFilename = Path.Combine(AppIconPath, @"Steam.ico");
|
||
public static string AppEpicIconFilename = Path.Combine(AppIconPath, @"Epic.ico");
|
||
//internal static string ShortcutIconCachePath;
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// The main entry point for the application.
|
||
/// </summary>
|
||
[STAThread]
|
||
private static int Main(string[] args)
|
||
{
|
||
|
||
// Write the Application Name
|
||
Console.WriteLine($"{Application.ProductName} v{Application.ProductVersion}");
|
||
for (int i = 0; i <= Application.ProductName.Length + Application.ProductVersion .Length; i++)
|
||
{
|
||
Console.Write("=");
|
||
}
|
||
Console.WriteLine("=");
|
||
Console.WriteLine($"Copyright <20> Terry MacDonald 2020-{DateTime.Today.Year}");
|
||
Console.WriteLine(@"Based on Helios Display Management - Copyright <20> Soroush Falahati 2017-2020");
|
||
|
||
var app = new CommandLineApplication();
|
||
|
||
//app.Name = "HeliosDM+";
|
||
//app.Name = Assembly.GetEntryAssembly().GetName().Name;
|
||
app.Description = "This application helps configure your NVIDIA Videocard for multiple displays.";
|
||
app.ExtendedHelpText = "This application helps configure your NVIDIA Videocard for multiple displays. It has some nifty features such as the "
|
||
+ Environment.NewLine + " ability to temporarily change your screen settings while you are playing a game, and then change them back once finished.";
|
||
|
||
app.GetFullNameAndVersion();
|
||
app.MakeSuggestionsInErrorMessage = true;
|
||
app.HelpOption("-?|-h|--help", inherited:true);
|
||
|
||
app.VersionOption("-v|--version", () => {
|
||
return string.Format("Version {0}", Assembly.GetExecutingAssembly().GetName().Version);
|
||
});
|
||
|
||
// This is the RunShortcut command
|
||
app.Command(HeliosStartupAction.RunShortcut.ToString(), (runShortcutCmd) =>
|
||
{
|
||
var argumentShortcut = runShortcutCmd.Argument("\"SHORTCUT_UUID\"", "(required) The UUID of the shortcut to run from those stored in the shortcut library.").IsRequired();
|
||
argumentShortcut.Validators.Add(new ShortcutMustExistValidator());
|
||
|
||
//description and help text of the command.
|
||
runShortcutCmd.Description = "Use this command to run favourite game or application with a display profile of your choosing.";
|
||
|
||
runShortcutCmd.OnExecute(() =>
|
||
{
|
||
//
|
||
RunShortcut(argumentShortcut.Value);
|
||
return 0;
|
||
});
|
||
});
|
||
|
||
app.OnExecute(() =>
|
||
{
|
||
|
||
Console.WriteLine("Starting Normally...");
|
||
StartUpNormally();
|
||
return 0;
|
||
});
|
||
|
||
|
||
try
|
||
{
|
||
// This begins the actual execution of the application
|
||
app.Execute(args);
|
||
}
|
||
catch (CommandParsingException ex)
|
||
{
|
||
//Console.WriteLine($"Program/Main commandParsingException: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
|
||
// You'll always want to catch this exception, otherwise it will generate a messy and confusing error for the end user.
|
||
// the message will usually be something like:
|
||
// "Unrecognized command or argument '<invalid-command>'"
|
||
//Console.WriteLine(ex.Message);
|
||
Console.WriteLine("Didn't recognise the supplied commandline options: {0}", ex.Message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"Program/Main exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
|
||
Console.WriteLine("Unable to execute application: {0}", ex.Message);
|
||
}
|
||
return 0;
|
||
//return app.Execute(args);
|
||
}
|
||
|
||
private static void StartUpNormally()
|
||
{
|
||
|
||
|
||
Application.EnableVisualStyles();
|
||
Application.SetCompatibleTextRenderingDefault(false);
|
||
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
|
||
|
||
try
|
||
{
|
||
// Start the IPC Service to
|
||
if (!IPCService.StartService())
|
||
{
|
||
throw new Exception(Language.Can_not_open_a_named_pipe_for_Inter_process_communication);
|
||
}
|
||
|
||
|
||
// Create the Shortcut Icon Cache if it doesn't exist so that it's avilable for all the program
|
||
if (!Directory.Exists(AppIconPath))
|
||
{
|
||
try
|
||
{
|
||
Directory.CreateDirectory(AppIconPath);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"Program/StartUpNormally exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
|
||
// TODO
|
||
}
|
||
}
|
||
|
||
|
||
// Create the Shortcut Icon Cache if it doesn't exist so that it's avilable for all the program
|
||
if (!Directory.Exists(AppIconPath))
|
||
{
|
||
try
|
||
{
|
||
Directory.CreateDirectory(AppIconPath);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"Program/StartUpNormally exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
|
||
// TODO
|
||
}
|
||
}
|
||
|
||
try
|
||
{
|
||
// Save a copy of the HeliosPlus Icon, and all the game library ones in preparation for future use
|
||
if (!File.Exists(AppHeliosPlusIconFilename))
|
||
{
|
||
Icon heliosIcon = (Icon)Properties.Resources.HeliosPlus;
|
||
using (FileStream fs = new FileStream(AppHeliosPlusIconFilename, FileMode.Create))
|
||
heliosIcon.Save(fs);
|
||
}
|
||
|
||
// Save a copy of the Steam Icon, and all the game library ones in preparation for future use
|
||
if (!File.Exists(AppSteamIconFilename))
|
||
{
|
||
Icon heliosIcon = (Icon)Properties.Resources.Steam;
|
||
using (FileStream fs = new FileStream(AppSteamIconFilename, FileMode.Create))
|
||
heliosIcon.Save(fs);
|
||
}
|
||
|
||
// Save a copy of the Uplay Icon, and all the game library ones in preparation for future use
|
||
if (!File.Exists(AppUplayIconFilename))
|
||
{
|
||
Icon heliosIcon = (Icon)Properties.Resources.Uplay;
|
||
using (FileStream fs = new FileStream(AppUplayIconFilename, FileMode.Create))
|
||
heliosIcon.Save(fs);
|
||
}
|
||
|
||
// Save a copy of the Epic Icon, and all the game library ones in preparation for future use
|
||
if (!File.Exists(AppEpicIconFilename))
|
||
{
|
||
Icon heliosIcon = (Icon)Properties.Resources.Epic;
|
||
using (FileStream fs = new FileStream(AppEpicIconFilename, FileMode.Create))
|
||
heliosIcon.Save(fs);
|
||
}
|
||
|
||
// Save a copy of the Origin Icon, and all the game library ones in preparation for future use
|
||
if (!File.Exists(AppOriginIconFilename))
|
||
{
|
||
Icon heliosIcon = (Icon)Properties.Resources.Origin;
|
||
using (FileStream fs = new FileStream(AppOriginIconFilename, FileMode.Create))
|
||
heliosIcon.Save(fs);
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"Program/StartUpNormally exception 2: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
|
||
// TODO
|
||
}
|
||
|
||
IPCService.GetInstance().Status = InstanceStatus.User;
|
||
Application.Run(new UIForms.MainForm());
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"Program/StartUpNormally exception 3: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
|
||
MessageBox.Show(
|
||
ex.Message,
|
||
Language.Fatal_Error,
|
||
MessageBoxButtons.OK,
|
||
MessageBoxIcon.Error);
|
||
}
|
||
|
||
}
|
||
|
||
|
||
// ReSharper disable once CyclomaticComplexity
|
||
private static void RunShortcut(string shortcutUUID)
|
||
{
|
||
ShortcutItem shortcutToRun = null;
|
||
|
||
// Check there is only one version of this application so we won't
|
||
// mess with another monitoring session
|
||
if (
|
||
IPCClient.QueryAll()
|
||
.Any(
|
||
client =>
|
||
client.Status == InstanceStatus.Busy ||
|
||
client.Status == InstanceStatus.OnHold))
|
||
{
|
||
throw new Exception(
|
||
Language
|
||
.Another_instance_of_this_program_is_in_working_state_Please_close_other_instances_before_trying_to_switch_profile);
|
||
}
|
||
|
||
// Match the ShortcutName to the actual shortcut listed in the shortcut library
|
||
// And error if we can't find it.
|
||
if (ShortcutRepository.ContainsShortcut(shortcutUUID))
|
||
{
|
||
// make sure we trim the "" if there are any
|
||
shortcutUUID = shortcutUUID.Trim('"');
|
||
shortcutToRun = ShortcutRepository.GetShortcut(shortcutUUID);
|
||
}
|
||
else
|
||
{
|
||
throw new Exception(Language.Cannot_find_shortcut_in_library);
|
||
}
|
||
|
||
if (shortcutToRun is ShortcutItem)
|
||
{
|
||
ShortcutRepository.RunShortcut(shortcutToRun);
|
||
}
|
||
|
||
IPCService.GetInstance().Status = InstanceStatus.Busy;
|
||
|
||
}
|
||
|
||
public static bool IsValidFilename(string testName)
|
||
{
|
||
string strTheseAreInvalidFileNameChars = new string(Path.GetInvalidFileNameChars());
|
||
Regex regInvalidFileName = new Regex("[" + Regex.Escape(strTheseAreInvalidFileNameChars) + "]");
|
||
|
||
if (regInvalidFileName.IsMatch(testName)) { return false; };
|
||
|
||
return true;
|
||
}
|
||
|
||
// ApplyProfile lives here so that the UI works.
|
||
public static bool ApplyProfile(ProfileItem profile)
|
||
{
|
||
// If we're already on the wanted profile then no need to change!
|
||
if (ProfileRepository.IsActiveProfile(profile))
|
||
return true;
|
||
|
||
// We need to check if the profile is valid
|
||
if (!profile.IsPossible)
|
||
return false;
|
||
|
||
try
|
||
{
|
||
// Now lets prepare changing the display topology task
|
||
Task applyTopologyTask = new Task(() =>
|
||
{
|
||
Console.WriteLine("Program/ApplyProfile : Applying Profile Topology " + profile.Name);
|
||
if (!ProfileRepository.ApplyNVIDIAGridTopology(profile))
|
||
{
|
||
// Somehow return that this profile topology didn't apply
|
||
}
|
||
});
|
||
|
||
Task applyPathInfoTask = new Task(() => {
|
||
Console.WriteLine("Program/ApplyProfile : Applying Profile Path " + profile.Name);
|
||
if (!ProfileRepository.ApplyWindowsDisplayPathInfo(profile))
|
||
{
|
||
// Somehow return that this profile path info didn't apply
|
||
}
|
||
|
||
});
|
||
|
||
// Set up the UI forms to show
|
||
ApplyingProfileForm timeoutForm = new ApplyingProfileForm(null, 3, $"Changing to '{profile.Name}' Profile", "Press ESC to cancel", Color.Orange, true);
|
||
ApplyingProfileForm topologyForm = new ApplyingProfileForm(applyTopologyTask, 30, $"Changing to '{profile.Name}' Profile", "Applying NVIDIA Grid Topology", Color.Aquamarine);
|
||
ApplyingProfileForm pathInfoForm = new ApplyingProfileForm(applyPathInfoTask, 15, $"Changing to '{profile.Name}' Profile", "Adjusting Windows Display Device positions", Color.LawnGreen);
|
||
|
||
if (timeoutForm.ShowDialog() == DialogResult.Cancel)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// We only want to do the topology change if the profile we're on now
|
||
// or the profile we're going to are NVIDIA surround profiles
|
||
int toProfileSurroundTopologyCount =
|
||
profile.Paths.SelectMany(paths => paths.TargetDisplays)
|
||
.Select(target => target.SurroundTopology)
|
||
.Where(topology => topology != null)
|
||
.Select(topology => topology.ToGridTopology())
|
||
.Count();
|
||
int fromProfileSurroundTopologyCount =
|
||
ProfileRepository.CurrentProfile.Paths.SelectMany(paths => paths.TargetDisplays)
|
||
.Select(target => target.SurroundTopology)
|
||
.Where(topology => topology != null)
|
||
.Select(topology => topology.ToGridTopology())
|
||
.Count();
|
||
|
||
if (toProfileSurroundTopologyCount > 0 || fromProfileSurroundTopologyCount > 0)
|
||
{
|
||
topologyForm.ShowDialog();
|
||
|
||
try
|
||
{
|
||
applyTopologyTask.Wait();
|
||
}
|
||
catch (AggregateException ae)
|
||
{
|
||
foreach (var e in ae.InnerExceptions)
|
||
{
|
||
// Handle the custom exception.
|
||
if (e is ApplyTopologyException)
|
||
{
|
||
Console.WriteLine(e.Message);
|
||
}
|
||
// Rethrow any other exception.
|
||
else
|
||
{
|
||
throw;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (applyTopologyTask.IsFaulted)
|
||
Console.WriteLine("Program/ApplyProfile : Applying Profile Topology stage failed to complete");
|
||
}
|
||
|
||
// We always want to do the WindowsDisplayAPI PathInfo part
|
||
pathInfoForm.ShowDialog();
|
||
applyPathInfoTask.Wait();
|
||
try
|
||
{
|
||
applyPathInfoTask.Wait();
|
||
}
|
||
catch (AggregateException ae)
|
||
{
|
||
foreach (var e in ae.InnerExceptions)
|
||
{
|
||
// Handle the custom exception.
|
||
if (e is ApplyPathInfoException)
|
||
{
|
||
Console.WriteLine(e.Message);
|
||
}
|
||
// Rethrow any other exception.
|
||
else
|
||
{
|
||
throw;
|
||
}
|
||
}
|
||
}
|
||
|
||
if (applyPathInfoTask.IsFaulted)
|
||
Console.WriteLine("Program/ApplyProfile : Applying Profile PathInfo stage failed to complete");
|
||
|
||
if (applyTopologyTask.IsCompleted && applyPathInfoTask.IsCompleted)
|
||
return true;
|
||
else
|
||
return false;
|
||
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Console.WriteLine($"ProfileRepository/ApplyTopology exception: {ex.Message}: {ex.StackTrace} - {ex.InnerException}");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
public class ApplyTopologyException : Exception
|
||
{
|
||
public ApplyTopologyException(String message) : base(message)
|
||
{ }
|
||
}
|
||
|
||
public class ApplyPathInfoException : Exception
|
||
{
|
||
public ApplyPathInfoException(String message) : base(message)
|
||
{ }
|
||
}
|
||
} |