1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Startup - Bring existing instances to foreground/focus them

Bootstrapper - Cleaned up code into a separate state manager
This commit is contained in:
Robert 2021-01-28 19:14:58 +01:00
parent 28e1532064
commit 5c2a96eee0
2 changed files with 137 additions and 75 deletions

View File

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using Artemis.Core;
using Artemis.UI.Utilities;
using Stylet;
namespace Artemis.UI
{
public class ApplicationStateManager
{
// ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released
private Mutex _artemisMutex;
public ApplicationStateManager(string[] startupArguments)
{
StartupArguments = startupArguments;
IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
}
public string[] StartupArguments { get; }
public bool IsElevated { get; }
public bool FocusExistingInstance()
{
_artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535d", out bool createdNew);
if (createdNew)
return false;
try
{
// Blocking is required here otherwise Artemis shuts down before the remote call gets a chance to finish
RemoteFocus().GetAwaiter().GetResult();
}
catch (Exception)
{
// Not much could go wrong here but this code runs so early it'll crash if something does go wrong
return true;
}
return true;
}
private async Task RemoteFocus()
{
// At this point we cannot read the database yet to retrieve the web server port.
// Instead use the method external applications should use as well.
if (!File.Exists(Path.Combine(Constants.DataFolder, "webserver.txt")))
return;
string url = await File.ReadAllTextAsync(Path.Combine(Constants.DataFolder, "webserver.txt"));
using HttpClient client = new();
await client.PostAsync(url + "api/remote/bring-to-foreground", null!);
}
private void UtilitiesOnRestartRequested(object sender, RestartEventArgs e)
{
List<string> argsList = new();
argsList.AddRange(StartupArguments);
if (e.ExtraArgs != null)
argsList.AddRange(e.ExtraArgs.Except(argsList));
string args = argsList.Any() ? "-ArgumentList " + string.Join(',', argsList) : "";
string command =
$"-Command \"& {{Start-Sleep -Milliseconds {(int) e.Delay.TotalMilliseconds}; " +
"(Get-Process 'Artemis.UI').kill(); " +
$"Start-Process -FilePath '{Constants.ExecutablePath}' -WorkingDirectory '{Constants.ApplicationFolder}' {args}}}\"";
// Elevated always runs with RunAs
if (e.Elevate)
{
ProcessStartInfo info = new()
{
Arguments = command.Replace("}\"", " -Verb RunAs}\""),
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
FileName = "PowerShell.exe"
};
Process.Start(info);
}
// Non-elevated runs regularly if currently not elevated
else if (!IsElevated)
{
ProcessStartInfo info = new()
{
Arguments = command,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
FileName = "PowerShell.exe"
};
Process.Start(info);
}
// Non-elevated runs via a utility method is currently elevated (de-elevating is hacky)
else
{
string powerShell = Path.Combine(Environment.SystemDirectory, "WindowsPowerShell", "v1.0", "powershell.exe");
ProcessUtilities.RunAsDesktopUser(powerShell, command, true);
}
// Lets try a graceful shutdown, PowerShell will kill if needed
Execute.OnUIThread(() => Application.Current.Shutdown());
}
private void UtilitiesOnShutdownRequested(object sender, EventArgs e)
{
// Use PowerShell to kill the process after 2 sec just in case
ProcessStartInfo info = new()
{
Arguments = "-Command \"& {Start-Sleep -s 2; (Get-Process 'Artemis.UI').kill()}",
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
FileName = "PowerShell.exe"
};
Process.Start(info);
Execute.OnUIThread(() => Application.Current.Shutdown());
}
}
}

View File

@ -1,16 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Threading;
using Artemis.Core;
using Artemis.Core.Ninject;
using Artemis.Core.Services;
using Artemis.UI.Ninject;
@ -19,7 +14,6 @@ using Artemis.UI.Services;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Stylet;
using Artemis.UI.Utilities;
using Ninject;
using Serilog;
using Stylet;
@ -28,6 +22,7 @@ namespace Artemis.UI
{
public class Bootstrapper : NinjectBootstrapper<TrayViewModel>
{
private ApplicationStateManager _applicationStateManager;
private ICoreService _core;
public static List<string> StartupArguments { get; private set; }
@ -41,14 +36,18 @@ namespace Artemis.UI
protected override void Launch()
{
// TODO: Move shutdown code out of bootstrapper
Core.Utilities.ShutdownRequested += UtilitiesOnShutdownRequested;
Core.Utilities.RestartRequested += UtilitiesOnRestartRequested;
_applicationStateManager = new ApplicationStateManager(Args);
Core.Utilities.PrepareFirstLaunch();
ILogger logger = Kernel.Get<ILogger>();
IViewManager viewManager = Kernel.Get<IViewManager>();
if (_applicationStateManager.FocusExistingInstance())
{
logger.Information("Shutting down because a different instance is already running.");
Application.Current.Shutdown(1);
return;
}
IViewManager viewManager = Kernel.Get<IViewManager>();
StartupArguments = Args.ToList();
// Create the Artemis core
@ -78,7 +77,7 @@ namespace Artemis.UI
try
{
_core.StartupArguments = StartupArguments;
_core.IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
_core.IsElevated = _applicationStateManager.IsElevated;
_core.Initialize();
}
catch (Exception e)
@ -125,67 +124,6 @@ namespace Artemis.UI
e.Handled = true;
}
private void UtilitiesOnShutdownRequested(object sender, EventArgs e)
{
// Use PowerShell to kill the process after 2 sec just in case
ProcessStartInfo info = new()
{
Arguments = "-Command \"& {Start-Sleep -s 2; (Get-Process 'Artemis.UI').kill()}",
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
FileName = "PowerShell.exe"
};
Process.Start(info);
Execute.OnUIThread(() => Application.Current.Shutdown());
}
private void UtilitiesOnRestartRequested(object sender, RestartEventArgs e)
{
List<string> argsList = new();
argsList.AddRange(Args);
if (e.ExtraArgs != null)
argsList.AddRange(e.ExtraArgs.Except(argsList));
string args = argsList.Any() ? "-ArgumentList " + string.Join(',', argsList) : "";
string command =
$"-Command \"& {{Start-Sleep -Milliseconds {(int) e.Delay.TotalMilliseconds}; " +
$"(Get-Process 'Artemis.UI').kill(); " +
$"Start-Process -FilePath '{Constants.ExecutablePath}' -WorkingDirectory '{Constants.ApplicationFolder}' {args}}}\"";
// Elevated always runs with RunAs
if (e.Elevate)
{
ProcessStartInfo info = new()
{
Arguments = command.Replace("}\"", " -Verb RunAs}\""),
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
FileName = "PowerShell.exe"
};
Process.Start(info);
}
// Non-elevated runs regularly if currently not elevated
else if (!_core.IsElevated)
{
ProcessStartInfo info = new()
{
Arguments = command,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
FileName = "PowerShell.exe"
};
Process.Start(info);
}
// Non-elevated runs via a utility method is currently elevated (de-elevating is hacky)
else
{
string powerShell = Path.Combine(Environment.SystemDirectory, "WindowsPowerShell", "v1.0", "powershell.exe");
ProcessUtilities.RunAsDesktopUser(powerShell, command, true);
}
// Lets try a graceful shutdown, PowerShell will kill if needed
Execute.OnUIThread(() => Application.Current.Shutdown());
}
private void HandleFatalException(Exception e, ILogger logger)
{
logger.Fatal(e, "Fatal exception during initialization, shutting down.");
@ -201,8 +139,5 @@ namespace Artemis.UI
Environment.Exit(1);
});
}
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
}
}