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

Core - Moved startup arguments from CoreService to Constants

Web server - Added setting to disable the web server
Web server - Added --disable-webserver startup argument to disable the web server
UI - Fixed Artemis not bringing existing instances to foreground if already running
This commit is contained in:
Robert 2022-08-26 20:32:36 +02:00
parent 87b87a9145
commit 928d9711af
14 changed files with 516 additions and 455 deletions

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using Artemis.Core.JsonConverters;
@ -90,6 +91,11 @@ public static class Constants
/// </summary>
public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null);
/// <summary>
/// Gets the startup arguments provided to the application
/// </summary>
public static ReadOnlyCollection<string> StartupArguments { get; set; } = null!;
internal static readonly CorePluginFeature CorePluginFeature = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Core")};
internal static readonly EffectPlaceholderPlugin EffectPlaceholderPlugin = new() {Plugin = CorePlugin, Profiler = CorePlugin.GetProfiler("Feature - Effect Placeholder")};

View File

@ -58,7 +58,6 @@ internal class CoreService : ICoreService
_scriptingService = scriptingService;
_loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug);
_frameStopWatch = new Stopwatch();
StartupArguments = new List<string>();
_rgbService.Surface.Updating += SurfaceOnUpdating;
_loggingLevel.SettingChanged += (sender, args) => ApplyLoggingLevel();
@ -78,7 +77,7 @@ internal class CoreService : ICoreService
private void ApplyLoggingLevel()
{
string? argument = StartupArguments.FirstOrDefault(a => a.StartsWith("--logging"));
string? argument = Constants.StartupArguments.FirstOrDefault(a => a.StartsWith("--logging"));
if (argument != null)
{
// Parse the provided log level
@ -194,7 +193,6 @@ internal class CoreService : ICoreService
public int FrameRate { get; private set; }
public TimeSpan FrameTime { get; private set; }
public bool ProfileRenderingDisabled { get; set; }
public List<string> StartupArguments { get; set; }
public bool IsElevated { get; set; }
public void Dispose()
@ -217,7 +215,7 @@ internal class CoreService : ICoreService
Constants.BuildInfo.BuildNumber,
Constants.BuildInfo.SourceBranch
);
_logger.Information("Startup arguments: {args}", StartupArguments);
_logger.Information("Startup arguments: {args}", Constants.StartupArguments);
_logger.Information("Elevated permissions: {perms}", IsElevated);
_logger.Information("Stopwatch high resolution: {perms}", Stopwatch.IsHighResolution);
@ -230,9 +228,9 @@ internal class CoreService : ICoreService
// Initialize the services
_pluginManagementService.CopyBuiltInPlugins();
_pluginManagementService.LoadPlugins(StartupArguments, IsElevated);
_pluginManagementService.LoadPlugins(IsElevated);
_rgbService.ApplyPreferredGraphicsContext(StartupArguments.Contains("--force-software-render"));
_rgbService.ApplyPreferredGraphicsContext(Constants.StartupArguments.Contains("--force-software-render"));
_rgbService.SetRenderPaused(false);
OnInitialized();
}

View File

@ -28,11 +28,6 @@ public interface ICoreService : IArtemisService, IDisposable
/// </summary>
bool ProfileRenderingDisabled { get; set; }
/// <summary>
/// Gets or sets a list of startup arguments
/// </summary>
List<string> StartupArguments { get; set; }
/// <summary>
/// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions)
/// </summary>

View File

@ -26,7 +26,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable
/// <summary>
/// Loads all installed plugins. If plugins already loaded this will reload them all
/// </summary>
void LoadPlugins(List<string> startupArguments, bool isElevated);
void LoadPlugins(bool isElevated);
/// <summary>
/// Unloads all installed plugins.

View File

