diff --git a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs
index ee9f256a9..263748f0d 100644
--- a/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs
+++ b/src/Artemis.Core/Plugins/DeviceProviders/DeviceProvider.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Linq;
+using System.Threading.Tasks;
using RGB.NET.Core;
namespace Artemis.Core.DeviceProviders;
@@ -15,7 +16,7 @@ public abstract class DeviceProvider : PluginFeature
/// The RGB.NET device provider backing this Artemis device provider
///
public abstract IRGBDeviceProvider RgbDeviceProvider { get; }
-
+
///
/// A boolean indicating whether this device provider detects the physical layout of connected keyboards.
///
@@ -48,6 +49,11 @@ public abstract class DeviceProvider : PluginFeature
///
public bool RemoveExcessiveLedsSupported { get; protected set; } = true;
+ ///
+ /// Gets or sets a boolean indicating whether suspending the device provider is supported
+ ///
+ public bool SuspendSupported { get; protected set; }
+
///
/// Loads a layout for the specified device and wraps it in an
///
@@ -109,4 +115,12 @@ public abstract class DeviceProvider : PluginFeature
return fileName + ".xml";
}
+
+ ///
+ /// Called when the device provider is being suspended, like when the system is going to sleep.
+ /// Note: This will be called while the plugin is disabled.
+ ///
+ public virtual void Suspend()
+ {
+ }
}
\ No newline at end of file
diff --git a/src/Artemis.Core/Services/DeviceService.cs b/src/Artemis.Core/Services/DeviceService.cs
index 111594c4a..5d2da4db4 100644
--- a/src/Artemis.Core/Services/DeviceService.cs
+++ b/src/Artemis.Core/Services/DeviceService.cs
@@ -2,13 +2,13 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
using Artemis.Core.DeviceProviders;
using Artemis.Core.Providers;
using Artemis.Core.Services.Models;
using Artemis.Storage.Entities.Surface;
using Artemis.Storage.Repositories.Interfaces;
-using DryIoc;
using RGB.NET.Core;
using Serilog;
@@ -21,8 +21,10 @@ internal class DeviceService : IDeviceService
private readonly IDeviceRepository _deviceRepository;
private readonly Lazy _renderService;
private readonly Func> _getLayoutProviders;
- private readonly List _enabledDevices = new();
- private readonly List _devices = new();
+ private readonly List _enabledDevices = [];
+ private readonly List _devices = [];
+ private readonly List _suspendedDeviceProviders = [];
+ private readonly object _suspensionLock = new();
public DeviceService(ILogger logger,
IPluginManagementService pluginManagementService,
@@ -69,7 +71,7 @@ internal class DeviceService : IDeviceService
OnDeviceRemoved(new DeviceEventArgs(device));
}
- List providerExceptions = new();
+ List providerExceptions = [];
void DeviceProviderOnException(object? sender, ExceptionEventArgs e)
{
@@ -95,7 +97,7 @@ internal class DeviceService : IDeviceService
return;
}
- List addedDevices = new();
+ List addedDevices = [];
foreach (IRGBDevice rgbDevice in rgbDeviceProvider.Devices)
{
ArtemisDevice artemisDevice = GetArtemisDevice(rgbDevice);
@@ -184,7 +186,7 @@ internal class DeviceService : IDeviceService
device.ApplyLayout(null, false, false);
else
provider?.ApplyLayout(device, layout);
-
+
UpdateLeds();
}
catch (Exception e)
@@ -241,6 +243,83 @@ internal class DeviceService : IDeviceService
UpdateLeds();
}
+ ///
+ public void SuspendDeviceProviders()
+ {
+ lock (_suspensionLock)
+ {
+ _logger.Information("Suspending all device providers");
+
+ bool wasPaused = _renderService.Value.IsPaused;
+ try
+ {
+ _renderService.Value.IsPaused = true;
+ foreach (DeviceProvider deviceProvider in _pluginManagementService.GetFeaturesOfType().Where(d => d.SuspendSupported))
+ SuspendDeviceProvider(deviceProvider);
+ }
+ finally
+ {
+ _renderService.Value.IsPaused = wasPaused;
+ }
+ }
+ }
+
+ ///
+ public void ResumeDeviceProviders()
+ {
+ lock (_suspensionLock)
+ {
+ _logger.Information("Resuming all device providers");
+
+ bool wasPaused = _renderService.Value.IsPaused;
+ try
+ {
+ _renderService.Value.IsPaused = true;
+ foreach (DeviceProvider deviceProvider in _suspendedDeviceProviders.ToList())
+ ResumeDeviceProvider(deviceProvider);
+ }
+ finally
+ {
+ _renderService.Value.IsPaused = wasPaused;
+ }
+ }
+ }
+
+ private void SuspendDeviceProvider(DeviceProvider deviceProvider)
+ {
+ if (_suspendedDeviceProviders.Contains(deviceProvider))
+ {
+ _logger.Warning("Device provider {DeviceProvider} is already suspended", deviceProvider.Info.Name);
+ return;
+ }
+
+ try
+ {
+ _pluginManagementService.DisablePluginFeature(deviceProvider, false);
+ deviceProvider.Suspend();
+ _suspendedDeviceProviders.Add(deviceProvider);
+ _logger.Information("Device provider {DeviceProvider} suspended", deviceProvider.Info.Name);
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Device provider {DeviceProvider} failed to suspend", deviceProvider.Info.Name);
+ }
+ }
+
+ private void ResumeDeviceProvider(DeviceProvider deviceProvider)
+ {
+ try
+ {
+ _pluginManagementService.EnablePluginFeature(deviceProvider, false, true);
+ _suspendedDeviceProviders.Remove(deviceProvider);
+ _logger.Information("Device provider {DeviceProvider} resumed", deviceProvider.Info.Name);
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "Device provider {DeviceProvider} failed to resume", deviceProvider.Info.Name);
+ }
+ }
+
private ArtemisDevice GetArtemisDevice(IRGBDevice rgbDevice)
{
string deviceIdentifier = rgbDevice.GetDeviceIdentifier();
diff --git a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
index 51b19d937..5c5c60ad7 100644
--- a/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
+++ b/src/Artemis.Core/Services/Interfaces/IDeviceService.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Threading.Tasks;
using Artemis.Core.DeviceProviders;
namespace Artemis.Core.Services;
@@ -71,6 +72,16 @@ public interface IDeviceService : IArtemisService
///
void SaveDevices();
+ ///
+ /// Suspends all active device providers
+ ///
+ void SuspendDeviceProviders();
+
+ ///
+ /// Resumes all previously active device providers
+ ///
+ void ResumeDeviceProviders();
+
///
/// Occurs when a single device was added.
///
diff --git a/src/Artemis.UI.Windows/App.axaml.cs b/src/Artemis.UI.Windows/App.axaml.cs
index 66d5aac1b..593a9ea2a 100644
--- a/src/Artemis.UI.Windows/App.axaml.cs
+++ b/src/Artemis.UI.Windows/App.axaml.cs
@@ -35,10 +35,10 @@ public class App : Application
}
_container = ArtemisBootstrapper.Bootstrap(this, c => c.RegisterProviders());
-
+
Program.CreateLogger(_container);
LegacyMigrationService.MigrateToSqlite(_container);
-
+
RxApp.MainThreadScheduler = AvaloniaScheduler.Instance;
AvaloniaXamlLoader.Load(this);
}
@@ -47,10 +47,13 @@ public class App : Application
{
if (ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop || Design.IsDesignMode || _shutDown)
return;
-
- _applicationStateManager = new ApplicationStateManager(_container!, desktop.Args ?? Array.Empty());
+ if (_container == null)
+ throw new InvalidOperationException("Container is null");
+
+ _applicationStateManager = new ApplicationStateManager(_container, desktop.Args ?? Array.Empty());
+ _suspensionManager = new SuspensionManager(_container);
ArtemisBootstrapper.Initialize();
- RegisterProviders(_container!);
+ RegisterProviders(_container);
}
private void RegisterProviders(IContainer container)
@@ -117,8 +120,10 @@ public class App : Application
}
// ReSharper disable NotAccessedField.Local
- private ApplicationStateManager? _applicationStateManager;
+ private ApplicationStateManager? _applicationStateManager;
+ private SuspensionManager? _suspensionManager;
private Mutex? _artemisMutex;
+
// ReSharper restore NotAccessedField.Local
}
\ No newline at end of file
diff --git a/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj b/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj
index f74f2bd4b..0547102a2 100644
--- a/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj
+++ b/src/Artemis.UI.Windows/Artemis.UI.Windows.csproj
@@ -24,6 +24,7 @@
+
diff --git a/src/Artemis.UI.Windows/SuspensionManager.cs b/src/Artemis.UI.Windows/SuspensionManager.cs
new file mode 100644
index 000000000..18da8db80
--- /dev/null
+++ b/src/Artemis.UI.Windows/SuspensionManager.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Threading.Tasks;
+using Artemis.Core.Services;
+using DryIoc;
+using Microsoft.Win32;
+using Serilog;
+
+namespace Artemis.UI.Windows;
+
+public class SuspensionManager
+{
+ private readonly ILogger _logger;
+ private readonly IDeviceService _deviceService;
+
+ public SuspensionManager(IContainer container)
+ {
+ _logger = container.Resolve();
+ _deviceService = container.Resolve();
+
+ try
+ {
+ SystemEvents.PowerModeChanged += SystemEventsOnPowerModeChanged;
+ SystemEvents.SessionSwitch += SystemEventsOnSessionSwitch;
+ }
+ catch (Exception e)
+ {
+ _logger.Warning(e, "Could not subscribe to system events");
+ }
+ }
+
+ private void SystemEventsOnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
+ {
+ if (e.Mode == PowerModes.Suspend)
+ Task.Run(() => SetDeviceSuspension(true));
+ else if (e.Mode == PowerModes.Resume)
+ Task.Run(() => SetDeviceSuspension(false));
+ }
+
+ private void SystemEventsOnSessionSwitch(object sender, SessionSwitchEventArgs e)
+ {
+ if (e.Reason is SessionSwitchReason.SessionLock or SessionSwitchReason.SessionLogoff)
+ Task.Run(() => SetDeviceSuspension(true));
+ else if (e.Reason is SessionSwitchReason.SessionUnlock or SessionSwitchReason.SessionLogon)
+ Task.Run(() => SetDeviceSuspension(false));
+ }
+
+ private async Task SetDeviceSuspension(bool suspend)
+ {
+ try
+ {
+ if (suspend)
+ {
+ // Suspend instantly, system is going into sleep at any moment
+ _deviceService.SuspendDeviceProviders();
+ }
+ else
+ {
+ await Task.Delay(TimeSpan.FromSeconds(2));
+ _deviceService.ResumeDeviceProviders();
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.Error(e, "An error occurred while setting provider suspension to {Suspension}", suspend);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.WebClient.Workshop/Services/AuthenticationService.cs b/src/Artemis.WebClient.Workshop/Services/AuthenticationService.cs
index c8727c361..9c0dc6d7b 100644
--- a/src/Artemis.WebClient.Workshop/Services/AuthenticationService.cs
+++ b/src/Artemis.WebClient.Workshop/Services/AuthenticationService.cs
@@ -21,7 +21,7 @@ internal class AuthenticationService : CorePropertyChanged, IAuthenticationServi
{
private const string CLIENT_ID = "artemis.desktop";
private readonly IAuthenticationRepository _authenticationRepository;
- private readonly SemaphoreSlim _authLock = new(1);
+ private readonly SemaphoreSlim _authLock = new(1, 1);
private readonly SourceList _claims;
private readonly IDiscoveryCache _discoveryCache;
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index e85192de6..3eb203ff3 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -14,6 +14,7 @@
+