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:
parent
db6fb33c96
commit
3022c7df65
@ -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()
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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" />
|
||||
|
||||
67
src/Artemis.UI.Windows/SuspensionManager.cs
Normal file
67
src/Artemis.UI.Windows/SuspensionManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user