@ -203,17 +203,17 @@ internal class PluginManagementService : IPluginManagementService
#region Plugins
public void LoadPlugins(List<string> startupArguments, bool isElevated)
public void LoadPlugins(bool isElevated)
{
if (startupArguments.Contains("--no-plugins"))
if (Constants.StartupArguments.Contains("--no-plugins"))
{
_logger.Warning("Artemis launched with --no-plugins, skipping the loading of plugins");
return;
}
bool ignorePluginLock = startupArguments.Contains("--ignore-plugin-lock");
bool stayElevated = startupArguments.Contains("--force-elevation");
bool droppedAdmin = startupArguments.Contains("--dropped-admin");
bool ignorePluginLock = Constants.StartupArguments.Contains("--ignore-plugin-lock");
bool stayElevated = Constants.StartupArguments.Contains("--force-elevation");
bool droppedAdmin = Constants.StartupArguments.Contains("--dropped-admin");
if (LoadingPlugins)
throw new ArtemisCoreException("Cannot load plugins while a previous load hasn't been completed yet.");

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Artemis.Core.Modules;
using EmbedIO;
@ -17,15 +18,19 @@ internal class WebServerService : IWebServerService, IDisposable
private readonly List<WebApiControllerRegistration> _controllers;
private readonly ILogger _logger;
private readonly List<WebModuleRegistration> _modules;
private readonly PluginSetting<bool> _webServerEnabledSetting;
private readonly PluginSetting<int> _webServerPortSetting;
private CancellationTokenSource? _cts;
public WebServerService(ILogger logger, ISettingsService settingsService, IPluginManagementService pluginManagementService)
public WebServerService(ILogger logger, ICoreService coreService, ISettingsService settingsService, IPluginManagementService pluginManagementService)
{
_logger = logger;
_controllers = new List<WebApiControllerRegistration>();
_modules = new List<WebModuleRegistration>();
_webServerEnabledSetting = settingsService.GetSetting("WebServer.Enabled", true);
_webServerPortSetting = settingsService.GetSetting("WebServer.Port", 9696);
_webServerEnabledSetting.SettingChanged += WebServerEnabledSettingOnSettingChanged;
_webServerPortSetting.SettingChanged += WebServerPortSettingOnSettingChanged;
pluginManagementService.PluginFeatureDisabled += PluginManagementServiceOnPluginFeatureDisabled;
@ -33,9 +38,9 @@ internal class WebServerService : IWebServerService, IDisposable
StartWebServer();
}
protected virtual void OnWebServerStarting()
private void WebServerEnabledSettingOnSettingChanged(object? sender, EventArgs e)
{
WebServerStarting?.Invoke(this, EventArgs.Empty);
StartWebServer();
}
private void WebServerPortSettingOnSettingChanged(object? sender, EventArgs e)
@ -72,14 +77,23 @@ internal class WebServerService : IWebServerService, IDisposable
public WebServer? Server { get; private set; }
public PluginsModule PluginsModule { get; }
public event EventHandler? WebServerStarting;
#region Web server managament
private WebServer CreateWebServer()
{
Server?.Dispose();
if (Server != null)
{
if (_cts != null)
{
_cts.Cancel();
_cts = null;
}
Server.Dispose();
OnWebServerStopped();
Server = null;
}
WebApiModule apiModule = new("/", JsonNetSerializer);
PluginsModule.ServerUrl = $"http://localhost:{_webServerPortSetting.Value}/";
@ -112,8 +126,20 @@ internal class WebServerService : IWebServerService, IDisposable
private void StartWebServer()
{
Server = CreateWebServer();
if (!_webServerEnabledSetting.Value)
return;
if (Constants.StartupArguments.Contains("--disable-webserver"))
{
_logger.Warning("Artemis launched with --disable-webserver, not enabling the webserver");
return;
}
OnWebServerStarting();
Server.Start();
_cts = new CancellationTokenSource();
Server.Start(_cts.Token);
OnWebServerStarted();
}
#endregion
@ -276,4 +302,27 @@ internal class WebServerService : IWebServerService, IDisposable
}
#endregion
#region Events
protected virtual void OnWebServerStopped()
{
WebServerStopped?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnWebServerStarting()
{
WebServerStarting?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnWebServerStarted()
{
WebServerStarted?.Invoke(this, EventArgs.Empty);
}
public event EventHandler? WebServerStopped;
public event EventHandler? WebServerStarting;
public event EventHandler? WebServerStarted;
#endregion
}

View File

@ -1,3 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Windows.Ninject;
using Artemis.UI.Windows.Providers.Input;
@ -13,14 +21,23 @@ namespace Artemis.UI.Windows;
public class App : Application
{
private StandardKernel? _kernel;
private bool _shutDown;
// ReSharper disable NotAccessedField.Local
private ApplicationStateManager? _applicationStateManager;
private Mutex? _artemisMutex;
// ReSharper restore NotAccessedField.Local
private StandardKernel? _kernel;
public override void Initialize()
{
// If Artemis is already running, bring it to foreground and stop this process
if (FocusExistingInstance())
{
_shutDown = true;
Environment.Exit(1);
}
_kernel = ArtemisBootstrapper.Bootstrap(this, new WindowsModule());
Program.CreateLogger(_kernel);
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
@ -29,7 +46,7 @@ public class App : Application
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode)
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown)
return;
ArtemisBootstrapper.Initialize();
@ -42,4 +59,55 @@ public class App : Application
IInputService inputService = standardKernel.Get<IInputService>();
inputService.AddInputProvider(standardKernel.Get<WindowsInputProvider>());
}
private bool FocusExistingInstance()
{
_artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535f", out bool createdNew);
return !createdNew && RemoteFocus();
}
private bool 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")))
{
KillOtherInstances();
return false;
}
string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt"));
using HttpClient client = new();
try
{
CancellationTokenSource cts = new();
cts.CancelAfter(2000);
HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token);
httpResponseMessage.EnsureSuccessStatusCode();
return true;
}
catch (Exception)
{
KillOtherInstances();
return false;
}
}
private void KillOtherInstances()
{
// Kill everything else heh
List<Process> processes = Process.GetProcessesByName("Artemis.UI.Windows").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList();
foreach (Process process in processes)
{
try
{
process.Kill(true);
}
catch (Exception)
{
// ignored
}
}
}
}

