1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-12 13:28:33 +00:00

Core - Add device provider suspension system

This commit is contained in:
RobertBeekman 2024-05-16 20:40:45 +02:00
parent db6fb33c96
commit 3022c7df65
8 changed files with 192 additions and 14 deletions

View File

@ -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
/// </summary>
public abstract IRGBDeviceProvider RgbDeviceProvider { get; }
/// <summary>
/// A boolean indicating whether this device provider detects the physical layout of connected keyboards.
/// <para>
@ -48,6 +49,11 @@ public abstract class DeviceProvider : PluginFeature
/// </summary>
public bool RemoveExcessiveLedsSupported { get; protected set; } = true;
/// <summary>
/// Gets or sets a boolean indicating whether suspending the device provider is supported
/// </summary>
public bool SuspendSupported { get; protected set; }
/// <summary>
/// Loads a layout for the specified device and wraps it in an <see cref="ArtemisLayout" />
/// </summary>
@ -109,4 +115,12 @@ public abstract class DeviceProvider : PluginFeature
return fileName + ".xml";
}
/// <summary>
/// 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.
/// </summary>
public virtual void Suspend()
{
}
}

View File

@ -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<IRenderService> _renderService;
private readonly Func<List<ILayoutProvider>> _getLayoutProviders;
private readonly List<ArtemisDevice> _enabledDevices = new();
private readonly List<ArtemisDevice> _devices = new();
private readonly List<ArtemisDevice> _enabledDevices = [];
private readonly List<ArtemisDevice> _devices = [];
private readonly List<DeviceProvider> _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<Exception> providerExceptions = new();
List<Exception> providerExceptions = [];
void DeviceProviderOnException(object? sender, ExceptionEventArgs e)
{
@ -95,7 +97,7 @@ internal class DeviceService : IDeviceService
return;
}
List<ArtemisDevice> addedDevices = new();
List<ArtemisDevice> 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();
}
/// <inheritdoc />
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<DeviceProvider>().Where(d => d.SuspendSupported))
SuspendDeviceProvider(deviceProvider);
}
finally
{
_renderService.Value.IsPaused = wasPaused;
}
}
}
/// <inheritdoc />
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();

View File

@ -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
/// </summary>
void SaveDevices();
/// <summary>
/// Suspends all active device providers
/// </summary>
void SuspendDeviceProviders();
/// <summary>
/// Resumes all previously active device providers
/// </summary>
void ResumeDeviceProviders();
/// <summary>
/// Occurs when a single device was added.
/// </summary>

View File

@ -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<string>());
if (_container == null)
throw new InvalidOperationException("Container is null");
_applicationStateManager = new ApplicationStateManager(_container, desktop.Args ?? Array.Empty<string>());
_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
}

View File

@ -24,6 +24,7 @@
<PackageReference Include="Avalonia.Win32" />
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" />
<PackageReference Include="Microsoft.Win32" />
<PackageReference Include="Microsoft.Win32.SystemEvents" />
<PackageReference Include="Microsoft.Windows.Compatibility" />
<PackageReference Include="RawInput.Sharp" />
<PackageReference Include="SkiaSharp.Vulkan.SharpVk" />

View File

@ -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<ILogger>();
_deviceService = container.Resolve<IDeviceService>();
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);
}
}
}

View File

@ -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<Claim> _claims;
private readonly IDiscoveryCache _discoveryCache;

View File

@ -14,6 +14,7 @@
<PackageVersion Include="Avalonia.ReactiveUI" Version="11.0.9" />
<PackageVersion Include="Avalonia.Skia.Lottie" Version="11.0.0" />
<PackageVersion Include="Avalonia.Win32" Version="11.0.9" />
<PackageVersion Include="Microsoft.Win32.SystemEvents" Version="8.0.0" />
<PackageVersion Include="Avalonia.Xaml.Behaviors" Version="11.0.6" />
<PackageVersion Include="AvaloniaEdit.TextMate" Version="11.0.6" />
<PackageVersion Include="DryIoc.Microsoft.DependencyInjection" Version="6.2.0" />