diff --git a/src/Artemis.Core/Constants.cs b/src/Artemis.Core/Constants.cs index 15f337748..e37f18ffb 100644 --- a/src/Artemis.Core/Constants.cs +++ b/src/Artemis.Core/Constants.cs @@ -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 /// public static readonly Plugin CorePlugin = new(CorePluginInfo, new DirectoryInfo(ApplicationFolder), null); + /// + /// Gets the startup arguments provided to the application + /// + public static ReadOnlyCollection 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")}; diff --git a/src/Artemis.Core/Services/CoreService.cs b/src/Artemis.Core/Services/CoreService.cs index 3aad1511b..81d8232ab 100644 --- a/src/Artemis.Core/Services/CoreService.cs +++ b/src/Artemis.Core/Services/CoreService.cs @@ -58,7 +58,6 @@ internal class CoreService : ICoreService _scriptingService = scriptingService; _loggingLevel = settingsService.GetSetting("Core.LoggingLevel", LogEventLevel.Debug); _frameStopWatch = new Stopwatch(); - StartupArguments = new List(); _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 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(); } diff --git a/src/Artemis.Core/Services/Interfaces/ICoreService.cs b/src/Artemis.Core/Services/Interfaces/ICoreService.cs index 4eb41b269..8dd297038 100644 --- a/src/Artemis.Core/Services/Interfaces/ICoreService.cs +++ b/src/Artemis.Core/Services/Interfaces/ICoreService.cs @@ -27,12 +27,7 @@ public interface ICoreService : IArtemisService, IDisposable /// Gets or sets whether profiles are rendered each frame by calling their Render method /// bool ProfileRenderingDisabled { get; set; } - - /// - /// Gets or sets a list of startup arguments - /// - List StartupArguments { get; set; } - + /// /// Gets a boolean indicating whether Artemis is running in an elevated environment (admin permissions) /// diff --git a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs index d1281ef8e..209158529 100644 --- a/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs +++ b/src/Artemis.Core/Services/Interfaces/IPluginManagementService.cs @@ -26,7 +26,7 @@ public interface IPluginManagementService : IArtemisService, IDisposable /// /// Loads all installed plugins. If plugins already loaded this will reload them all /// - void LoadPlugins(List startupArguments, bool isElevated); + void LoadPlugins(bool isElevated); /// /// Unloads all installed plugins. diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index 5b1abd65b..c5eba9608 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -203,17 +203,17 @@ internal class PluginManagementService : IPluginManagementService #region Plugins - public void LoadPlugins(List 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."); diff --git a/src/Artemis.Core/Services/WebServer/WebServerService.cs b/src/Artemis.Core/Services/WebServer/WebServerService.cs index 0fffc9570..f082ed6df 100644 --- a/src/Artemis.Core/Services/WebServer/WebServerService.cs +++ b/src/Artemis.Core/Services/WebServer/WebServerService.cs @@ -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 _controllers; private readonly ILogger _logger; private readonly List _modules; + private readonly PluginSetting _webServerEnabledSetting; private readonly PluginSetting _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(); _modules = new List(); + _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(); - Server = null; + 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 } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs index 411bd3227..f82d58f29 100644 --- a/src/Artemis.UI.Windows/App.axaml.cs +++ b/src/Artemis.UI.Windows/App.axaml.cs @@ -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(); inputService.AddInputProvider(standardKernel.Get()); } + + 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 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 + } + } + } } \ No newline at end of file diff --git a/src/Artemis.UI.Windows/ApplicationStateManager.cs b/src/Artemis.UI.Windows/ApplicationStateManager.cs index 6225b10e5..c08f42316 100644 --- a/src/Artemis.UI.Windows/ApplicationStateManager.cs +++ b/src/Artemis.UI.Windows/ApplicationStateManager.cs @@ -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(); 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 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 argsList = new(); diff --git a/src/Artemis.UI/ArtemisBootstrapper.cs b/src/Artemis.UI/ArtemisBootstrapper.cs index 5eac6489d..7bade4f67 100644 --- a/src/Artemis.UI/ArtemisBootstrapper.cs +++ b/src/Artemis.UI/ArtemisBootstrapper.cs @@ -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(new List(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 diff --git a/src/Artemis.UI/MainWindow.axaml.cs b/src/Artemis.UI/MainWindow.axaml.cs index df0371090..a22f46831 100644 --- a/src/Artemis.UI/MainWindow.axaml.cs +++ b/src/Artemis.UI/MainWindow.axaml.cs @@ -47,12 +47,12 @@ public class MainWindow : ReactiveCoreWindow 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() diff --git a/src/Artemis.UI/Screens/Root/RootViewModel.cs b/src/Artemis.UI/Screens/Root/RootViewModel.cs index 0845b50e7..61fbe58c2 100644 --- a/src/Artemis.UI/Screens/Root/RootViewModel.cs +++ b/src/Artemis.UI/Screens/Root/RootViewModel.cs @@ -54,8 +54,7 @@ public class RootViewModel : ActivatableViewModelBase, IScreen, IMainWindowProvi _defaultTitleBarViewModel = defaultTitleBarViewModel; _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) diff --git a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml index 012f083ad..d8cd0ff65 100644 --- a/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml +++ b/src/Artemis.UI/Screens/Settings/Tabs/GeneralTabView.axaml @@ -13,364 +13,379 @@ x:DataType="settings:GeneralTabViewModel"> - - - - General - - - - - - - Auto-run on startup - - - - + + + + General + + + + + + + Auto-run on startup + + + + - - - Hide window on auto-run - - - - - - + + + Hide window on auto-run + + + + + + - - - Startup delay - - Set the amount of seconds to wait before auto-running Artemis. - - - If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value. - - - - - - - - - sec - - - - - - - - - Log level - - - Sets the logging level, a higher logging level will result in more log files. - - - - - - - + + + Startup delay + + Set the amount of seconds to wait before auto-running Artemis. + + + If some devices don't work because Artemis starts before the manufacturer's software, try increasing this value. + + + + + + + + + sec + + + + - - - - Logs - - - Opens the directory where logs are stored. - - - -