diff --git a/Project-Aurora/Aurora-Updater/Program.cs b/Project-Aurora/Aurora-Updater/Program.cs index fa3239cfc..9c2053762 100755 --- a/Project-Aurora/Aurora-Updater/Program.cs +++ b/Project-Aurora/Aurora-Updater/Program.cs @@ -72,6 +72,10 @@ static void Main(string[] args) return; } var versionToCheck = VersionParser.ParseVersion(fileVersion); + if (versionToCheck is { Major: 0, Minor: 0, Patch: 0 }) + { + _isSilent = false; + } var owner = FileVersionInfo.GetVersionInfo(auroraPath).CompanyName; var repository = FileVersionInfo.GetVersionInfo(auroraPath).ProductName; diff --git a/Project-Aurora/AuroraDeviceManager/AuroraDeviceManager.csproj b/Project-Aurora/AuroraDeviceManager/AuroraDeviceManager.csproj index 5e9f1c02b..5c5ff8db6 100644 --- a/Project-Aurora/AuroraDeviceManager/AuroraDeviceManager.csproj +++ b/Project-Aurora/AuroraDeviceManager/AuroraDeviceManager.csproj @@ -124,6 +124,7 @@ + diff --git a/Project-Aurora/AuroraDeviceManager/Devices/Creative/SoundBlasterXDevice.cs b/Project-Aurora/AuroraDeviceManager/Devices/Creative/SoundBlasterXDevice.cs index f6288b5f4..cbb8c1de4 100644 --- a/Project-Aurora/AuroraDeviceManager/Devices/Creative/SoundBlasterXDevice.cs +++ b/Project-Aurora/AuroraDeviceManager/Devices/Creative/SoundBlasterXDevice.cs @@ -55,7 +55,7 @@ protected override Task DoInitialize(CancellationToken cancellationToken) } catch (Exception exc) { - Global.Logger.Error("There was an error scanning for SoundBlasterX devices", exc); + Global.Logger.Error(exc, "There was an error scanning for SoundBlasterX devices"); IsInitialized = false; return Task.FromResult(false); } diff --git a/Project-Aurora/AuroraDeviceManager/Devices/UnifiedHID/UnifiedHIDDevice.cs b/Project-Aurora/AuroraDeviceManager/Devices/UnifiedHID/UnifiedHIDDevice.cs index 8f82d7716..d61635ef4 100644 --- a/Project-Aurora/AuroraDeviceManager/Devices/UnifiedHID/UnifiedHIDDevice.cs +++ b/Project-Aurora/AuroraDeviceManager/Devices/UnifiedHID/UnifiedHIDDevice.cs @@ -67,7 +67,7 @@ protected override Task DoInitialize(CancellationToken cancellationToken) } catch (Exception e) { - Global.Logger.Error($"[UnifiedHID] device could not be initialized:", e); + Global.Logger.Error(e, $"[UnifiedHID] device could not be initialized"); } return Task.FromResult(IsInitialized); @@ -98,7 +98,7 @@ protected override Task Shutdown() } catch (Exception ex) { - Global.Logger.Error("[UnifiedHID] there was an error shutting down devices", ex); + Global.Logger.Error(ex, "[UnifiedHID] there was an error shutting down devices"); } return Task.CompletedTask; @@ -157,7 +157,7 @@ protected override Task UpdateDevice(Dictionary k } catch (Exception ex) { - Global.Logger.Error("[UnifiedHID] error when updating device:", ex); + Global.Logger.Error(ex, "[UnifiedHID] error when updating device:"); return Task.FromResult(false); } } diff --git a/Project-Aurora/AuroraDeviceManager/Utils/ProcessUtils.cs b/Project-Aurora/AuroraDeviceManager/Utils/ProcessUtils.cs index 95eb7b59c..2df00be8f 100644 --- a/Project-Aurora/AuroraDeviceManager/Utils/ProcessUtils.cs +++ b/Project-Aurora/AuroraDeviceManager/Utils/ProcessUtils.cs @@ -1,4 +1,7 @@ -using System.Diagnostics; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.ServiceProcess; namespace AuroraDeviceManager.Utils; @@ -6,7 +9,46 @@ public static class ProcessUtils { public static bool IsProcessRunning(string processName) { - var processesByName = Process.GetProcessesByName(processName); - return processesByName.Length > 0; + if (string.IsNullOrWhiteSpace(processName)) + { + return false; + } + + var normalizedName = Path.GetFileNameWithoutExtension(processName).Trim(); + if (string.IsNullOrEmpty(normalizedName)) + { + normalizedName = processName.Trim(); + } + + try + { + var processesByName = Process.GetProcessesByName(normalizedName); + if (processesByName.Length > 0) + { + return true; + } + } + catch + { + // ignore and fall back to service lookup + } + + try + { + using var service = new ServiceController(normalizedName); + return service.Status is ServiceControllerStatus.Running + or ServiceControllerStatus.StartPending + or ServiceControllerStatus.ContinuePending; + } + catch (InvalidOperationException) + { + // service not found + } + catch (Win32Exception) + { + // access denied or service not accessible + } + + return false; } } \ No newline at end of file diff --git a/Project-Aurora/MemoryAccessProfiles/PluginMain.cs b/Project-Aurora/MemoryAccessProfiles/PluginMain.cs index 04ab8e414..651811291 100644 --- a/Project-Aurora/MemoryAccessProfiles/PluginMain.cs +++ b/Project-Aurora/MemoryAccessProfiles/PluginMain.cs @@ -1,4 +1,5 @@ -using AuroraRgb.Profiles; +using AuroraRgb.EffectsEngine; +using AuroraRgb.Profiles; using AuroraRgb.Settings; using MemoryAccessProfiles.Profiles.Borderlands2; using MemoryAccessProfiles.Profiles.CloneHero; @@ -36,6 +37,6 @@ public void ProcessManager(object manager) // Register all Application types in the assembly foreach (var inst in profiles) - lightingStateManager.RegisterEvent(inst); + lightingStateManager.ApplicationManager.RegisterEvent(inst); } } \ No newline at end of file diff --git a/Project-Aurora/MemoryAccessProfiles/Profiles/Borderlands2/Borderlands2Profile.cs b/Project-Aurora/MemoryAccessProfiles/Profiles/Borderlands2/Borderlands2Profile.cs index 4c33efc88..f4094372a 100644 --- a/Project-Aurora/MemoryAccessProfiles/Profiles/Borderlands2/Borderlands2Profile.cs +++ b/Project-Aurora/MemoryAccessProfiles/Profiles/Borderlands2/Borderlands2Profile.cs @@ -1,5 +1,5 @@ using System.Drawing; -using AuroraRgb.Profiles; +using AuroraRgb.EffectsEngine; using AuroraRgb.Settings; using AuroraRgb.Settings.Layers; using Common.Devices; diff --git a/Project-Aurora/MemoryAccessProfiles/Profiles/Dishonored/DishonoredProfile.cs b/Project-Aurora/MemoryAccessProfiles/Profiles/Dishonored/DishonoredProfile.cs index bc0c297fb..1e4cf73b3 100644 --- a/Project-Aurora/MemoryAccessProfiles/Profiles/Dishonored/DishonoredProfile.cs +++ b/Project-Aurora/MemoryAccessProfiles/Profiles/Dishonored/DishonoredProfile.cs @@ -1,5 +1,5 @@ using System.Drawing; -using AuroraRgb.Profiles; +using AuroraRgb.EffectsEngine; using AuroraRgb.Settings; using AuroraRgb.Settings.Layers; using Common.Devices; diff --git a/Project-Aurora/Project-Aurora/AuroraApp.cs b/Project-Aurora/Project-Aurora/AuroraApp.cs index 00ead1d64..cb03f6fb9 100644 --- a/Project-Aurora/Project-Aurora/AuroraApp.cs +++ b/Project-Aurora/Project-Aurora/AuroraApp.cs @@ -108,7 +108,7 @@ public async Task OnStartup() } var lsm = await LightingStateManagerModule.LightningStateManager; - lsm.InitializeApps(); + lsm.ApplicationManager.InitializeApps(); //Debug Windows on Startup if (Global.Configuration.BitmapWindowOnStartUp) diff --git a/Project-Aurora/Project-Aurora/ConfigUi.xaml.cs b/Project-Aurora/Project-Aurora/ConfigUi.xaml.cs index 46f340b4d..ef7406e5f 100755 --- a/Project-Aurora/Project-Aurora/ConfigUi.xaml.cs +++ b/Project-Aurora/Project-Aurora/ConfigUi.xaml.cs @@ -86,7 +86,7 @@ public Application? FocusedApplication set { SetValue(FocusedApplicationProperty, value); - _lightingStateManager.Result.PreviewProfileKey = value != null ? value.Config.ID : string.Empty; + _lightingStateManager.Result.ApplicationManager.PreviewProfileKey = value != null ? value.Config.ID : string.Empty; } } @@ -448,7 +448,7 @@ private async Task Window_ClosingAsync(Application? focusedApp) } var lightingStateManager = await _lightingStateManager; - lightingStateManager.PreviewProfileKey = string.Empty; + lightingStateManager.ApplicationManager.PreviewProfileKey = string.Empty; switch (Global.Configuration.CloseMode) { diff --git a/Project-Aurora/Project-Aurora/Control_ProfilesStack.xaml.cs b/Project-Aurora/Project-Aurora/Control_ProfilesStack.xaml.cs index 0aea1ee65..dd3a6bc4f 100644 --- a/Project-Aurora/Project-Aurora/Control_ProfilesStack.xaml.cs +++ b/Project-Aurora/Project-Aurora/Control_ProfilesStack.xaml.cs @@ -10,6 +10,7 @@ using System.Windows.Input; using System.Windows.Media.Imaging; using System.Windows.Threading; +using AuroraRgb.EffectsEngine; using AuroraRgb.Modules.GameStateListen; using AuroraRgb.Profiles; using AuroraRgb.Profiles.Generic_Application; @@ -85,8 +86,8 @@ private async Task GenerateProfileStack(string focusedKey) var lightingStateManager = await _lightingStateManager; var profileLoadTasks = Global.Configuration.ProfileOrder - .Where(profileName => lightingStateManager.Events.ContainsKey(profileName)) - .Select(profileName => lightingStateManager.Events[profileName]) + .Where(profileName => lightingStateManager.ApplicationManager.Events.ContainsKey(profileName)) + .Select(profileName => lightingStateManager.ApplicationManager.Events[profileName]) .OrderBy(item => item.Settings is { Hidden: false } ? 0 : 1) .Select(application => InsertApplicationImage(focusedKey, application, focusedSetTaskCompletion, cancellationToken)) .Select(x => x.Task); @@ -145,7 +146,7 @@ private async Task RemoveApplication(Application application) var name = application.Config.ID; var lightingStateManager = await _lightingStateManager; - if (!lightingStateManager.Events.TryGetValue(name, out var value)) return; + if (!lightingStateManager.ApplicationManager.Events.TryGetValue(name, out var value)) return; var applicationName = value.Config.ProcessNames[0]; var cancelled = MessageBox.Show( @@ -153,7 +154,7 @@ private async Task RemoveApplication(Application application) MessageBoxButton.YesNo, MessageBoxImage.Warning) != MessageBoxResult.Yes; if (cancelled) return; - lightingStateManager.RemoveGenericProfile(name); + lightingStateManager.ApplicationManager.RemoveGenericProfile(name); Dispatcher.InvokeAsync(async () => await GenerateProfileStack()); } @@ -167,7 +168,7 @@ private async void AddProfile_MouseDown(object? sender, MouseButtonEventArgs e) var filename = Path.GetFileName(dialog.ChosenExecutablePath.ToLowerInvariant()); var lightingStateManager = await _lightingStateManager; - if (lightingStateManager.Events.ContainsKey(filename)) + if (lightingStateManager.ApplicationManager.Events.ContainsKey(filename)) { MessageBox.Show("Profile for this application already exists."); return; @@ -188,7 +189,7 @@ private async void AddProfile_MouseDown(object? sender, MouseButtonEventArgs e) ico.Dispose(); - await lightingStateManager.RegisterEvent(genAppPm); + await lightingStateManager.ApplicationManager.RegisterEvent(genAppPm); await ConfigManager.SaveAsync(Global.Configuration); await GenerateProfileStack(filename); } @@ -247,12 +248,12 @@ private void ContextMenu_Opened(object? sender, RoutedEventArgs e) private async void Control_ProfilesStack_OnLoaded(object sender, RoutedEventArgs e) { - (await _lightingStateManager).EventAdded += LightingStateManagerOnEventAdded; + (await _lightingStateManager).ApplicationManager.EventAdded += LightingStateManagerOnEventAdded; } private async void Control_ProfilesStack_OnUnloaded(object sender, RoutedEventArgs e) { - (await _lightingStateManager).EventAdded -= LightingStateManagerOnEventAdded; + (await _lightingStateManager).ApplicationManager.EventAdded -= LightingStateManagerOnEventAdded; } private void FocusApplication(Application application) diff --git a/Project-Aurora/Project-Aurora/Controls/EffectSettingsWindow.xaml.cs b/Project-Aurora/Project-Aurora/Controls/EffectSettingsWindow.xaml.cs index 894dabcb1..02f49e56a 100644 --- a/Project-Aurora/Project-Aurora/Controls/EffectSettingsWindow.xaml.cs +++ b/Project-Aurora/Project-Aurora/Controls/EffectSettingsWindow.xaml.cs @@ -55,7 +55,7 @@ public EffectSettingsWindow(LayerEffectConfig EffectConfig) } catch(Exception exc) { - Global.logger.Error("Could not set brush:", exc); + Global.logger.Error(exc, "Could not set brush"); } gradient_editor.BrushChanged += Gradient_editor_BrushChanged; @@ -114,12 +114,12 @@ private void accept_button_Click(object? sender, RoutedEventArgs e) private void Window_Activated(object? sender, EventArgs e) { - Global.LightingStateManager.PreviewProfileKey = preview_key; + Global.LightingStateManager.ApplicationManager.PreviewProfileKey = preview_key; } private void Window_Deactivated(object? sender, EventArgs e) { - Global.LightingStateManager.PreviewProfileKey = null; + Global.LightingStateManager.ApplicationManager.PreviewProfileKey = null; } private void effect_angle_ValueChanged(object? sender, RoutedPropertyChangedEventArgs e) diff --git a/Project-Aurora/Project-Aurora/Controls/GameStateParameterPicker.xaml.cs b/Project-Aurora/Project-Aurora/Controls/GameStateParameterPicker.xaml.cs index d99258e2e..087e8c458 100644 --- a/Project-Aurora/Project-Aurora/Controls/GameStateParameterPicker.xaml.cs +++ b/Project-Aurora/Project-Aurora/Controls/GameStateParameterPicker.xaml.cs @@ -9,6 +9,7 @@ using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; +using AuroraRgb.EffectsEngine; using AuroraRgb.Profiles; using AuroraRgb.Utils; using Xceed.Wpf.Toolkit; diff --git a/Project-Aurora/Project-Aurora/EffectsEngine/ApplicationManager.cs b/Project-Aurora/Project-Aurora/EffectsEngine/ApplicationManager.cs new file mode 100644 index 000000000..0b7eca976 --- /dev/null +++ b/Project-Aurora/Project-Aurora/EffectsEngine/ApplicationManager.cs @@ -0,0 +1,417 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using AuroraRgb.Controls; +using AuroraRgb.Modules; +using AuroraRgb.Modules.GameStateListen; +using AuroraRgb.Modules.ProcessMonitor; +using AuroraRgb.Profiles; +using AuroraRgb.Profiles.Desktop; +using AuroraRgb.Profiles.Generic_Application; +using Common.Utils; +using AssemblyExtensions = AuroraRgb.Utils.AssemblyExtensions; + +namespace AuroraRgb.EffectsEngine; + +public sealed class ApplicationInitializedEventArgs(Application application) : EventArgs +{ + public Application Application { get; } = application; +} + +public sealed class ApplicationManager : IAsyncDisposable, IDisposable +{ + public event EventHandler? EventAdded; + public Dictionary Events { get; } = new() { { "desktop", new Desktop() } }; + + public Desktop DesktopProfile => (Desktop)Events["desktop"]; + + + private string _previewModeProfileKey = ""; + + public string? PreviewProfileKey { + get => _previewModeProfileKey; + set => _previewModeProfileKey = value ?? string.Empty; + } + + private Dictionary> EventProcesses { get; } = new(); + private Dictionary EventTitles { get; } = new(); + private Dictionary EventAppIDs { get; } = new(); + + private readonly CancellationTokenSource _initializeCancelSource = new(); + private readonly ConcurrentQueue> _initTaskQueue = new(); + + private readonly Task _runningProcessMonitor; + private readonly Task _ipcListener; + private readonly Task _activeProcessMonitor; + + private readonly Func _isOverlayActiveProfile; + private readonly SingleConcurrentThread _profileInitThread; + + private bool Initialized { get; set; } + + public ApplicationManager(Task runningProcessMonitor, Task ipcListener, Task activeProcessMonitor) + { + _runningProcessMonitor = runningProcessMonitor; + _ipcListener = ipcListener; + _activeProcessMonitor = activeProcessMonitor; + + Predicate processRunning = ProcessRunning; + _isOverlayActiveProfile = evt => evt.IsOverlayEnabled && + Array.Exists(evt.Config.ProcessNames, processRunning); + + _profileInitThread = new SingleConcurrentThread("ProfileInit", ProfileInitAction, ProfileInitExceptionCallback); + + bool ProcessRunning(string name) => _runningProcessMonitor.Result.IsProcessRunning(name); + } + + public async Task Initialize() + { + if (Initialized) + return; + + if (_initializeCancelSource.IsCancellationRequested) + return; + var cancellationToken = _initializeCancelSource.Token; + var defaultApps = EnumerateDefaultApps(); + var userApps = EnumerateUserApps(); + + // Register all Application types in the assembly + var profiles = defaultApps.Concat(userApps); + foreach (var inst in profiles) + await RegisterEvent(inst); + + await DesktopProfile.Initialize(cancellationToken); + LoadSettings(); + + // Listen for profile keybind triggers + // TODO make this optional + (await InputsModule.InputEvents).KeyDown += CheckProfileKeybinds; + + Initialized = true; + } + + private static IEnumerable EnumerateDefaultApps() + { + return from type in AssemblyExtensions.GetLoadableTypes(Assembly.GetExecutingAssembly()) + where (type.BaseType == typeof(Application) || type.BaseType == typeof(GsiApplication)) && type != typeof(GenericApplication) && type != typeof(GsiApplication) + let inst = (Application)Activator.CreateInstance(type) + orderby inst.Config.Name + select inst; + } + + private static IEnumerable EnumerateUserApps() + { + var additionalProfilesPath = Path.Combine(Global.AppDataDirectory, "AdditionalProfiles"); + if (!Directory.Exists(additionalProfilesPath)) + { + return Array.Empty(); + } + + var userApps = from dir in Directory.EnumerateDirectories(additionalProfilesPath) + where File.Exists(Path.Combine(dir, "settings.json")) + select Path.GetFileName(dir); + + return userApps.Select(processName => new GenericApplication(processName)); + } + + public void InitializeApps() + { + var cancellationToken = _initializeCancelSource.Token; + foreach (var (_, profile) in Events) + { + _initTaskQueue.Enqueue(async () => + { + try + { + await profile.Initialize(cancellationToken); + EventAdded?.Invoke(this, new ApplicationInitializedEventArgs(profile)); + } + catch (Exception e) + { + Global.logger.Error(e, "Error initializing profile {Profile}", profile.GetType()); + } + }); + } + + _profileInitThread.Trigger(); + } + + private void LoadSettings() + { + foreach (var kvp in Events + .Where(kvp => !Global.Configuration.ProfileOrder.Contains(kvp.Key) && kvp.Value is Application)) + { + Global.Configuration.ProfileOrder.Add(kvp.Key); + } + + Global.Configuration.ProfileOrder + .RemoveAll(key => !Events.ContainsKey(key) || Events[key] is not Application); + + PutProfileTop("games"); + PutProfileTop("logitech"); + PutProfileTop("icue"); + PutProfileTop("chroma"); + PutProfileTop("desktop"); + } + + public async Task RegisterEvent(Application application) + { + var profileId = application.Config.ID; + if (string.IsNullOrWhiteSpace(profileId) || !Events.TryAdd(profileId, application)) return; + + foreach (var exe in application.Config.ProcessNames) + { + AddEventProcess(exe, application); + } + + AddEventProcess(profileId, application); + + application.Config.ProcessNamesChanged += ConfigOnProcessNamesChanged(application); + + if (application.Config.ProcessTitles != null) + foreach (var titleRx in application.Config.ProcessTitles) + EventTitles.Add(titleRx, application); + + if (!string.IsNullOrWhiteSpace(profileId)) + EventAppIDs.Add(profileId, application); + + if (!Global.Configuration.ProfileOrder.Contains(profileId)) + { + Global.Configuration.ProfileOrder.Add(profileId); + } + + if (Initialized) + await application.Initialize(_initializeCancelSource.Token); + } + + private EventHandler ConfigOnProcessNamesChanged(Application application) + { + return (_, _) => + { + var profileId = application.Config.ID; + var keysToRemove = new List(); + foreach (var (s, applications) in EventProcesses) + { + foreach (var app in applications) + { + if (app.Config.ID == profileId) + { + keysToRemove.Add(s); + } + } + } + + foreach (var s in keysToRemove) + { + EventProcesses.Remove(s); + } + + foreach (var exe in application.Config.ProcessNames) + { + if (exe.Equals(profileId)) continue; + var processKey = exe.ToLower(); + AddEventProcess(processKey, application); + } + + AddEventProcess(profileId, application); + }; + } + + private void AddEventProcess(string processKey, Application application) + { + if (!EventProcesses.TryGetValue(processKey, out var applicationList)) + { + applicationList = new SortedSet(new ApplicationPriorityComparer()); + EventProcesses[processKey] = applicationList; + } + + applicationList.Add(application); + } + + public void RemoveGenericProfile(string key) + { + if (!Events.TryGetValue(key, out var value)) return; + if (value is not GenericApplication profile) + return; + Events.Remove(key); + Global.Configuration.ProfileOrder.Remove(key); + + profile.Dispose(); + + var path = profile.GetProfileFolderPath(); + if (Directory.Exists(path)) + Directory.Delete(path, true); + } + + // Used to match a process's name and optional window title to a profile + private Application? GetProfileFromProcessData(string processName, string processTitle) + { + var processNameProfile = GetProfileFromProcessName(processName); + + if (processNameProfile == null) + return null; + + // Is title matching required? + if (processNameProfile.Config.ProcessTitles != null) + { + var processTitleProfile = GetProfileFromProcessTitle(processTitle); + + if (processTitleProfile != null && processTitleProfile.Equals(processNameProfile)) + { + return processTitleProfile; + } + } + else + { + return processNameProfile; + } + + return null; + } + + public Application? GetProfileFromProcessName(string process) + { + return EventProcesses.GetValueOrDefault(process)? + .FirstOrDefault(a => a.Settings?.IsEnabled ?? false); + } + + + private Application? GetProfileFromProcessTitle(string title) + { + return EventTitles.Where(entry => entry.Key.IsMatch(title)) + .Select(kv => kv.Value) + .FirstOrDefault(); + } + + public Application? GetProfileFromAppId(string appid) + { + return !EventAppIDs.TryGetValue(appid, out var value) ? Events.GetValueOrDefault(appid) : value; + } + + //TODO make this event based rather than polling + /// Gets the current application. + /// Boolean indicating whether the application is selected because it is previewing (true) + /// or because the process is open (false). + public Application GetCurrentProfile(out bool preview) + { + var processName = _activeProcessMonitor.Result.ProcessName.ToLower(); + var processTitle = _activeProcessMonitor.Result.ProcessTitle; + Application? profile = null; + Application? tempProfile; + preview = false; + + //TODO: GetProfile that checks based on event type + if ((tempProfile = GetProfileFromProcessData(processName, processTitle)) != null && tempProfile.IsEnabled) + profile = tempProfile; + //Don't check for it being Enabled as a preview should always end-up with the previewed profile regardless of it being disabled + else if ((tempProfile = GetProfileFromProcessName(_previewModeProfileKey)) != null) + { + profile = tempProfile; + preview = true; + } + else if (Global.Configuration.ExcludedPrograms.Contains(processName)) + { + return DesktopProfile; + } + else if (Global.Configuration.AllowWrappersInBackground + && LfxState.IsWrapperConnected + && (tempProfile = GetProfileFromProcessName(LfxState.WrappedProcess)) != null + && tempProfile.IsEnabled) + profile = tempProfile; + + profile ??= DesktopProfile; + + return profile; + } + + /// Gets the current application. + public Application GetCurrentProfile() => GetCurrentProfile(out _); + /// + /// Returns a list of all profiles that should have their overlays active. This will include processes that running but not in the foreground. + /// + /// + public IEnumerable GetOverlayActiveProfiles() + { + return Events.Values.Where(_isOverlayActiveProfile); + } + + /// KeyDown handler that checks the current application's profiles for keybinds. + /// In the case of multiple profiles matching the keybind, it will pick the next one as specified in the Application.Profile order. + private void CheckProfileKeybinds(object? sender, EventArgs e) + { + var profile = GetCurrentProfile(); + + // Check profile is valid and do not switch profiles if the user is trying to enter a keybind + if (Control_Keybind._ActiveKeybind != null) return; + + // Find all profiles that have their keybinds pressed + var currentIndex = -1; + var count = 0; + foreach (var prof in profile.Profiles) + { + if (!prof.TriggerKeybind.IsPressed()) continue; + if (currentIndex == -1 && prof == profile.Profile) + currentIndex = count; + count++; + } + + // If at least one profile has it's key pressed\ + if (count == 0) return; + // The target profile is the NEXT valid profile after the currently selected one + // (or the first valid one if the currently selected one doesn't share this keybind) + var nextIndex = (currentIndex + 1) % count; + var idx = 0; + foreach (var prof in profile.Profiles) + { + if (!prof.TriggerKeybind.IsPressed()) continue; + if (idx == nextIndex) + { + profile.SwitchToProfile(prof); + break; + } + idx++; + } + } + + private static void PutProfileTop(string profileId) + { + Global.Configuration.ProfileOrder.Remove(profileId); + Global.Configuration.ProfileOrder.Insert(0, profileId); + } + + private async Task ProfileInitAction() + { + if (_initTaskQueue.TryDequeue(out var action)) + { + await action.Invoke(); + _profileInitThread.Trigger(); + } + } + + private static void ProfileInitExceptionCallback(object? arg1, SingleThreadExceptionEventArgs arg2) + { + Global.logger.Fatal(arg2.Exception, "Profile load failed"); + } + + public void Dispose() + { + _initializeCancelSource.Cancel(); + _initializeCancelSource.Dispose(); + foreach (var (_, lightEvent) in Events) + lightEvent.Dispose(); + } + + public async ValueTask DisposeAsync() + { + await _initializeCancelSource.CancelAsync(); + _initializeCancelSource.Dispose(); + foreach (var (_, lightEvent) in Events) + lightEvent.Dispose(); + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/EffectsEngine/LightingStateManager.cs b/Project-Aurora/Project-Aurora/EffectsEngine/LightingStateManager.cs new file mode 100644 index 000000000..5c4f22b11 --- /dev/null +++ b/Project-Aurora/Project-Aurora/EffectsEngine/LightingStateManager.cs @@ -0,0 +1,433 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using AuroraRgb.Devices; +using AuroraRgb.Modules.GameStateListen; +using AuroraRgb.Modules.ProcessMonitor; +using AuroraRgb.Profiles; +using AuroraRgb.Profiles.Desktop; +using AuroraRgb.Settings; +using AuroraRgb.Settings.Layers; +using AuroraRgb.Utils; +using Common.Utils; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using Application = AuroraRgb.Profiles.Application; +using AssemblyExtensions = AuroraRgb.Utils.AssemblyExtensions; +using JsonSerializer = System.Text.Json.JsonSerializer; + +namespace AuroraRgb.EffectsEngine; + +public sealed class LightingStateManager : IDisposable +{ + private static readonly JsonSerializerOptions GameStateJsonSerializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + TypeInfoResolverChain = { GameStateSourceGenerationContext.Default } + }; + + private Application? _currentEvent; + public Application CurrentEvent => _currentEvent ?? ApplicationManager.DesktopProfile; + + private readonly HashSet _startedEvents = []; + private readonly HashSet _updatedEvents = []; + + public Dictionary LayerHandlers { get; } = new(); + + public event EventHandler? PreUpdate; + public event EventHandler? PostUpdate; + + public ApplicationManager ApplicationManager { get; } + + private readonly Task _pluginManager; + private readonly Task _deviceManager; + private readonly Task _activeProcessMonitor; + + private bool Initialized { get; set; } + + public LightingStateManager(Task pluginManager, Task ipcListener, + Task deviceManager, Task activeProcessMonitor, Task runningProcessMonitor) + { + _pluginManager = pluginManager; + _deviceManager = deviceManager; + _activeProcessMonitor = activeProcessMonitor; + + ApplicationManager = new ApplicationManager(runningProcessMonitor, ipcListener, activeProcessMonitor); + + _updateTimer = new SingleConcurrentThread("LightingStateManager", TimerUpdate, ExceptionCallback); + } + + private static void ExceptionCallback(object? sender, SingleThreadExceptionEventArgs eventArgs) + { + Global.logger.Error(eventArgs.Exception, "Unexpected error with LightingStateManager loop"); + } + + public async Task Initialize() + { + if (Initialized) + return; + + // Register all layer types that are in the Aurora.Settings.Layers namespace. + // Do not register all that are inside the assembly since some are application-specific (e.g. minecraft health layer) + var layerTypes = from type in AssemblyExtensions.GetLoadableTypes(Assembly.GetExecutingAssembly()) + where type.GetInterfaces().Contains(typeof(ILayerHandler)) + let name = type.Name.CamelCaseToSpaceCase() + let meta = type.GetCustomAttribute() + where !type.IsGenericType + where meta is not { Exclude: true } + select (type, meta); + foreach (var (type, meta) in layerTypes) + LayerHandlers.Add(type, new LayerHandlerMeta(type, meta)); + + await ApplicationManager.Initialize(); + + await LoadPlugins(); + + Initialized = true; + } + + private async Task LoadPlugins() + { + (await _pluginManager).ProcessManager(this); + } + + /// + /// Manually registers a layer. Only needed externally. + /// + [PublicAPI] + public bool RegisterLayer() where T : ILayerHandler + { + var t = typeof(T); + if (LayerHandlers.ContainsKey(t)) return false; + var meta = t.GetCustomAttribute(); + LayerHandlers.Add(t, new LayerHandlerMeta(t, meta)); + return true; + } + + private readonly SingleConcurrentThread _updateTimer; + + private long _nextProcessNameUpdate; + private long _currentTick; + + private readonly EventIdle _idleE = new(); + + private readonly Stopwatch _watch = new(); + + public Task InitUpdate() + { + _watch.Start(); + _updateTimer?.Trigger(); + return Task.CompletedTask; + } + + private void TimerUpdate() + { + GC.WaitForPendingFinalizers(); + if (Debugger.IsAttached) + { + Thread.Sleep(40); + } + + if (Global.isDebug) + Update(); + else + { + try + { + Update(); + } + catch (Exception exc) + { + Global.logger.Error(exc, "ProfilesManager.Update() Exception:"); + //TODO make below non-blocking + MessageBox.Show("Error while updating light effects: " + exc.Message); + } + } + _currentTick += _watch.ElapsedMilliseconds; + var millisecondsTimeout = Math.Max(Global.Configuration.UpdateDelay - _watch.ElapsedMilliseconds, 1); + Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsTimeout)); + _updateTimer.Trigger(); + _watch.Restart(); + } + + private void UpdateProcess() + { + var pollingEnabled = Global.Configuration.DetectionMode is ApplicationDetectionMode.ForegroundApp or ApplicationDetectionMode.EventsAndForeground; + if (!pollingEnabled || _currentTick < _nextProcessNameUpdate) return; + _activeProcessMonitor.Result.UpdateActiveProcessPolling(); + _nextProcessNameUpdate = _currentTick + 2000L; + } + + private void UpdateIdleEffects(EffectFrame newFrame) + { + if (!User32.GetLastInputInfoOut(out var lastInput)) return; + var idleTime = Environment.TickCount - lastInput.dwTime; + + if (idleTime < Global.Configuration.IdleDelay * 60 * 1000) return; + if (Global.Configuration.TimeBasedDimmingEnabled && + Time.IsCurrentTimeBetween(Global.Configuration.TimeBasedDimmingStartHour, + Global.Configuration.TimeBasedDimmingStartMinute, + Global.Configuration.TimeBasedDimmingEndHour, + Global.Configuration.TimeBasedDimmingEndMinute)) return; + UpdateEvent(_idleE, newFrame); + } + + private void UpdateEvent(ILightEvent @event, EffectFrame frame) + { + StartEvent(@event); + @event.UpdateLights(frame); + } + + private void StartEvent(ILightEvent @event) + { + _updatedEvents.Add(@event); + + // Skip if event was already started + if (!_startedEvents.Add(@event)) return; + + @event.OnStart(); + } + + private void StopUnUpdatedEvents() + { + // Skip if there are no started events or started events are the same since last update + if (_startedEvents.Count == 0 || _startedEvents.SequenceEqual(_updatedEvents)) return; + + var eventsToStop = _startedEvents.Except(_updatedEvents).ToList(); + foreach (var eventToStop in eventsToStop) + eventToStop.OnStop(); + + _startedEvents.Clear(); + foreach (var updatedEvent in _updatedEvents) + { + _startedEvents.Add(updatedEvent); + } + } + + private bool _profilesDisabled; + private readonly EffectFrame _drawnFrame = new(); + + private static readonly DefaultContractResolver ContractResolver = new() + { + NamingStrategy = new SnakeCaseNamingStrategy() + { + OverrideSpecifiedNames = false + } + }; + + private void Update() + { + PreUpdate?.Invoke(this, EventArgs.Empty); + _updatedEvents.Clear(); + + //Blackout. TODO: Cleanup this a bit. Maybe push blank effect frame to keyboard incase it has existing stuff displayed + var dimmingStartTime = new TimeSpan(Global.Configuration.TimeBasedDimmingStartHour, + Global.Configuration.TimeBasedDimmingStartMinute, 0); + var dimmingEndTime = new TimeSpan(Global.Configuration.TimeBasedDimmingEndHour, + Global.Configuration.TimeBasedDimmingEndMinute, 0); + if (Global.Configuration.TimeBasedDimmingEnabled && + Time.IsCurrentTimeBetween(dimmingStartTime, dimmingEndTime)) + { + var blackFrame = new EffectFrame(); + Global.effengine.PushFrame(blackFrame); + StopUnUpdatedEvents(); + return; + } + + UpdateProcess(); + + var profile = ApplicationManager.GetCurrentProfile(out var preview); + _currentEvent = profile; + + // If the current foreground process is excluded from Aurora, disable the lighting manager + if (profile is Desktop && !profile.IsEnabled) + { + if (!_profilesDisabled) + { + StopUnUpdatedEvents(); + Global.effengine.PushFrame(_drawnFrame); + _deviceManager.Result.ShutdownDevices(); + } + + _profilesDisabled = true; + return; + } + + if (_profilesDisabled) + { + _deviceManager.Result.InitializeDevices(); + _profilesDisabled = false; + } + + //Need to do another check in case Desktop is disabled or the selected preview is disabled + if (profile.IsEnabled) + UpdateEvent(profile, _drawnFrame); + + // Overlay layers + if (!preview || Global.Configuration.OverlaysInPreview) + { + if (ApplicationManager.DesktopProfile.IsOverlayEnabled) + { + ApplicationManager.DesktopProfile.UpdateOverlayLights(_drawnFrame); + } + + foreach (var @event in ApplicationManager.GetOverlayActiveProfiles()) + { + StartEvent(@event); + @event.UpdateOverlayLights(_drawnFrame); + } + + //Add the Light event that we're previewing to be rendered as an overlay (assuming it's not already active) + if (preview && Global.Configuration.OverlaysInPreview && !ApplicationManager.GetOverlayActiveProfiles().Contains(profile)) + { + StartEvent(profile); + profile.UpdateOverlayLights(_drawnFrame); + } + + if (Global.Configuration.IdleType != IdleEffects.None) + { + UpdateIdleEffects(_drawnFrame); + } + } + + Global.effengine.PushFrame(_drawnFrame); + + StopUnUpdatedEvents(); + PostUpdate?.Invoke(this, EventArgs.Empty); + } + + public void JsonGameStateUpdate(object? sender, JsonGameStateEventArgs eventArgs) + { + var gameId = eventArgs.GameId; + var profile = ApplicationManager.GetProfileFromAppId(gameId); + if (profile == null) + { + return; + } + + var gameStateType = profile.Config.GameStateType; + var json = eventArgs.Json; + + var gameState = JsonSerializer.Deserialize(json, gameStateType, GameStateJsonSerializerOptions) as IGameState; + + profile.SetGameState(gameState); + } + + public void GameStateUpdate(object? sender, IGameState gs) + { + try + { + if (gs is not NewtonsoftGameState newtonsoftGameState) + { + return; + } + + if (!newtonsoftGameState.Announce) + { + return; + } + + var provider = JObject.Parse(newtonsoftGameState.GetNode("provider")); + var appid = provider.GetValue("appid").ToString(); + var name = provider.GetValue("name").ToString().ToLowerInvariant(); + + Application? profile; + if ((profile = ApplicationManager.GetProfileFromAppId(appid)) == null && (profile = ApplicationManager.GetProfileFromProcessName(name)) == null) + { + return; + } + + // profile supports System.Text.Json but we received data on old endpoint + if (!profile.Config.GameStateType.IsAssignableTo(typeof(NewtonsoftGameState))) + { + var njGameState = JsonConvert.DeserializeObject(newtonsoftGameState.Json, profile.Config.GameStateType, new JsonSerializerSettings() + { + ContractResolver = ContractResolver, + }) as IGameState; + profile.SetGameState(njGameState); + return; + } + + var gameState = (NewtonsoftGameState)Activator.CreateInstance(profile.Config.GameStateType, newtonsoftGameState.Json); + profile.SetGameState(gameState); + } + catch (Exception e) + { + Global.logger.Warning(e, "Exception during GameStateUpdate(), error: "); + if (Debugger.IsAttached) + { + throw; + } + } + } + + public void ResetGameState(object? sender, string process) + { + var profile = ApplicationManager.GetProfileFromProcessName(process); + profile?.ResetGameState(); + } + + public void Dispose() + { + ApplicationManager.Dispose(); + _updateTimer.Dispose(200); + } + + public async Task DisposeAsync() + { + await ApplicationManager.DisposeAsync(); + _updateTimer.Dispose(200); + } +} + +/// +/// POCO that stores data about a type of layer. +/// +public class LayerHandlerMeta +{ + + /// Creates a new LayerHandlerMeta object from the given meta attribute and type. + public LayerHandlerMeta(Type type, LayerHandlerMetaAttribute? attribute) + { + Name = attribute?.Name ?? type.Name.CamelCaseToSpaceCase().TrimEndStr(" Layer Handler"); + Type = type; + // if the layer is in the Aurora.Settings.Layers namespace, make the IsDefault true unless otherwise specified. + // If it is in another namespace, it's probably a custom application layer and so make IsDefault false unless otherwise specified + IsDefault = attribute?.IsDefault ?? type.Namespace == "AuroraRgb.Settings.Layers"; + Order = attribute?.Order ?? 0; + } + + public string Name { get; } + public Type Type { get; } + public bool IsDefault { get; } + public int Order { get; } +} + + +/// +/// Attribute to provide additional meta data about layers for them to be registered. +/// +[AttributeUsage(AttributeTargets.Class, Inherited = false)] +public class LayerHandlerMetaAttribute : Attribute +{ + /// A different name for the layer. If not specified, will automatically take it from the layer's class name. + public string Name { get; set; } + + /// If true, this layer will be excluded from automatic registration. Default false. + public bool Exclude { get; set; } + + /// If true, this layer will be registered as a 'default' layer for all applications. Default true. + public bool IsDefault { get; set; } + + /// A number used when ordering the layer entry in the list. + /// Only to be used for layers that need to appear at the top/bottom of the list. + public int Order { get; set; } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Profiles/VariablePath.cs b/Project-Aurora/Project-Aurora/EffectsEngine/VariablePath.cs similarity index 99% rename from Project-Aurora/Project-Aurora/Profiles/VariablePath.cs rename to Project-Aurora/Project-Aurora/EffectsEngine/VariablePath.cs index 9852bbda9..61b919672 100644 --- a/Project-Aurora/Project-Aurora/Profiles/VariablePath.cs +++ b/Project-Aurora/Project-Aurora/EffectsEngine/VariablePath.cs @@ -3,7 +3,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace AuroraRgb.Profiles; +namespace AuroraRgb.EffectsEngine; [JsonConverter(typeof(VariablePathConverter))] public sealed class VariablePath diff --git a/Project-Aurora/Project-Aurora/Modules/AutomaticGsiPatcher.cs b/Project-Aurora/Project-Aurora/Modules/AutomaticGsiPatcher.cs index 27a7d3046..ccea4a9fb 100644 --- a/Project-Aurora/Project-Aurora/Modules/AutomaticGsiPatcher.cs +++ b/Project-Aurora/Project-Aurora/Modules/AutomaticGsiPatcher.cs @@ -22,7 +22,7 @@ protected override async Task Initialize() var lsm = await LightingStateManagerModule.LightningStateManager; - lsm.EventAdded += (_, args) => + lsm.ApplicationManager.EventAdded += (_, args) => { var lightEvent = args.Application; Task.Run(async () => @@ -30,7 +30,7 @@ protected override async Task Initialize() await RunInstallation(userPromptTcs, lightEvent); }); }; - foreach (var application in lsm.Events.Values) + foreach (var application in lsm.ApplicationManager.Events.Values) { _ = Task.Run(async () => { diff --git a/Project-Aurora/Project-Aurora/Modules/GameStateListen/Http/HttpEndpointFactory.Profiles.cs b/Project-Aurora/Project-Aurora/Modules/GameStateListen/Http/HttpEndpointFactory.Profiles.cs index 57e576df5..d2d48cbbb 100644 --- a/Project-Aurora/Project-Aurora/Modules/GameStateListen/Http/HttpEndpointFactory.Profiles.cs +++ b/Project-Aurora/Project-Aurora/Modules/GameStateListen/Http/HttpEndpointFactory.Profiles.cs @@ -27,8 +27,8 @@ private static void ProcessGetProfiles(HttpListenerContext context) response.ContentType = "application/json"; response.Headers = WebHeaderCollection; - var activeProfile = ConvertProfile(LightingStateManagerModule.LightningStateManager.Result.GetCurrentProfile()); - var activeOverlays = LightingStateManagerModule.LightningStateManager.Result.GetOverlayActiveProfiles() + var activeProfile = ConvertProfile(LightingStateManagerModule.LightningStateManager.Result.ApplicationManager.GetCurrentProfile()); + var activeOverlays = LightingStateManagerModule.LightningStateManager.Result.ApplicationManager.GetOverlayActiveProfiles() .Select(ConvertProfile); var responseJson = new ProfilesResponse(activeProfile, activeOverlays); diff --git a/Project-Aurora/Project-Aurora/Modules/Icue/IcueGsi.cs b/Project-Aurora/Project-Aurora/Modules/Icue/IcueGsi.cs index 3aab927fc..39e4066f9 100644 --- a/Project-Aurora/Project-Aurora/Modules/Icue/IcueGsi.cs +++ b/Project-Aurora/Project-Aurora/Modules/Icue/IcueGsi.cs @@ -11,6 +11,7 @@ public class IcueGsi public HashSet States { get; } = []; public Dictionary EventTimestamps { get; } = []; public string GameName { get; private set; } = string.Empty; + public Dictionary StateStore { get; } = new(); private GsiHandler? _gsiHandler; @@ -24,6 +25,8 @@ public void SetGsiHandler(GsiHandler gsiHandler, IcueGsiConnectionEventArgs e) GameName = e.GameName; GameChanged?.Invoke(this, EventArgs.Empty); + + StateStore.TryAdd(GameName, new IcueGsiStateStore(GameName)); } public void ClearGsiHandler() @@ -47,6 +50,7 @@ public void ClearGsiHandler() private void OnStateAdded(object? sender, IcueStateEventArgs icueStateEventArgs) { States.Add(icueStateEventArgs.StateName); + StateStore[GameName].AddState(icueStateEventArgs.StateName); } private void OnStateRemoved(object? sender, IcueStateEventArgs icueStateEventArgs) @@ -62,6 +66,7 @@ private void OnStatesCleared(object? sender, EventArgs e) private void OnEventAdded(object? sender, IcueStateEventArgs icueStateEventArgs) { EventTimestamps[icueStateEventArgs.StateName] = DateTimeOffset.Now.ToUnixTimeMilliseconds(); + StateStore[GameName].AddEvent(icueStateEventArgs.StateName); EventReceived?.Invoke(this, icueStateEventArgs); } } \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Modules/Icue/IcueGsiStateStore.cs b/Project-Aurora/Project-Aurora/Modules/Icue/IcueGsiStateStore.cs new file mode 100644 index 000000000..273c89143 --- /dev/null +++ b/Project-Aurora/Project-Aurora/Modules/Icue/IcueGsiStateStore.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; + +namespace AuroraRgb.Modules.Icue; + +public sealed class IcueGsiStateStore(string gameName) +{ + private readonly HashSet _states = []; + private readonly HashSet _events = []; + + public event EventHandler? StateChanged; + + public string GameName { get; } = gameName; + + public IReadOnlySet States => _states; + + public IReadOnlySet Events => _events; + + public void AddState(string state) + { + if (_states.Add(state)) + StateChanged?.Invoke(this, EventArgs.Empty); + } + + public void AddEvent(string state) + { + if (_events.Add(state)) + StateChanged?.Invoke(this, EventArgs.Empty); + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Modules/Inputs/InputEvents.cs b/Project-Aurora/Project-Aurora/Modules/Inputs/InputEvents.cs index 785c7f358..43b43efaa 100644 --- a/Project-Aurora/Project-Aurora/Modules/Inputs/InputEvents.cs +++ b/Project-Aurora/Project-Aurora/Modules/Inputs/InputEvents.cs @@ -5,6 +5,7 @@ using AuroraRgb.Utils; using Linearstar.Windows.RawInput; using Linearstar.Windows.RawInput.Native; +using Microsoft.Win32; using User32 = AuroraRgb.Utils.User32; namespace AuroraRgb.Modules.Inputs; @@ -55,6 +56,7 @@ public sealed class InputEvents : IInputEvents public bool Windows { get; private set; } private delegate nint WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + // ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable to keep reference for garbage collector private readonly WndProc? _fnWndProcHook; private readonly nint _originalWndProc; @@ -73,8 +75,34 @@ public InputEvents() _fnWndProcHook = Hook; var newLong = Marshal.GetFunctionPointerForDelegate(_fnWndProcHook); User32.SetWindowLongPtr(_hWnd, -4, newLong); + + // can't detect Win + L, just clear when it happens + SystemEvents.SessionSwitch += SystemEvents_SessionSwitch; + } + + private void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e) + { + switch (e.Reason) + { + case SessionSwitchReason.SessionLock: + case SessionSwitchReason.SessionUnlock: + ClearButtons(); + break; + } } - + + private void ClearButtons() + { + var pressedKeys = new List(_pressedKeySequence); + pressedKeys.Reverse(); + _pressedKeySequence.Clear(); + PressedKeys = []; + foreach (var pressedKey in pressedKeys) + { + KeyUp?.Invoke(this, new KeyboardKeyEventArgs(pressedKey, false, pressedKeys)); + } + } + private nint Hook(IntPtr hwnd, uint msg, IntPtr wparam, IntPtr lparam) { const int wmInput = 0x00FF; @@ -163,12 +191,13 @@ private bool ProcessKeyList(bool down, Keys key) // this key is already processed return false; } + _pressedKeySequence.Add(key); } else { var removed = _pressedKeySequence.Remove(key); - return removed; // return if key is removed + return removed; // return if key is removed } return true; @@ -242,7 +271,8 @@ private bool DeviceOnMouseInput(RawMouse mouseData) return mouseKeyEvent.Intercepted; } - public TimeSpan GetTimeSinceLastInput() { + public TimeSpan GetTimeSinceLastInput() + { var inf = new User32.TagLastInputInfo { cbSize = (uint)Marshal.SizeOf() }; return !User32.GetLastInputInfo(ref inf) ? new TimeSpan(0) : @@ -253,5 +283,6 @@ public void Dispose() { if (_disposed) return; _disposed = true; + SystemEvents.SessionSwitch -= SystemEvents_SessionSwitch; } } \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Profiles/BlackOps6/Bo6Profile.cs b/Project-Aurora/Project-Aurora/Profiles/BlackOps6/Bo6Profile.cs index 4480c49f7..0cd173541 100644 --- a/Project-Aurora/Project-Aurora/Profiles/BlackOps6/Bo6Profile.cs +++ b/Project-Aurora/Project-Aurora/Profiles/BlackOps6/Bo6Profile.cs @@ -7,6 +7,7 @@ using AuroraRgb.Settings.Layers; using AuroraRgb.Settings.Overrides.Logic; using AuroraRgb.Settings.Overrides.Logic.Boolean; +using AuroraRgb.Settings.Overrides.Logic.Number; using AuroraRgb.Utils; using Common.Devices; using Common.Utils; @@ -15,16 +16,8 @@ namespace AuroraRgb.Profiles.BlackOps6; -[JsonObject(ItemTypeNameHandling = TypeNameHandling.None)] public class Bo6Profile : ApplicationProfile { - [OnDeserialized] - void OnDeserialized(StreamingContext context) - { - if (Layers.Any(lyr => lyr.Handler is IcueSdkLayerHandler)) return; - Layers.Add(new Layer("iCUE Lighting", new IcueSdkLayerHandler())); - } - public override void Reset() { base.Reset(); @@ -165,6 +158,16 @@ public override void Reset() new OverrideLogicBuilder() .SetDynamicBoolean("_Enabled", new BooleanIcueState("BO6_NearDeath")) ), + new Layer("WZ Circle Alert", new SolidFillLayerHandler + { + Properties = + { + PrimaryColor = Color.FromArgb(93, 226, 215) + } + }, + new OverrideLogicBuilder() + .SetDynamicFloat("_LayerOpacity", new NumberIcueEventFade("BO6_CircleAlert", 1)) + ), new Layer("On Kill", new SimpleParticleLayerHandler { Properties = new SimpleParticleLayerProperties @@ -258,11 +261,44 @@ public override void Reset() new OverrideLogicBuilder() .SetDynamicBoolean("_Enabled", new BooleanIcueState("BO6_ThemeMain")) ), + new Layer("WZ Cinematic", new SimpleParticleLayerHandler + { + Properties = + { + MinSpawnTime = 0.1f, + MaxSpawnTime = 0.1f, + MinSpawnAmount = 1, + MaxSpawnAmount = 1, + MinInitialVelocityX = 0.0f, + MaxInitialVelocityX = 0.0f, + MinInitialVelocityY = -1.0f, + MaxInitialVelocityY = -1.0f, + MinLifetime = 0.0f, + MaxLifetime = 2.0f, + AccelerationX = 0.0f, + AccelerationY = 0.5f, + DragX = 0.0f, + DragY = 0.0f, + MinSize = 6.0f, + MaxSize = 6.0f, + DeltaSize = 0.0f, + SpawnLocation = ParticleSpawnLocations.BottomEdge, + ParticleColorStops = new ColorStopCollection + { + { 0f, Color.FromArgb(250, 104, 0) }, + { 1f, Color.FromArgb(0, 0, 0, 0) } + }, + Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("SpawningEnabled", new BooleanIcueState("BO6_Cinematic")) + ), new Layer("Background", new SolidColorLayerHandler { Properties = new LayerHandlerProperties { - _PrimaryColor = Color.FromArgb(250, 104, 0), + _PrimaryColor = Color.FromArgb(171, 0, 110), _Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), }, }, diff --git a/Project-Aurora/Project-Aurora/Profiles/BlackOps7/Bo7Application.cs b/Project-Aurora/Project-Aurora/Profiles/BlackOps7/Bo7Application.cs new file mode 100644 index 000000000..15672db2d --- /dev/null +++ b/Project-Aurora/Project-Aurora/Profiles/BlackOps7/Bo7Application.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AuroraRgb.Modules; + +namespace AuroraRgb.Profiles.BlackOps7; + +public sealed class Bo7Application() : Application(new LightEventConfig +{ + Name = "Black Ops 7", + ID = "blackops7", + ProcessNames = [], // will be set dynamically when iCUE game is "BlackOps6" + ProfileType = typeof(Bo7Profile), + OverviewControlType = typeof(ControlBo7), + IconURI = "Resources/bo7.png", + EnableByDefault = true, +}) +{ + public override async Task Initialize(CancellationToken cancellationToken) + { + var baseInit = await base.Initialize(cancellationToken); + + IcueModule.AuroraIcueServer.Gsi.GameChanged += IcueSdkGameChanged; + SetProfileApplication(); + + return baseInit; + } + + private void IcueSdkGameChanged(object? sender, EventArgs e) + { + SetProfileApplication(); + } + + private void SetProfileApplication() + { + var sdkGameProcess = IcueModule.AuroraIcueServer.Gsi.GameName; + if (sdkGameProcess != "BlackOps7") + { + Config.ProcessNames = []; + return; + } + + Config.ProcessNames = ["cod.exe"]; + } + + public override void Dispose() + { + base.Dispose(); + + IcueModule.AuroraIcueServer.Gsi.GameChanged -= IcueSdkGameChanged; + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Profiles/BlackOps7/Bo7Profile.cs b/Project-Aurora/Project-Aurora/Profiles/BlackOps7/Bo7Profile.cs new file mode 100644 index 000000000..0e52762b6 --- /dev/null +++ b/Project-Aurora/Project-Aurora/Profiles/BlackOps7/Bo7Profile.cs @@ -0,0 +1,277 @@ +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Runtime.Serialization; +using AuroraRgb.EffectsEngine; +using AuroraRgb.Settings; +using AuroraRgb.Settings.Layers; +using AuroraRgb.Settings.Overrides.Logic; +using AuroraRgb.Settings.Overrides.Logic.Boolean; +using AuroraRgb.Settings.Overrides.Logic.Number; +using AuroraRgb.Utils; +using Common.Devices; +using Common.Utils; +using Newtonsoft.Json; +using Color = System.Drawing.Color; + +namespace AuroraRgb.Profiles.BlackOps7; + +public class Bo7Profile : ApplicationProfile +{ + public override void Reset() + { + base.Reset(); + OverlayLayers = + [ + new Layer("Loading Animation", new RadialLayerHandler + { + Properties = new RadialLayerProperties + { + Brush = new SegmentedRadialBrushFactory(new ColorStopCollection + { + { 0.0f, Color.Red }, + { 0.10797343f, Color.Orange }, + { 0.2690457f, Color.FromArgb(0, 121, 212, 0) }, + { 0.42602244f, Color.FromArgb(93, 222, 0) }, + { 0.5946844f, Color.Aqua }, + { 0.7819433f, Color.FromArgb(0, 68, 187, 187) }, + { 1.0f, Color.Red } + }) + }, + }, new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("SDKL_SmokeBlackout")) + ), + new Layer("Endgame", new BreathingLayerHandler + { + Properties = new BreathingLayerHandlerProperties + { + _PrimaryColor = CommonColorUtils.FastColor(35, 5, 215), + SecondaryColor = CommonColorUtils.FastColor(255, 0, 29), + CurveFunction = CurveFunction.SineSquared, + EffectSpeed = 15, + Sequence = new KeySequence + { + Keys = [DeviceKeys.ARROW_LEFT, DeviceKeys.ARROW_DOWN, DeviceKeys.ARROW_RIGHT, DeviceKeys.ARROW_UP], + }, + } + }, new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanOr( + [ + new BooleanIcueState("BO7_Victory"), + new BooleanIcueState("BO7_Defeat") + ] + ) + ) + ), + ]; + Layers = + [ + new Layer("Death", new SolidColorLayerHandler + { + Properties = new LayerHandlerProperties + { + _PrimaryColor = Color.Red, + _Sequence = new KeySequence + { + Keys = + [ + DeviceKeys.ESC, + DeviceKeys.TILDE, + DeviceKeys.TAB, + DeviceKeys.CAPS_LOCK, + DeviceKeys.LEFT_SHIFT, + DeviceKeys.LEFT_CONTROL, + DeviceKeys.LEFT_WINDOWS, + DeviceKeys.LEFT_ALT, + DeviceKeys.SPACE, + DeviceKeys.RIGHT_ALT, + DeviceKeys.FN_Key, + DeviceKeys.APPLICATION_SELECT, + DeviceKeys.RIGHT_CONTROL, + DeviceKeys.RIGHT_SHIFT, + DeviceKeys.ENTER, + DeviceKeys.BACKSPACE, + DeviceKeys.F12, + DeviceKeys.F11, + DeviceKeys.F10, + DeviceKeys.F9, + DeviceKeys.F8, + DeviceKeys.F7, + DeviceKeys.F6, + DeviceKeys.F5, + DeviceKeys.F4, + DeviceKeys.F3, + DeviceKeys.F2, + DeviceKeys.F1, + ], + Type = KeySequenceType.Sequence, + } + }, + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("BO7_Death")) + ), + new Layer("Near Death", new BlinkingLayerHandler + { + Properties = new BlinkingLayerHandlerProperties + { + _PrimaryColor = Color.Red, + SecondaryColor = CommonColorUtils.FastColor(238, 104, 62), + _Sequence = new KeySequence + { + Keys = + [ + DeviceKeys.ESC, + DeviceKeys.TILDE, + DeviceKeys.TAB, + DeviceKeys.CAPS_LOCK, + DeviceKeys.LEFT_SHIFT, + DeviceKeys.LEFT_CONTROL, + DeviceKeys.LEFT_WINDOWS, + DeviceKeys.LEFT_ALT, + DeviceKeys.SPACE, + DeviceKeys.RIGHT_ALT, + DeviceKeys.FN_Key, + DeviceKeys.APPLICATION_SELECT, + DeviceKeys.RIGHT_CONTROL, + DeviceKeys.RIGHT_SHIFT, + DeviceKeys.ENTER, + DeviceKeys.BACKSPACE, + DeviceKeys.F12, + DeviceKeys.F11, + DeviceKeys.F10, + DeviceKeys.F9, + DeviceKeys.F8, + DeviceKeys.F7, + DeviceKeys.F6, + DeviceKeys.F5, + DeviceKeys.F4, + DeviceKeys.F3, + DeviceKeys.F2, + DeviceKeys.F1, + ], + Type = KeySequenceType.Sequence, + }, + EffectSpeed = 10, + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("BO7_NearDeath")) + ), + new Layer("On Kill", new SimpleParticleLayerHandler + { + Properties = new SimpleParticleLayerProperties + { + _Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + MinSpawnTime = 0.1f, + MaxSpawnTime = 0.1f, + MinSpawnAmount = 1, + MaxSpawnAmount = 1, + MinInitialVelocityX = 0.0f, + MaxInitialVelocityX = 0.0f, + MinInitialVelocityY = -1.0f, + MaxInitialVelocityY = -1.0f, + MinLifetime = 0.0f, + MaxLifetime = 3.0f, + AccelerationX = 0.0f, + AccelerationY = 0.3f, + DragX = 0.0f, + DragY = 0.80147064f, + MinSize = 30.0f, + MaxSize = 35.0f, + DeltaSize = 7.0f, + SpawnLocation = ParticleSpawnLocations.BottomEdge, + ParticleColorStops = new ColorStopCollection + { + { 0f, Color.FromArgb(250, 104, 0) }, + { 1f, Color.FromArgb(0, 250, 103, 0) } + }, + SpawningEnabled = false, + PrimaryColor = Color.FromArgb(41, 126, 81), + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("SpawningEnabled", new BooleanIcueEventTriggered("SDKL_FlashOrange", 1.5)) + ), + new Layer("Gradient", new GradientLayerHandler + { + Properties = + { + GradientConfig = new LayerEffectConfig + { + Speed = 3f, + Angle = 0.0f, + GradientSize = 24.25f, + AnimationType = AnimationType.TranslateXy, + Brush = new EffectBrush(EffectBrush.BrushType.Linear, EffectBrush.BrushWrap.Repeat) + { + Start = new PointF(0, -0.5f), + Center = new PointF(0, 0), + End = new PointF(1, 1), + ColorGradients = new SortedDictionary + { + { .0f, Color.FromArgb(0, 250, 103, 0) }, + { 0.27f, Color.FromArgb(0, 250, 103, 0) }, + { 0.5f, Color.FromArgb(250, 103, 0) }, + { 0.74f, Color.FromArgb(0, 250, 103, 0) }, + { 1f, Color.FromArgb(0, 250, 103, 0) }, + }, + }, + }, + _Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + }, + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("BO7_ThemeMain")) + ), + new Layer("Gradient Reversed", new GradientLayerHandler + { + Properties = + { + GradientConfig = new LayerEffectConfig + { + Speed = 4.25f, + Angle = 0.0f, + GradientSize = 24.25f, + AnimationType = AnimationType.TranslateXy, + AnimationReverse = true, + Brush = new EffectBrush(EffectBrush.BrushType.Linear, EffectBrush.BrushWrap.Repeat) + { + Start = new PointF(0, -0.5f), + Center = new PointF(0, 0), + End = new PointF(1, 1), + ColorGradients = new SortedDictionary + { + { .0f, Color.FromArgb(0, 250, 103, 0) }, + { 0.27f, Color.FromArgb(0, 250, 103, 0) }, + { 0.5f, Color.FromArgb(250, 103, 0) }, + { 0.74f, Color.FromArgb(0, 250, 103, 0) }, + { 1f, Color.FromArgb(0, 250, 103, 0) }, + }, + }, + }, + _Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + }, + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("BO7_ThemeMain")) + ), + new Layer("Background", new SolidColorLayerHandler + { + Properties = new LayerHandlerProperties + { + _PrimaryColor = Color.FromArgb(76, 208, 208), + _Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + }, + }, + new OverrideLogicBuilder() + .SetLookupTable( + "_LayerOpacity", + new OverrideLookupTableBuilder() + .AddEntry(0.75, new BooleanIcueState("BO7_ThemeMain")) + .AddEntry(0.15, new BooleanConstant(true)) + ) + ), + ]; + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Profiles/BlackOps7/ControlBo7.xaml b/Project-Aurora/Project-Aurora/Profiles/BlackOps7/ControlBo7.xaml new file mode 100644 index 000000000..6778594c0 --- /dev/null +++ b/Project-Aurora/Project-Aurora/Profiles/BlackOps7/ControlBo7.xaml @@ -0,0 +1,43 @@ + + + + + + + + Integration is made trough iCUE + + If you want to change the animations, here are known state and event names: + + + + + + + + + + + + States: + + + Events: + + + + TBD + + + + TBD + + + + diff --git a/Project-Aurora/Project-Aurora/Profiles/BlackOps7/ControlBo7.xaml.cs b/Project-Aurora/Project-Aurora/Profiles/BlackOps7/ControlBo7.xaml.cs new file mode 100644 index 000000000..1db75932b --- /dev/null +++ b/Project-Aurora/Project-Aurora/Profiles/BlackOps7/ControlBo7.xaml.cs @@ -0,0 +1,9 @@ +namespace AuroraRgb.Profiles.BlackOps7; + +public partial class ControlBo7 +{ + public ControlBo7(Application _) + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Profiles/CSGO/CSGOProfile.cs b/Project-Aurora/Project-Aurora/Profiles/CSGO/CSGOProfile.cs index 2b2670acb..a21465c6b 100644 --- a/Project-Aurora/Project-Aurora/Profiles/CSGO/CSGOProfile.cs +++ b/Project-Aurora/Project-Aurora/Profiles/CSGO/CSGOProfile.cs @@ -1,4 +1,5 @@ using System.Drawing; +using AuroraRgb.EffectsEngine; using AuroraRgb.Profiles.CSGO.GSI.Nodes; using AuroraRgb.Profiles.CSGO.Layers; using AuroraRgb.Settings; diff --git a/Project-Aurora/Project-Aurora/Profiles/Discord/DiscordProfile.cs b/Project-Aurora/Project-Aurora/Profiles/Discord/DiscordProfile.cs index a92886552..452fe3a1a 100644 --- a/Project-Aurora/Project-Aurora/Profiles/Discord/DiscordProfile.cs +++ b/Project-Aurora/Project-Aurora/Profiles/Discord/DiscordProfile.cs @@ -1,4 +1,5 @@ using System.Drawing; +using AuroraRgb.EffectsEngine; using AuroraRgb.Settings; using AuroraRgb.Settings.Layers; using AuroraRgb.Settings.Overrides.Logic; diff --git a/Project-Aurora/Project-Aurora/Profiles/Dota 2/Dota2Profile.cs b/Project-Aurora/Project-Aurora/Profiles/Dota 2/Dota2Profile.cs index 7b4f42366..c9a534ce1 100644 --- a/Project-Aurora/Project-Aurora/Profiles/Dota 2/Dota2Profile.cs +++ b/Project-Aurora/Project-Aurora/Profiles/Dota 2/Dota2Profile.cs @@ -1,4 +1,5 @@ using System.Drawing; +using AuroraRgb.EffectsEngine; using AuroraRgb.Profiles.Dota_2.Layers; using AuroraRgb.Settings; using AuroraRgb.Settings.Layers; diff --git a/Project-Aurora/Project-Aurora/Profiles/GameState.cs b/Project-Aurora/Project-Aurora/Profiles/GameState.cs index b8ac01e5d..5d4f36f71 100644 --- a/Project-Aurora/Project-Aurora/Profiles/GameState.cs +++ b/Project-Aurora/Project-Aurora/Profiles/GameState.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text.Json.Serialization; +using AuroraRgb.EffectsEngine; using AuroraRgb.Nodes; using AuroraRgb.Utils; using FastMember; diff --git a/Project-Aurora/Project-Aurora/Profiles/Generic_Application/Control_GenericApplication.xaml.cs b/Project-Aurora/Project-Aurora/Profiles/Generic_Application/Control_GenericApplication.xaml.cs index 23ecc4310..85475669c 100644 --- a/Project-Aurora/Project-Aurora/Profiles/Generic_Application/Control_GenericApplication.xaml.cs +++ b/Project-Aurora/Project-Aurora/Profiles/Generic_Application/Control_GenericApplication.xaml.cs @@ -68,7 +68,7 @@ private async void AddProcessButton_OnClick(object sender, RoutedEventArgs e) var eventName = Path.GetFileNameWithoutExtension(filename); var lightingStateManager = Global.LightingStateManager!; - if (lightingStateManager.Events.TryGetValue(eventName, out _)) + if (lightingStateManager.ApplicationManager.Events.TryGetValue(eventName, out _)) { MessageBox.Show("Profile for this application already exists."); return; diff --git a/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/ControlGhostrunner.xaml b/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/ControlGhostrunner.xaml new file mode 100644 index 000000000..35e9e7228 --- /dev/null +++ b/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/ControlGhostrunner.xaml @@ -0,0 +1,64 @@ + + + + + + + + Integration is made trough iCUE + + If you want to change the animations, here are known state and event names: + + + + + + + + + + + + States: + + + Events: + + + + SDKL_ScreenReactive + GHST_Menu + GHST_Sensory + GHST_Zipline + GHST_Death + GHST_Blink + + GHST_CyberVoid + GHST_CyberVoidDestination + GHST_CyberVoidRed + GHST_Dharma + GHST_IndustryGreen + GHST_IndustryMain + GHST_IndustryOrange + GHST_IndustryRed + + + + SDKL_Melee + SDKL_PulseBarBlue + SDKL_PulseBarWhite + SDKL_AlertEdgesOrange + SDKL_AlertEdgesYellow + SDKL_SplashBlue + SDKL_SplashGrey + SDKL_SplashYellow + + + + diff --git a/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/ControlGhostrunner.xaml.cs b/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/ControlGhostrunner.xaml.cs new file mode 100644 index 000000000..9ff93c3fe --- /dev/null +++ b/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/ControlGhostrunner.xaml.cs @@ -0,0 +1,9 @@ +namespace AuroraRgb.Profiles.Ghostrunner; + +public partial class ControlGhostrunner +{ + public ControlGhostrunner(Application _) + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/GhostrunnerApplication.cs b/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/GhostrunnerApplication.cs new file mode 100644 index 000000000..da1cf299a --- /dev/null +++ b/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/GhostrunnerApplication.cs @@ -0,0 +1,52 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using AuroraRgb.Modules; + +namespace AuroraRgb.Profiles.Ghostrunner; + +public sealed class GhostrunnerApplication() : Application(new LightEventConfig +{ + Name = "Ghostrunner", + ID = "ghostrunner", + ProcessNames = [], // will be set dynamically when iCUE game is "Ghostrunner" + ProfileType = typeof(GhostrunnerProfile), + OverviewControlType = typeof(ControlGhostrunner), + IconURI = "Resources/Ghostrunner.png", + EnableByDefault = true, +}) +{ + public override async Task Initialize(CancellationToken cancellationToken) + { + var baseInit = await base.Initialize(cancellationToken); + + IcueModule.AuroraIcueServer.Gsi.GameChanged += IcueSdkGameChanged; + SetProfileApplication(); + + return baseInit; + } + + private void IcueSdkGameChanged(object? sender, EventArgs e) + { + SetProfileApplication(); + } + + private void SetProfileApplication() + { + var sdkGameProcess = IcueModule.AuroraIcueServer.Gsi.GameName; + if (sdkGameProcess != "Ghostrunner") + { + Config.ProcessNames = []; + return; + } + + Config.ProcessNames = ["ghostrunner-win64-shipping.exe"]; + } + + public override void Dispose() + { + base.Dispose(); + + IcueModule.AuroraIcueServer.Gsi.GameChanged -= IcueSdkGameChanged; + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/GhostrunnerProfile.cs b/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/GhostrunnerProfile.cs new file mode 100644 index 000000000..99439f02d --- /dev/null +++ b/Project-Aurora/Project-Aurora/Profiles/Ghostrunner/GhostrunnerProfile.cs @@ -0,0 +1,444 @@ +using System.Collections.Generic; +using System.Drawing; +using AuroraRgb.EffectsEngine; +using AuroraRgb.Settings; +using AuroraRgb.Settings.Layers; +using AuroraRgb.Settings.Overrides.Logic; +using AuroraRgb.Settings.Overrides.Logic.Boolean; +using AuroraRgb.Settings.Overrides.Logic.Number; +using AuroraRgb.Utils; +using Common.Devices; +using Common.Utils; + +namespace AuroraRgb.Profiles.Ghostrunner; + +public class GhostrunnerProfile : ApplicationProfile +{ + public override void Reset() + { + base.Reset(); + Layers = + [ + new Layer("Sensory", new BinaryCounterLayerHandler + { + Properties = + { + PrimaryColor = Color.FromArgb(154, 147, 66), + Sequence = new KeySequence + { + Type = KeySequenceType.Sequence, + Keys = + [ + DeviceKeys.ESC, DeviceKeys.TILDE, DeviceKeys.TAB, DeviceKeys.CAPS_LOCK, DeviceKeys.LEFT_SHIFT, DeviceKeys.LEFT_CONTROL, + DeviceKeys.LEFT_WINDOWS, DeviceKeys.BACKSLASH_UK, DeviceKeys.A, DeviceKeys.Q, DeviceKeys.ONE, DeviceKeys.F1, DeviceKeys.F2, + DeviceKeys.TWO, DeviceKeys.W, DeviceKeys.S, DeviceKeys.Z, DeviceKeys.LEFT_ALT, DeviceKeys.SPACE, DeviceKeys.X, DeviceKeys.D, + DeviceKeys.E, DeviceKeys.THREE, DeviceKeys.F3, DeviceKeys.FOUR, DeviceKeys.F4, DeviceKeys.FIVE, DeviceKeys.R, DeviceKeys.F, + DeviceKeys.C, DeviceKeys.V, DeviceKeys.G, DeviceKeys.T, DeviceKeys.SIX, DeviceKeys.F5, DeviceKeys.SEVEN, DeviceKeys.Y, + DeviceKeys.H, DeviceKeys.B, DeviceKeys.N, DeviceKeys.J, DeviceKeys.U, DeviceKeys.EIGHT, DeviceKeys.F6, DeviceKeys.F7, + DeviceKeys.NINE, DeviceKeys.I, DeviceKeys.K, DeviceKeys.M, DeviceKeys.COMMA, DeviceKeys.RIGHT_ALT, + DeviceKeys.PERIOD, DeviceKeys.L, DeviceKeys.O, DeviceKeys.ZERO, DeviceKeys.F8, DeviceKeys.F9, DeviceKeys.MINUS, + DeviceKeys.P, DeviceKeys.SEMICOLON, DeviceKeys.FORWARD_SLASH, DeviceKeys.FN_Key, DeviceKeys.APPLICATION_SELECT, + DeviceKeys.APOSTROPHE, DeviceKeys.OPEN_BRACKET, DeviceKeys.EQUALS, DeviceKeys.F10, + DeviceKeys.F11, DeviceKeys.BACKSPACE, DeviceKeys.CLOSE_BRACKET, DeviceKeys.HASHTAG, DeviceKeys.RIGHT_SHIFT, + DeviceKeys.RIGHT_CONTROL, DeviceKeys.ENTER, DeviceKeys.F12 + ] + } + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("GHST_Sensory")) + .SetDynamicDouble("_Value", + new NumberMathsOperation( + new NumberMathsOperation( + new NumberMathsOperation( + new NumberGSINumeric("LocalPCInfo/Time/MillisecondsSinceEpoch"), + MathsOperator.Div, + 16000 + ), + MathsOperator.Mod, + 20 + ), + MathsOperator.Mul, + 20 + ) + ) + ), + new Layer("Splash Blue?", new SolidColorLayerHandler + { + Properties = + { + Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + PrimaryColor = Color.FromArgb(100, 0, 0, 255), + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueEventTriggered("SDKL_SplashBlue", 2)) + ), + new Layer("Splash Gray?", new SolidColorLayerHandler + { + Properties = + { + Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + PrimaryColor = Color.FromArgb(100, 128, 128, 128), + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueEventTriggered("SDKL_SplashGray", 2)) + ), + new Layer("Splash Yellow?", new SolidColorLayerHandler + { + Properties = + { + Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + PrimaryColor = Color.FromArgb(100, 255, 255, 0), + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueEventTriggered("SDKL_SplashYellow", 2)) + ), + new Layer("Dead", new SolidColorLayerHandler + { + Properties = + { + PrimaryColor = Color.FromArgb(255, 0, 74), + Sequence = + { + Type = KeySequenceType.Sequence, + Keys = + [ + DeviceKeys.ESC, DeviceKeys.TWO, DeviceKeys.THREE, DeviceKeys.FOUR, DeviceKeys.T, DeviceKeys.Y, DeviceKeys.K, DeviceKeys.L, + DeviceKeys.SEMICOLON, DeviceKeys.FORWARD_SLASH, DeviceKeys.RIGHT_SHIFT, DeviceKeys.RIGHT_CONTROL, DeviceKeys.F12, + DeviceKeys.F11, DeviceKeys.EQUALS, DeviceKeys.MINUS, DeviceKeys.P, DeviceKeys.O, DeviceKeys.I, DeviceKeys.J, DeviceKeys.H, + DeviceKeys.V, DeviceKeys.C, DeviceKeys.X, DeviceKeys.Z, DeviceKeys.BACKSLASH_UK, DeviceKeys.LEFT_CONTROL, + ], + }, + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("GHST_Death")) + ), + new Layer("On Pulse Blue?", new SolidColorLayerHandler + { + Properties = + { + PrimaryColor = Color.Blue, + Sequence = + { + Type = KeySequenceType.Sequence, + Keys = + [ + DeviceKeys.TAB, DeviceKeys.Q, DeviceKeys.W, DeviceKeys.E, DeviceKeys.R, DeviceKeys.T, DeviceKeys.Y, DeviceKeys.U, DeviceKeys.I, + DeviceKeys.O, DeviceKeys.P, DeviceKeys.OPEN_BRACKET, DeviceKeys.CLOSE_BRACKET, DeviceKeys.ENTER, DeviceKeys.DELETE, + DeviceKeys.END, + DeviceKeys.PAGE_DOWN, DeviceKeys.NUM_SEVEN, DeviceKeys.NUM_EIGHT, DeviceKeys.NUM_NINE, DeviceKeys.NUM_PLUS, + DeviceKeys.CAPS_LOCK, + DeviceKeys.A, DeviceKeys.S, DeviceKeys.D, DeviceKeys.F, DeviceKeys.G, DeviceKeys.H, DeviceKeys.J, DeviceKeys.K, DeviceKeys.L, + DeviceKeys.SEMICOLON, DeviceKeys.APOSTROPHE, DeviceKeys.HASHTAG, DeviceKeys.NUM_FOUR, DeviceKeys.NUM_FIVE, DeviceKeys.NUM_SIX, + ] + } + } + }, + new OverrideLogicBuilder() + .SetDynamicFloat("_LayerOpacity", new NumberIcueEventFade("SDKL_PulseBarBlue", 2)) + ), + new Layer("On Deflect", new SolidColorLayerHandler + { + Properties = + { + PrimaryColor = Color.White, + Sequence = + { + Type = KeySequenceType.Sequence, + Keys = + [ + DeviceKeys.TAB, DeviceKeys.Q, DeviceKeys.W, DeviceKeys.E, DeviceKeys.R, DeviceKeys.T, DeviceKeys.Y, DeviceKeys.U, DeviceKeys.I, + DeviceKeys.O, DeviceKeys.P, DeviceKeys.OPEN_BRACKET, DeviceKeys.CLOSE_BRACKET, DeviceKeys.ENTER, DeviceKeys.DELETE, + DeviceKeys.END, + DeviceKeys.PAGE_DOWN, DeviceKeys.NUM_SEVEN, DeviceKeys.NUM_EIGHT, DeviceKeys.NUM_NINE, DeviceKeys.NUM_PLUS, + DeviceKeys.CAPS_LOCK, + DeviceKeys.A, DeviceKeys.S, DeviceKeys.D, DeviceKeys.F, DeviceKeys.G, DeviceKeys.H, DeviceKeys.J, DeviceKeys.K, DeviceKeys.L, + DeviceKeys.SEMICOLON, DeviceKeys.APOSTROPHE, DeviceKeys.HASHTAG, DeviceKeys.NUM_FOUR, DeviceKeys.NUM_FIVE, DeviceKeys.NUM_SIX, + ] + } + } + }, + new OverrideLogicBuilder() + .SetDynamicFloat("_LayerOpacity", new NumberIcueEventFade("SDKL_PulseBarWhite", 2)) + ), + new Layer("On Kill", new SimpleParticleLayerHandler + { + Properties = + { + SpawnLocation = ParticleSpawnLocations.Random, + ParticleColorStops = new ColorStopCollection + { + { 0.0f, Color.Red }, + { 1.0f, Color.FromArgb(0, 255, 0, 0) }, + }, + MinSpawnTime = 0.1f, + MaxSpawnTime = 0.1f, + MinSpawnAmount = 1, + MaxSpawnAmount = 1, + MinInitialVelocityX = 0.0f, + MaxInitialVelocityX = 0.0f, + MinInitialVelocityY = 0.0f, + MaxInitialVelocityY = 0.0f, + MinLifetime = 1.0f, + MaxLifetime = 2.0f, + AccelerationX = 0.0f, + AccelerationY = 0.0f, + DragX = 0.0f, + DragY = 0.0f, + MinSize = 30.0f, + MaxSize = 40.0f, + DeltaSize = 0.0f, + Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("SpawningEnabled", new BooleanIcueEventTriggered("SDKL_Melee", 0.6)) + ), + new Layer("AlertYellow", new SolidColorLayerHandler + { + Properties = + { + PrimaryColor = Color.Yellow, + Sequence = + { + Type = KeySequenceType.Sequence, + Keys = + [ + DeviceKeys.ESC, DeviceKeys.TILDE, DeviceKeys.TAB, DeviceKeys.CAPS_LOCK, DeviceKeys.LEFT_SHIFT, DeviceKeys.LEFT_CONTROL, + DeviceKeys.LEFT_WINDOWS, DeviceKeys.LEFT_ALT, DeviceKeys.SPACE, DeviceKeys.RIGHT_ALT, DeviceKeys.FN_Key, + DeviceKeys.APPLICATION_SELECT, DeviceKeys.RIGHT_CONTROL, DeviceKeys.RIGHT_SHIFT, DeviceKeys.ENTER, DeviceKeys.BACKSPACE, + DeviceKeys.F12, DeviceKeys.F11, DeviceKeys.F10, DeviceKeys.F9, DeviceKeys.F8, DeviceKeys.F7, DeviceKeys.F6, + DeviceKeys.F5, DeviceKeys.F4, DeviceKeys.F3, DeviceKeys.F2, DeviceKeys.F1 + ] + }, + }, + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueEventTriggered("SDKL_AlertEdgesYellow", 2)) + .SetDynamicFloat("_LayerOpacity", new NumberIcueEventFade("SDKL_AlertEdgesYellow", 2)) + ), + new Layer("AlertOrange", new SolidColorLayerHandler + { + Properties = + { + PrimaryColor = Color.FromArgb(255, 180, 0), + Sequence = + { + Type = KeySequenceType.Sequence, + Keys = + [ + DeviceKeys.ESC, DeviceKeys.TILDE, DeviceKeys.TAB, DeviceKeys.CAPS_LOCK, DeviceKeys.LEFT_SHIFT, DeviceKeys.LEFT_CONTROL, + DeviceKeys.LEFT_WINDOWS, DeviceKeys.LEFT_ALT, DeviceKeys.SPACE, DeviceKeys.RIGHT_ALT, DeviceKeys.FN_Key, + DeviceKeys.APPLICATION_SELECT, DeviceKeys.RIGHT_CONTROL, DeviceKeys.RIGHT_SHIFT, DeviceKeys.ENTER, DeviceKeys.BACKSPACE, + DeviceKeys.F12, DeviceKeys.F11, DeviceKeys.F10, DeviceKeys.F9, DeviceKeys.F8, DeviceKeys.F7, DeviceKeys.F6, + DeviceKeys.F5, DeviceKeys.F4, DeviceKeys.F3, DeviceKeys.F2, DeviceKeys.F1 + ] + }, + }, + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueEventTriggered("SDKL_AlertEdgesOrange", 2)) + .SetDynamicFloat("_LayerOpacity", new NumberIcueEventFade("SDKL_AlertEdgesOrange", 2)) + ), + new Layer("Zipline (Right)", new GradientLayerHandler + { + Properties = + { + GradientConfig = + { + Speed = 10, + Angle = -45, + GradientSize = 38.75f, + AnimationType = AnimationType.TranslateXy, + AnimationReverse = true, + Brush = new EffectBrush(EffectBrush.BrushType.Linear, EffectBrush.BrushWrap.Repeat) + { + Start = new PointF(0, -0.5f), + Center = new PointF(0, 0), + End = new PointF(1, 1), + ColorGradients = new SortedDictionary + { + { 0, Color.FromArgb(0, 77, 16, 190) }, + { 0.25, Color.FromArgb(0, 255, 179, 0) }, + { 0.5, Color.FromArgb(255, 180, 0) }, + { 0.75, Color.FromArgb(0, 255, 179, 0) }, + { 1, Color.FromArgb(0, 145, 155, 176) }, + }, + }, + }, + Sequence = + { + Type = KeySequenceType.FreeForm, + Freeform = + { + X = 290, Y = 0, + Width = 550, + Height = 230 + } + }, + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("GHST_Zipline")) + ), + new Layer("Zipline (Left)", new GradientLayerHandler + { + Properties = + { + GradientConfig = + { + Speed = 10, + Angle = 45, + GradientSize = 38.75f, + AnimationType = AnimationType.TranslateXy, + AnimationReverse = false, + Brush = new EffectBrush(EffectBrush.BrushType.Linear, EffectBrush.BrushWrap.Repeat) + { + Start = new PointF(0, -0.5f), + Center = new PointF(0, 0), + End = new PointF(1, 1), + ColorGradients = new SortedDictionary + { + { 0, Color.FromArgb(0, 77, 16, 190) }, + { 0.25, Color.FromArgb(0, 255, 179, 0) }, + { 0.5, Color.FromArgb(255, 180, 0) }, + { 0.75, Color.FromArgb(0, 255, 179, 0) }, + { 1, Color.FromArgb(0, 145, 155, 176) }, + }, + }, + }, + Sequence = + { + Type = KeySequenceType.FreeForm, + Freeform = + { + X = 0, Y = 0, + Width = 290, + Height = 230 + } + }, + }, + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("GHST_Zipline")) + ), + new Layer("Main Menu particles", new SimpleParticleLayerHandler + { + Properties = + { + SpawnLocation = ParticleSpawnLocations.Random, + ParticleColorStops = new ColorStopCollection + { + { 0.0f, Color.Transparent }, + { 0.25f, Color.DeepSkyBlue }, + { 0.5f, Color.Purple }, + { 0.75f, Color.FromArgb(0, 131, 200) }, + { 1.0f, Color.FromArgb(0, 0, 131, 200) }, + }, + MinSpawnTime = 0, + MaxSpawnTime = 2, + MinSpawnAmount = 7, + MaxSpawnAmount = 7, + MinLifetime = 1, + MaxLifetime = 5, + MinInitialVelocityX = 0, + MaxInitialVelocityX = 0, + MinInitialVelocityY = 0, + MaxInitialVelocityY = 0, + AccelerationX = 0f, + AccelerationY = 0.0f, + DragX = 0f, + DragY = 0f, + MinSize = 8, + MaxSize = 8, + DeltaSize = 0, + _Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("GHST_Menu")) + ), + new Layer("Main Menu binary", new BinaryCounterLayerHandler + { + Properties = + { + PrimaryColor = Color.Maroon, + Sequence = new KeySequence + { + Type = KeySequenceType.Sequence, + Keys = + [ + DeviceKeys.ESC, DeviceKeys.TILDE, DeviceKeys.TAB, DeviceKeys.CAPS_LOCK, DeviceKeys.LEFT_SHIFT, DeviceKeys.LEFT_CONTROL, + DeviceKeys.LEFT_WINDOWS, DeviceKeys.BACKSLASH_UK, DeviceKeys.A, DeviceKeys.Q, DeviceKeys.ONE, DeviceKeys.F1, DeviceKeys.F2, + DeviceKeys.TWO, DeviceKeys.W, DeviceKeys.S, DeviceKeys.Z, DeviceKeys.LEFT_ALT, DeviceKeys.SPACE, DeviceKeys.X, DeviceKeys.D, + DeviceKeys.E, DeviceKeys.THREE, DeviceKeys.F3, DeviceKeys.FOUR, DeviceKeys.F4, DeviceKeys.FIVE, DeviceKeys.R, DeviceKeys.F, + DeviceKeys.C, DeviceKeys.V, DeviceKeys.G, DeviceKeys.T, DeviceKeys.SIX, DeviceKeys.F5, DeviceKeys.SEVEN, DeviceKeys.Y, + DeviceKeys.H, DeviceKeys.B, DeviceKeys.N, DeviceKeys.J, DeviceKeys.U, DeviceKeys.EIGHT, DeviceKeys.F6, DeviceKeys.F7, + DeviceKeys.NINE, DeviceKeys.I, DeviceKeys.K, DeviceKeys.M, DeviceKeys.COMMA, DeviceKeys.RIGHT_ALT, + DeviceKeys.PERIOD, DeviceKeys.L, DeviceKeys.O, DeviceKeys.ZERO, DeviceKeys.F8, DeviceKeys.F9, DeviceKeys.MINUS, + DeviceKeys.P, DeviceKeys.SEMICOLON, DeviceKeys.FORWARD_SLASH, DeviceKeys.FN_Key, DeviceKeys.APPLICATION_SELECT, + DeviceKeys.APOSTROPHE, DeviceKeys.OPEN_BRACKET, DeviceKeys.EQUALS, DeviceKeys.F10, + DeviceKeys.F11, DeviceKeys.BACKSPACE, DeviceKeys.CLOSE_BRACKET, DeviceKeys.HASHTAG, DeviceKeys.RIGHT_SHIFT, + DeviceKeys.RIGHT_CONTROL, DeviceKeys.ENTER, DeviceKeys.F12 + ] + } + } + }, + new OverrideLogicBuilder() + .SetDynamicBoolean("_Enabled", new BooleanIcueState("GHST_Menu")) + .SetDynamicDouble("_Value", + new NumberMathsOperation( + new NumberMathsOperation( + new NumberMathsOperation( + new NumberGSINumeric("LocalPCInfo/Time/MillisecondsSinceEpoch"), + MathsOperator.Div, + 16000 + ), + MathsOperator.Mod, + 20 + ), + MathsOperator.Mul, + 20 + ) + ) + ), + new Layer("Zone Background", new BreathingLayerHandler + { + Properties = + { + PrimaryColor = Color.Transparent, + SecondaryColor = Color.FromArgb(5, 50, 7), + EffectSpeed = 4, + CurveFunction = CurveFunction.Sine, + Sequence = new KeySequence(Effects.Canvas.WholeFreeForm), + _LayerOpacity = 0.3, + } + }, + new OverrideLogicBuilder() + .SetLookupTable( + "SecondaryColor", + new OverrideLookupTableBuilder() + .AddEntry(Color.FromArgb(24, 150, 255), new BooleanIcueState("GHST_CyberVoid")) + .AddEntry(Color.FromArgb(255, 47, 0), new BooleanIcueState("GHST_CyberVoidRed")) + .AddEntry(Color.FromArgb(35, 117, 46), new BooleanIcueState("GHST_CyberVoidDestination")) + .AddEntry(Color.FromArgb(255, 40, 0), new BooleanIcueState("GHST_IndustryRed")) + .AddEntry(Color.Maroon, new BooleanIcueState("GHST_Dharma")) + .AddEntry(Color.FromArgb(5, 50, 7), new BooleanIcueState("GHST_IndustryGreen")) + .AddEntry(Color.FromArgb(13, 14, 30), new BooleanIcueState("GHST_IndustryMain")) + .AddEntry(Color.FromArgb(133, 61, 11), new BooleanIcueState("GHST_IndustryOrange")) + ) + ), + new Layer("Background", new SolidFillLayerHandler + { + Properties = + { + PrimaryColor = Color.FromArgb(0, 28, 64) + } + }), + ]; + } +} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Profiles/LeagueOfLegends/LoLGSIProfile.cs b/Project-Aurora/Project-Aurora/Profiles/LeagueOfLegends/LoLGSIProfile.cs index d05f82a20..84c128a53 100644 --- a/Project-Aurora/Project-Aurora/Profiles/LeagueOfLegends/LoLGSIProfile.cs +++ b/Project-Aurora/Project-Aurora/Profiles/LeagueOfLegends/LoLGSIProfile.cs @@ -1,4 +1,5 @@ using System.Drawing; +using AuroraRgb.EffectsEngine; using AuroraRgb.Profiles.LeagueOfLegends.GSI.Nodes; using AuroraRgb.Settings; using AuroraRgb.Settings.Layers; diff --git a/Project-Aurora/Project-Aurora/Profiles/LightingStateManager.cs b/Project-Aurora/Project-Aurora/Profiles/LightingStateManager.cs deleted file mode 100755 index ab9a58724..000000000 --- a/Project-Aurora/Project-Aurora/Profiles/LightingStateManager.cs +++ /dev/null @@ -1,788 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.Json; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using System.Windows; -using AuroraRgb.Controls; -using AuroraRgb.Devices; -using AuroraRgb.EffectsEngine; -using AuroraRgb.Modules; -using AuroraRgb.Modules.GameStateListen; -using AuroraRgb.Modules.ProcessMonitor; -using AuroraRgb.Profiles.Desktop; -using AuroraRgb.Profiles.Generic_Application; -using AuroraRgb.Settings; -using AuroraRgb.Settings.Layers; -using AuroraRgb.Utils; -using Common.Utils; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using Newtonsoft.Json.Serialization; -using AssemblyExtensions = AuroraRgb.Utils.AssemblyExtensions; -using JsonSerializer = System.Text.Json.JsonSerializer; - -namespace AuroraRgb.Profiles; - -public sealed class ApplicationInitializedEventArgs(Application application) : EventArgs -{ - public Application Application { get; } = application; -} - -public sealed class LightingStateManager : IDisposable -{ - private static readonly JsonSerializerOptions GameStateJsonSerializerOptions = new() - { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, - TypeInfoResolverChain = { GameStateSourceGenerationContext.Default } - }; - - public event EventHandler? EventAdded; - public Dictionary Events { get; } = new() { { "desktop", new Desktop.Desktop() } }; - - private Desktop.Desktop DesktopProfile => (Desktop.Desktop)Events["desktop"]; - private Application? _currentEvent; - public Application CurrentEvent => _currentEvent ?? DesktopProfile; - - private readonly HashSet _startedEvents = []; - private readonly HashSet _updatedEvents = []; - - private Dictionary> EventProcesses { get; } = new(); - private Dictionary EventTitles { get; } = new(); - private Dictionary EventAppIDs { get; } = new(); - public Dictionary LayerHandlers { get; } = new(); - - public event EventHandler? PreUpdate; - public event EventHandler? PostUpdate; - - private readonly Func _isOverlayActiveProfile; - - private readonly Task _pluginManager; - private readonly Task _ipcListener; - private readonly Task _deviceManager; - private readonly Task _activeProcessMonitor; - private readonly Task _runningProcessMonitor; - - private bool Initialized { get; set; } - private readonly CancellationTokenSource _initializeCancelSource = new(); - - private readonly ConcurrentQueue> _initTaskQueue = new(); - private readonly SingleConcurrentThread _profileInitThread; - - public LightingStateManager(Task pluginManager, Task ipcListener, - Task deviceManager, Task activeProcessMonitor, Task runningProcessMonitor) - { - _pluginManager = pluginManager; - _ipcListener = ipcListener; - _deviceManager = deviceManager; - - _activeProcessMonitor = activeProcessMonitor; - _runningProcessMonitor = runningProcessMonitor; - Predicate processRunning = ProcessRunning; - _isOverlayActiveProfile = evt => evt.IsOverlayEnabled && - Array.Exists(evt.Config.ProcessNames, processRunning); - - _profileInitThread = new SingleConcurrentThread("ProfileInit", ProfileInitAction, ProfileInitExceptionCallback); - - _updateTimer = new SingleConcurrentThread("LightingStateManager", TimerUpdate, ExceptionCallback); - - bool ProcessRunning(string name) => _runningProcessMonitor.Result.IsProcessRunning(name); - } - - private static void ProfileInitExceptionCallback(object? arg1, SingleThreadExceptionEventArgs arg2) - { - Global.logger.Fatal(arg2.Exception, "Profile load failed"); - } - - private async Task ProfileInitAction() - { - if (_initTaskQueue.TryDequeue(out var action)) - { - await action.Invoke(); - _profileInitThread.Trigger(); - } - } - - private static void ExceptionCallback(object? sender, SingleThreadExceptionEventArgs eventArgs) - { - Global.logger.Error(eventArgs.Exception, "Unexpected error with LightingStateManager loop"); - } - - public async Task Initialize() - { - if (Initialized) - return; - - if (_initializeCancelSource.IsCancellationRequested) - return; - var cancellationToken = _initializeCancelSource.Token; - var defaultApps = EnumerateDefaultApps(); - var userApps = EnumerateUserApps(); - - // Register all Application types in the assembly - var profiles = defaultApps.Concat(userApps); - foreach (var inst in profiles) - await RegisterEvent(inst); - - // Register all layer types that are in the Aurora.Settings.Layers namespace. - // Do not register all that are inside the assembly since some are application-specific (e.g. minecraft health layer) - var layerTypes = from type in AssemblyExtensions.GetLoadableTypes(Assembly.GetExecutingAssembly()) - where type.GetInterfaces().Contains(typeof(ILayerHandler)) - let name = type.Name.CamelCaseToSpaceCase() - let meta = type.GetCustomAttribute() - where !type.IsGenericType - where meta is not { Exclude: true } - select (type, meta); - foreach (var (type, meta) in layerTypes) - LayerHandlers.Add(type, new LayerHandlerMeta(type, meta)); - - await DesktopProfile.Initialize(cancellationToken); - LoadSettings(); - await LoadPlugins(); - - // Listen for profile keybind triggers - // TODO make this optional - (await InputsModule.InputEvents).KeyDown += CheckProfileKeybinds; - - Initialized = true; - } - - public void InitializeApps() - { - var cancellationToken = _initializeCancelSource.Token; - foreach (var (_, profile) in Events) - { - _initTaskQueue.Enqueue(async () => - { - try - { - await profile.Initialize(cancellationToken); - EventAdded?.Invoke(this, new ApplicationInitializedEventArgs(profile)); - } - catch (Exception e) - { - Global.logger.Error(e, "Error initializing profile {Profile}", profile.GetType()); - } - }); - } - _profileInitThread.Trigger(); - } - - private static IEnumerable EnumerateDefaultApps() - { - return from type in AssemblyExtensions.GetLoadableTypes(Assembly.GetExecutingAssembly()) - where (type.BaseType == typeof(Application) || type.BaseType == typeof(GsiApplication)) && type != typeof(GenericApplication) && type != typeof(GsiApplication) - let inst = (Application)Activator.CreateInstance(type) - orderby inst.Config.Name - select inst; - } - - private static IEnumerable EnumerateUserApps() - { - var additionalProfilesPath = Path.Combine(Global.AppDataDirectory, "AdditionalProfiles"); - if (!Directory.Exists(additionalProfilesPath)) - { - return Array.Empty(); - } - - var userApps = from dir in Directory.EnumerateDirectories(additionalProfilesPath) - where File.Exists(Path.Combine(dir, "settings.json")) - select Path.GetFileName(dir); - - return userApps.Select(processName => new GenericApplication(processName)); - } - - private async Task LoadPlugins() - { - (await _pluginManager).ProcessManager(this); - } - - private void LoadSettings() - { - foreach (var kvp in Events - .Where(kvp => !Global.Configuration.ProfileOrder.Contains(kvp.Key) && kvp.Value is Application)) - { - Global.Configuration.ProfileOrder.Add(kvp.Key); - } - - Global.Configuration.ProfileOrder - .RemoveAll(key => !Events.ContainsKey(key) || Events[key] is not Application); - - PutProfileTop("games"); - PutProfileTop("logitech"); - PutProfileTop("icue"); - PutProfileTop("chroma"); - PutProfileTop("desktop"); - } - - private static void PutProfileTop(string profileId) - { - Global.Configuration.ProfileOrder.Remove(profileId); - Global.Configuration.ProfileOrder.Insert(0, profileId); - } - - public async Task RegisterEvent(Application application) - { - var profileId = application.Config.ID; - if (string.IsNullOrWhiteSpace(profileId) || !Events.TryAdd(profileId, application)) return; - - foreach (var exe in application.Config.ProcessNames) - { - AddEventProcess(exe, application); - } - - AddEventProcess(profileId, application); - - application.Config.ProcessNamesChanged += ConfigOnProcessNamesChanged(application); - - if (application.Config.ProcessTitles != null) - foreach (var titleRx in application.Config.ProcessTitles) - EventTitles.Add(titleRx, application); - - if (!string.IsNullOrWhiteSpace(profileId)) - EventAppIDs.Add(profileId, application); - - if (!Global.Configuration.ProfileOrder.Contains(profileId)) - { - Global.Configuration.ProfileOrder.Add(profileId); - } - - if (Initialized) - await application.Initialize(_initializeCancelSource.Token); - } - - private EventHandler ConfigOnProcessNamesChanged(Application application) - { - return (_, _) => - { - var profileId = application.Config.ID; - var keysToRemove = new List(); - foreach (var (s, applications) in EventProcesses) - { - foreach (var app in applications) - { - if (app.Config.ID == profileId) - { - keysToRemove.Add(s); - } - } - } - - foreach (var s in keysToRemove) - { - EventProcesses.Remove(s); - } - - foreach (var exe in application.Config.ProcessNames) - { - if (exe.Equals(profileId)) continue; - var processKey = exe.ToLower(); - AddEventProcess(processKey, application); - } - AddEventProcess(profileId, application); - }; - } - - private void AddEventProcess(string processKey, Application application) - { - if (!EventProcesses.TryGetValue(processKey, out var applicationList)) - { - applicationList = new SortedSet(new ApplicationPriorityComparer()); - EventProcesses[processKey] = applicationList; - } - - applicationList.Add(application); - } - - public void RemoveGenericProfile(string key) - { - if (!Events.TryGetValue(key, out var value)) return; - if (value is not GenericApplication profile) - return; - Events.Remove(key); - Global.Configuration.ProfileOrder.Remove(key); - - profile.Dispose(); - - var path = profile.GetProfileFolderPath(); - if (Directory.Exists(path)) - Directory.Delete(path, true); - } - - // Used to match a process's name and optional window title to a profile - private Application? GetProfileFromProcessData(string processName, string processTitle) - { - var processNameProfile = GetProfileFromProcessName(processName); - - if (processNameProfile == null) - return null; - - // Is title matching required? - if (processNameProfile.Config.ProcessTitles != null) - { - var processTitleProfile = GetProfileFromProcessTitle(processTitle); - - if (processTitleProfile != null && processTitleProfile.Equals(processNameProfile)) - { - return processTitleProfile; - } - } - else - { - return processNameProfile; - } - - return null; - } - - private Application? GetProfileFromProcessName(string process) - { - return EventProcesses.GetValueOrDefault(process)? - .FirstOrDefault(a => a.Settings?.IsEnabled ?? false); - } - - /// - /// Manually registers a layer. Only needed externally. - /// - public bool RegisterLayer() where T : ILayerHandler - { - var t = typeof(T); - if (LayerHandlers.ContainsKey(t)) return false; - var meta = t.GetCustomAttribute(); - LayerHandlers.Add(t, new LayerHandlerMeta(t, meta)); - return true; - } - - private Application? GetProfileFromProcessTitle(string title) - { - return EventTitles.Where(entry => entry.Key.IsMatch(title)) - .Select(kv => kv.Value) - .FirstOrDefault(); - } - - private Application? GetProfileFromAppId(string appid) - { - return !EventAppIDs.TryGetValue(appid, out var value) ? Events.GetValueOrDefault(appid) : value; - } - - private readonly SingleConcurrentThread _updateTimer; - - private long _nextProcessNameUpdate; - private long _currentTick; - private string _previewModeProfileKey = ""; - - private readonly EventIdle _idleE = new(); - - public string? PreviewProfileKey { - get => _previewModeProfileKey; - set => _previewModeProfileKey = value ?? string.Empty; - } - - private readonly Stopwatch _watch = new(); - - public Task InitUpdate() - { - _watch.Start(); - _updateTimer?.Trigger(); - return Task.CompletedTask; - } - - private void TimerUpdate() - { - GC.WaitForPendingFinalizers(); - if (Debugger.IsAttached) - { - Thread.Sleep(40); - } - - if (Global.isDebug) - Update(); - else - { - try - { - Update(); - } - catch (Exception exc) - { - Global.logger.Error(exc, "ProfilesManager.Update() Exception:"); - //TODO make below non-blocking - MessageBox.Show("Error while updating light effects: " + exc.Message); - } - } - _currentTick += _watch.ElapsedMilliseconds; - var millisecondsTimeout = Math.Max(Global.Configuration.UpdateDelay - _watch.ElapsedMilliseconds, 1); - Thread.Sleep(TimeSpan.FromMilliseconds(millisecondsTimeout)); - _updateTimer.Trigger(); - _watch.Restart(); - } - - private void UpdateProcess() - { - var pollingEnabled = Global.Configuration.DetectionMode is ApplicationDetectionMode.ForegroundApp or ApplicationDetectionMode.EventsAndForeground; - if (!pollingEnabled || _currentTick < _nextProcessNameUpdate) return; - _activeProcessMonitor.Result.UpdateActiveProcessPolling(); - _nextProcessNameUpdate = _currentTick + 2000L; - } - - private void UpdateIdleEffects(EffectFrame newFrame) - { - if (!User32.GetLastInputInfoOut(out var lastInput)) return; - var idleTime = Environment.TickCount - lastInput.dwTime; - - if (idleTime < Global.Configuration.IdleDelay * 60 * 1000) return; - if (Global.Configuration.TimeBasedDimmingEnabled && - Time.IsCurrentTimeBetween(Global.Configuration.TimeBasedDimmingStartHour, - Global.Configuration.TimeBasedDimmingStartMinute, - Global.Configuration.TimeBasedDimmingEndHour, - Global.Configuration.TimeBasedDimmingEndMinute)) return; - UpdateEvent(_idleE, newFrame); - } - - private void UpdateEvent(ILightEvent @event, EffectFrame frame) - { - StartEvent(@event); - @event.UpdateLights(frame); - } - - private void StartEvent(ILightEvent @event) - { - _updatedEvents.Add(@event); - - // Skip if event was already started - if (!_startedEvents.Add(@event)) return; - - @event.OnStart(); - } - - private void StopUnUpdatedEvents() - { - // Skip if there are no started events or started events are the same since last update - if (_startedEvents.Count == 0 || _startedEvents.SequenceEqual(_updatedEvents)) return; - - var eventsToStop = _startedEvents.Except(_updatedEvents).ToList(); - foreach (var eventToStop in eventsToStop) - eventToStop.OnStop(); - - _startedEvents.Clear(); - foreach (var updatedEvent in _updatedEvents) - { - _startedEvents.Add(updatedEvent); - } - } - - private bool _profilesDisabled; - private readonly EffectFrame _drawnFrame = new(); - - private static readonly DefaultContractResolver ContractResolver = new() - { - NamingStrategy = new SnakeCaseNamingStrategy() - { - OverrideSpecifiedNames = false - } - }; - - private void Update() - { - PreUpdate?.Invoke(this, EventArgs.Empty); - _updatedEvents.Clear(); - - //Blackout. TODO: Cleanup this a bit. Maybe push blank effect frame to keyboard incase it has existing stuff displayed - var dimmingStartTime = new TimeSpan(Global.Configuration.TimeBasedDimmingStartHour, - Global.Configuration.TimeBasedDimmingStartMinute, 0); - var dimmingEndTime = new TimeSpan(Global.Configuration.TimeBasedDimmingEndHour, - Global.Configuration.TimeBasedDimmingEndMinute, 0); - if (Global.Configuration.TimeBasedDimmingEnabled && - Time.IsCurrentTimeBetween(dimmingStartTime, dimmingEndTime)) - { - var blackFrame = new EffectFrame(); - Global.effengine.PushFrame(blackFrame); - StopUnUpdatedEvents(); - return; - } - - UpdateProcess(); - - var profile = GetCurrentProfile(out var preview); - _currentEvent = profile; - - // If the current foreground process is excluded from Aurora, disable the lighting manager - if (profile is Desktop.Desktop && !profile.IsEnabled) - { - if (!_profilesDisabled) - { - StopUnUpdatedEvents(); - Global.effengine.PushFrame(_drawnFrame); - _deviceManager.Result.ShutdownDevices(); - } - - _profilesDisabled = true; - return; - } - - if (_profilesDisabled) - { - _deviceManager.Result.InitializeDevices(); - _profilesDisabled = false; - } - - //Need to do another check in case Desktop is disabled or the selected preview is disabled - if (profile.IsEnabled) - UpdateEvent(profile, _drawnFrame); - - // Overlay layers - if (!preview || Global.Configuration.OverlaysInPreview) - { - if (DesktopProfile.IsOverlayEnabled) - { - DesktopProfile.UpdateOverlayLights(_drawnFrame); - } - - foreach (var @event in GetOverlayActiveProfiles()) - { - StartEvent(@event); - @event.UpdateOverlayLights(_drawnFrame); - } - - //Add the Light event that we're previewing to be rendered as an overlay (assuming it's not already active) - if (preview && Global.Configuration.OverlaysInPreview && !GetOverlayActiveProfiles().Contains(profile)) - { - StartEvent(profile); - profile.UpdateOverlayLights(_drawnFrame); - } - - if (Global.Configuration.IdleType != IdleEffects.None) - { - UpdateIdleEffects(_drawnFrame); - } - } - - Global.effengine.PushFrame(_drawnFrame); - - StopUnUpdatedEvents(); - PostUpdate?.Invoke(this, EventArgs.Empty); - } - - /// Gets the current application. - /// Boolean indicating whether the application is selected because it is previewing (true) - /// or because the process is open (false). - private Application GetCurrentProfile(out bool preview) - { - var processName = _activeProcessMonitor.Result.ProcessName.ToLower(); - var processTitle = _activeProcessMonitor.Result.ProcessTitle; - Application? profile = null; - Application? tempProfile; - preview = false; - - //TODO: GetProfile that checks based on event type - if ((tempProfile = GetProfileFromProcessData(processName, processTitle)) != null && tempProfile.IsEnabled) - profile = tempProfile; - //Don't check for it being Enabled as a preview should always end-up with the previewed profile regardless of it being disabled - else if ((tempProfile = GetProfileFromProcessName(_previewModeProfileKey)) != null) - { - profile = tempProfile; - preview = true; - } - else if (Global.Configuration.ExcludedPrograms.Contains(processName)) - { - return DesktopProfile; - } - else if (Global.Configuration.AllowWrappersInBackground - && LfxState.IsWrapperConnected - && (tempProfile = GetProfileFromProcessName(LfxState.WrappedProcess)) != null - && tempProfile.IsEnabled) - profile = tempProfile; - - profile ??= DesktopProfile; - - return profile; - } - - /// Gets the current application. - public Application GetCurrentProfile() => GetCurrentProfile(out _); - /// - /// Returns a list of all profiles that should have their overlays active. This will include processes that running but not in the foreground. - /// - /// - public IEnumerable GetOverlayActiveProfiles() - { - return Events.Values.Where(_isOverlayActiveProfile); - } - - /// KeyDown handler that checks the current application's profiles for keybinds. - /// In the case of multiple profiles matching the keybind, it will pick the next one as specified in the Application.Profile order. - private void CheckProfileKeybinds(object? sender, EventArgs e) - { - var profile = GetCurrentProfile(); - - // Check profile is valid and do not switch profiles if the user is trying to enter a keybind - if (Control_Keybind._ActiveKeybind != null) return; - - // Find all profiles that have their keybinds pressed - var currentIndex = -1; - var count = 0; - foreach (var prof in profile.Profiles) - { - if (!prof.TriggerKeybind.IsPressed()) continue; - if (currentIndex == -1 && prof == profile.Profile) - currentIndex = count; - count++; - } - - // If at least one profile has it's key pressed\ - if (count == 0) return; - // The target profile is the NEXT valid profile after the currently selected one - // (or the first valid one if the currently selected one doesn't share this keybind) - var nextIndex = (currentIndex + 1) % count; - var idx = 0; - foreach (var prof in profile.Profiles) - { - if (!prof.TriggerKeybind.IsPressed()) continue; - if (idx == nextIndex) - { - profile.SwitchToProfile(prof); - break; - } - idx++; - } - } - - public void JsonGameStateUpdate(object? sender, JsonGameStateEventArgs eventArgs) - { - var gameId = eventArgs.GameId; - var profile = GetProfileFromAppId(gameId); - if (profile == null) - { - return; - } - - var gameStateType = profile.Config.GameStateType; - var json = eventArgs.Json; - - var gameState = JsonSerializer.Deserialize(json, gameStateType, GameStateJsonSerializerOptions) as IGameState; - - profile.SetGameState(gameState); - } - - public void GameStateUpdate(object? sender, IGameState gs) - { - try - { - if (gs is not NewtonsoftGameState newtonsoftGameState) - { - return; - } - - if (!newtonsoftGameState.Announce) - { - return; - } - - var provider = JObject.Parse(newtonsoftGameState.GetNode("provider")); - var appid = provider.GetValue("appid").ToString(); - var name = provider.GetValue("name").ToString().ToLowerInvariant(); - - Application? profile; - if ((profile = GetProfileFromAppId(appid)) == null && (profile = GetProfileFromProcessName(name)) == null) - { - return; - } - - // profile supports System.Text.Json but we received data on old endpoint - if (!profile.Config.GameStateType.IsAssignableTo(typeof(NewtonsoftGameState))) - { - var njGameState = JsonConvert.DeserializeObject(newtonsoftGameState.Json, profile.Config.GameStateType, new JsonSerializerSettings() - { - ContractResolver = ContractResolver, - }) as IGameState; - profile.SetGameState(njGameState); - return; - } - - var gameState = (NewtonsoftGameState)Activator.CreateInstance(profile.Config.GameStateType, newtonsoftGameState.Json); - profile.SetGameState(gameState); - } - catch (Exception e) - { - Global.logger.Warning(e, "Exception during GameStateUpdate(), error: "); - if (Debugger.IsAttached) - { - throw; - } - } - } - - public void ResetGameState(object? sender, string process) - { - var profile = GetProfileFromProcessName(process); - profile?.ResetGameState(); - } - - public void Dispose() - { - _initializeCancelSource.Cancel(); - _initializeCancelSource.Dispose(); - _updateTimer.Dispose(200); - foreach (var (_, lightEvent) in Events) - lightEvent.Dispose(); - } - - public async Task DisposeAsync() - { - await _initializeCancelSource.CancelAsync(); - _initializeCancelSource.Dispose(); - _updateTimer.Dispose(200); - foreach (var (_, lightEvent) in Events) - lightEvent.Dispose(); - } -} - -/// -/// POCO that stores data about a type of layer. -/// -public class LayerHandlerMeta -{ - - /// Creates a new LayerHandlerMeta object from the given meta attribute and type. - public LayerHandlerMeta(Type type, LayerHandlerMetaAttribute? attribute) - { - Name = attribute?.Name ?? type.Name.CamelCaseToSpaceCase().TrimEndStr(" Layer Handler"); - Type = type; - // if the layer is in the Aurora.Settings.Layers namespace, make the IsDefault true unless otherwise specified. - // If it is in another namespace, it's probably a custom application layer and so make IsDefault false unless otherwise specified - IsDefault = attribute?.IsDefault ?? type.Namespace == "AuroraRgb.Settings.Layers"; - Order = attribute?.Order ?? 0; - } - - public string Name { get; } - public Type Type { get; } - public bool IsDefault { get; } - public int Order { get; } -} - - -/// -/// Attribute to provide additional meta data about layers for them to be registered. -/// -[AttributeUsage(AttributeTargets.Class, Inherited = false)] -public class LayerHandlerMetaAttribute : Attribute -{ - /// A different name for the layer. If not specified, will automatically take it from the layer's class name. - public string Name { get; set; } - - /// If true, this layer will be excluded from automatic registration. Default false. - public bool Exclude { get; set; } - - /// If true, this layer will be registered as a 'default' layer for all applications. Default true. - public bool IsDefault { get; set; } - - /// A number used when ordering the layer entry in the list. - /// Only to be used for layers that need to appear at the top/bottom of the list. - public int Order { get; set; } -} \ No newline at end of file diff --git a/Project-Aurora/Project-Aurora/Profiles/Minecraft/MinecraftProfile.cs b/Project-Aurora/Project-Aurora/Profiles/Minecraft/MinecraftProfile.cs index d470daf9b..cc8e106cc 100644 --- a/Project-Aurora/Project-Aurora/Profiles/Minecraft/MinecraftProfile.cs +++ b/Project-Aurora/Project-Aurora/Profiles/Minecraft/MinecraftProfile.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Drawing; +using AuroraRgb.EffectsEngine; using AuroraRgb.Profiles.Minecraft.Layers; using AuroraRgb.Settings; using AuroraRgb.Settings.Layers; diff --git a/Project-Aurora/Project-Aurora/Profiles/Payday 2/PD2Profile.cs b/Project-Aurora/Project-Aurora/Profiles/Payday 2/PD2Profile.cs index 8964ab9db..cd2799b43 100644 --- a/Project-Aurora/Project-Aurora/Profiles/Payday 2/PD2Profile.cs +++ b/Project-Aurora/Project-Aurora/Profiles/Payday 2/PD2Profile.cs @@ -1,4 +1,5 @@ using System.Drawing; +using AuroraRgb.EffectsEngine; using AuroraRgb.Profiles.Payday_2.Layers; using AuroraRgb.Settings; using AuroraRgb.Settings.Layers; diff --git a/Project-Aurora/Project-Aurora/Profiles/Slime Rancher/SlimeRancherProfile.cs b/Project-Aurora/Project-Aurora/Profiles/Slime Rancher/SlimeRancherProfile.cs index 4b42976d3..e136a313e 100644 --- a/Project-Aurora/Project-Aurora/Profiles/Slime Rancher/SlimeRancherProfile.cs +++ b/Project-Aurora/Project-Aurora/Profiles/Slime Rancher/SlimeRancherProfile.cs @@ -1,4 +1,5 @@ using System.Drawing; +using AuroraRgb.EffectsEngine; using AuroraRgb.Settings; using AuroraRgb.Settings.Layers; using AuroraRgb.Settings.Overrides.Logic; diff --git a/Project-Aurora/Project-Aurora/Profiles/StardewValley/StardewValleyProfile.cs b/Project-Aurora/Project-Aurora/Profiles/StardewValley/StardewValleyProfile.cs index c86d0459d..f6a68ea86 100644 --- a/Project-Aurora/Project-Aurora/Profiles/StardewValley/StardewValleyProfile.cs +++ b/Project-Aurora/Project-Aurora/Profiles/StardewValley/StardewValleyProfile.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Drawing; +using AuroraRgb.EffectsEngine; using AuroraRgb.EffectsEngine.Animations; using AuroraRgb.Settings; using AuroraRgb.Settings.Layers; diff --git a/Project-Aurora/Project-Aurora/Profiles/Witcher3/Control_Witcher3.xaml.cs b/Project-Aurora/Project-Aurora/Profiles/Witcher3/Control_Witcher3.xaml.cs index f440c9f12..38b50138e 100644 --- a/Project-Aurora/Project-Aurora/Profiles/Witcher3/Control_Witcher3.xaml.cs +++ b/Project-Aurora/Project-Aurora/Profiles/Witcher3/Control_Witcher3.xaml.cs @@ -81,7 +81,7 @@ private void InstallMod(string root) } catch (Exception e) { - Global.logger.Error("Error installing the Witcher 3 mod", e); + Global.logger.Error(e, "Error installing the Witcher 3 mod"); MessageBox.Show("Witcher 3 directory is not found.\r\nCould not install the mod."); } } @@ -156,7 +156,7 @@ private void UninstallMod(string root) } catch (Exception e) { - Global.logger.Error("Error uninstalling witcher 3 mod", e); + Global.logger.Error(e, "Error uninstalling witcher 3 mod"); MessageBox.Show("Witcher 3 mod uninstall failed!"); } } diff --git a/Project-Aurora/Project-Aurora/Profiles/Witcher3/Witcher3Profile.cs b/Project-Aurora/Project-Aurora/Profiles/Witcher3/Witcher3Profile.cs index e7aea6b88..17b462528 100644 --- a/Project-Aurora/Project-Aurora/Profiles/Witcher3/Witcher3Profile.cs +++ b/Project-Aurora/Project-Aurora/Profiles/Witcher3/Witcher3Profile.cs @@ -1,5 +1,6 @@ using System.Collections.ObjectModel; using System.Drawing; +using AuroraRgb.EffectsEngine; using AuroraRgb.Profiles.Witcher3.Layers; using AuroraRgb.Settings; using AuroraRgb.Settings.Layers; diff --git a/Project-Aurora/Project-Aurora/Project-Aurora.csproj b/Project-Aurora/Project-Aurora/Project-Aurora.csproj index dfcb772b2..63845fbb7 100644 --- a/Project-Aurora/Project-Aurora/Project-Aurora.csproj +++ b/Project-Aurora/Project-Aurora/Project-Aurora.csproj @@ -81,7 +81,7 @@ - + diff --git a/Project-Aurora/Project-Aurora/Resources/Ghostrunner.png b/Project-Aurora/Project-Aurora/Resources/Ghostrunner.png new file mode 100644 index 000000000..95f954623 Binary files /dev/null and b/Project-Aurora/Project-Aurora/Resources/Ghostrunner.png differ diff --git a/Project-Aurora/Project-Aurora/Resources/bo7.png b/Project-Aurora/Project-Aurora/Resources/bo7.png new file mode 100644 index 000000000..a69411de7 Binary files /dev/null and b/Project-Aurora/Project-Aurora/Resources/bo7.png differ diff --git a/Project-Aurora/Project-Aurora/Settings/Controls/Wrappers/ControlCorsairIcueWrapper.xaml b/Project-Aurora/Project-Aurora/Settings/Controls/Wrappers/ControlCorsairIcueWrapper.xaml index 12fc061c9..89dce9495 100644 --- a/Project-Aurora/Project-Aurora/Settings/Controls/Wrappers/ControlCorsairIcueWrapper.xaml +++ b/Project-Aurora/Project-Aurora/Settings/Controls/Wrappers/ControlCorsairIcueWrapper.xaml @@ -38,6 +38,7 @@