View File

@ -3,12 +3,9 @@ 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 Artemis.Core;
using Artemis.Core.Services;
using Artemis.UI.Shared.Services;
using Artemis.UI.Windows.Utilities;
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
@ -19,14 +16,8 @@ namespace Artemis.UI.Windows;
public class ApplicationStateManager
{
private readonly IWindowService _windowService;
// ReSharper disable once NotAccessedField.Local - Kept in scope to ensure it does not get released
private Mutex? _artemisMutex;
public ApplicationStateManager(IKernel kernel, string[] startupArguments)
{
_windowService = kernel.Get<IWindowService>();
StartupArguments = startupArguments;
IsElevated = new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
@ -51,72 +42,6 @@ public class ApplicationStateManager
public string[] StartupArguments { get; }
public bool IsElevated { get; }
public bool FocusExistingInstance()
{
_artemisMutex = new Mutex(true, "Artemis-3c24b502-64e6-4587-84bf-9072970e535f", out bool createdNew);
if (createdNew)
return false;
return RemoteFocus();
}
public void DisplayException(Exception e)
{
try
{
_windowService.ShowExceptionDialog("An unhandled exception occured", e);
}
catch
{
// ignored, we tried
}
}
private bool 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")))
{
KillOtherInstances();
return false;
}
string url = File.ReadAllText(Path.Combine(Constants.DataFolder, "webserver.txt"));
using HttpClient client = new();
try
{
CancellationTokenSource cts = new();
cts.CancelAfter(2000);
HttpResponseMessage httpResponseMessage = client.Send(new HttpRequestMessage(HttpMethod.Post, url + "remote/bring-to-foreground"), cts.Token);
httpResponseMessage.EnsureSuccessStatusCode();
return true;
}
catch (Exception)
{
KillOtherInstances();
return false;
}
}
private void KillOtherInstances()
{
// Kill everything else heh
List<Process> processes = Process.GetProcessesByName("Artemis.UI.Windows").Where(p => p.Id != Process.GetCurrentProcess().Id).ToList();
foreach (Process process in processes)
{
try
{
process.Kill(true);
}
catch (Exception)
{
// ignored
}
}
}
private void UtilitiesOnRestartRequested(object? sender, RestartEventArgs e)
{
List<string> argsList = new();

View File

@ -1,4 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Artemis.Core;
using Artemis.Core.Ninject;
@ -52,6 +55,8 @@ public static class ArtemisBootstrapper
if (_application.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
return;
Constants.StartupArguments = new ReadOnlyCollection<string>(new List<string>(desktop.Args));
// Don't shut down when the last window closes, we might still be active in the tray
desktop.ShutdownMode = ShutdownMode.OnExplicitShutdown;
// Create the root view model that drives the UI

View File

@ -47,12 +47,12 @@ public class MainWindow : ReactiveCoreWindow<RootViewModel>
private void OnActivated(object? sender, EventArgs e)
{
ViewModel.Focused();
ViewModel?.Focused();
}
private void OnDeactivated(object? sender, EventArgs e)
{
ViewModel.Unfocused();
ViewModel?.Unfocused();
}
private void InitializeComponent()

View File

@ -55,7 +55,6 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
_sidebarVmFactory = sidebarVmFactory;
_lifeTime = (IClassicDesktopStyleApplicationLifetime) Application.Current!.ApplicationLifetime!;
coreService.StartupArguments = _lifeTime.Args.ToList();
mainWindowService.ConfigureMainWindowProvider(this);
DisplayAccordingToSettings();
@ -99,8 +98,8 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi
private void DisplayAccordingToSettings()
{
bool autoRunning = _coreService.StartupArguments.Contains("--autorun");
bool minimized = _coreService.StartupArguments.Contains("--minimized");
bool autoRunning = Constants.StartupArguments.Contains("--autorun");
bool minimized = Constants.StartupArguments.Contains("--minimized");
bool showOnAutoRun = _settingsService.GetSetting("UI.ShowOnStartup", true).Value;
if ((autoRunning && !showOnAutoRun) || minimized)

View File

@ -25,7 +25,8 @@
<StackPanel Grid.Column="0">
<TextBlock>Auto-run on startup</TextBlock>
</StackPanel>
<ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10"/>
<ToggleSwitch Grid.Row="0" Grid.Column="1" VerticalAlignment="Center" IsChecked="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" OnContent="Yes"
OffContent="No" />
</Grid>
<Separator Classes="card-separator" />
@ -34,7 +35,8 @@
<TextBlock>Hide window on auto-run</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10"/>
<ToggleSwitch IsChecked="{CompiledBinding !UIShowOnStartup.Value}" IsEnabled="{CompiledBinding UIAutoRun.Value}" MinWidth="0" Margin="0 -10" OnContent="Yes"
OffContent="No" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
@ -100,9 +102,7 @@
<StackPanel>
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Web server port
</TextBlock>
<TextBlock>Enable web server</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
Artemis runs a local web server that can be used to externally interact with the application.
</TextBlock>
@ -110,6 +110,21 @@
This web server can only be accessed by applications running on your own computer, e.g. supported games.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding WebServerEnabled.Value}" OnContent="Yes" OffContent="No" MinWidth="0" Margin="0 -10" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
<Grid RowDefinitions="*,*" ColumnDefinitions="*,Auto">
<StackPanel Grid.Column="0">
<TextBlock>
Web server port
</TextBlock>
<TextBlock Classes="subtitle" TextWrapping="Wrap">
If the webserver does not work you can try changing the port to one that is available.
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<controls:NumberBox Width="150">
<Interaction.Behaviors>
@ -138,7 +153,7 @@
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" />
<ToggleSwitch IsChecked="{CompiledBinding UICheckForUpdates.Value}" MinWidth="0" OnContent="Yes" OffContent="No" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
@ -153,7 +168,7 @@
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" />
<ToggleSwitch IsEnabled="{CompiledBinding UICheckForUpdates.Value}" IsChecked="{CompiledBinding UIAutoUpdate.Value}" MinWidth="0" OnContent="Yes" OffContent="No" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />
@ -191,7 +206,7 @@
</TextBlock>
</StackPanel>
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
<ToggleSwitch IsChecked="{CompiledBinding ProfileEditorShowDataModelValues.Value}" MinWidth="0" />
<ToggleSwitch IsChecked="{CompiledBinding ProfileEditorShowDataModelValues.Value}" MinWidth="0" OnContent="Yes" OffContent="No" />
</StackPanel>
</Grid>
<Separator Classes="card-separator" />

View File

@ -149,6 +149,7 @@ public class GeneralTabViewModel : ActivatableViewModelBase
public PluginSetting<string> CorePreferredGraphicsContext => _settingsService.GetSetting("Core.PreferredGraphicsContext", "Software");
public PluginSetting<double> CoreRenderScale => _settingsService.GetSetting("Core.RenderScale", 0.25);
public PluginSetting<int> CoreTargetFrameRate => _settingsService.GetSetting("Core.TargetFrameRate", 30);
public PluginSetting<bool> WebServerEnabled => _settingsService.GetSetting("WebServer.Enabled", true);
public PluginSetting<int> WebServerPort => _settingsService.GetSetting("WebServer.Port", 9696);
private void ExecuteShowLogs()

View File

@ -17,7 +17,7 @@ public class SaturateNode : Node
#region Constructors
public SaturateNode()
: base("Clamp", "Clamps the value to be in between 0 and 1")
: base("Saturate", "Clamps the value to be in between 0 and 1")
{
Value = CreateInputPin<Numeric>();