Kaynağa Gözat

Add option to change controller LED color (#572)

This allows the user to change the controller LED while using Ryujinx.
Useful for PS4 and PS5 controllers as an example.

You can also use a spectrum-cycling Rainbow color option, or turn the LED off for DualSense controllers.

---------

Co-authored-by: Evan Husted <greem@greemdev.net>
Otozinclus 1 yıl önce
ebeveyn
işleme
1ce37ec317

+ 15 - 5
src/Ryujinx.Common/Configuration/Hid/Controller/LedConfigController.cs

@@ -2,14 +2,24 @@
 {
     public class LedConfigController
     {
-        /// <summary>
-        ///     Packed RGB int of the color
-        /// </summary>
-        public uint LedColor { get; set; }
-
         /// <summary>
         /// Enable LED color changing by the emulator
         /// </summary>
         public bool EnableLed { get; set; }
+        
+        /// <summary>
+        /// Ignores the color and disables the LED entirely.
+        /// </summary>
+        public bool TurnOffLed { get; set; }
+        
+        /// <summary>
+        /// Ignores the color and uses the rainbow color functionality for the LED.
+        /// </summary>
+        public bool UseRainbow { get; set; }
+        
+        /// <summary>
+        ///     Packed RGB int of the color
+        /// </summary>
+        public uint LedColor { get; set; }
     }
 }

+ 76 - 0
src/Ryujinx.Common/Utilities/Rainbow.cs

@@ -0,0 +1,76 @@
+using System;
+using System.Drawing;
+
+namespace Ryujinx.Common.Utilities
+{
+    public class Rainbow
+    {
+        public const float Speed = 1;
+        
+        public static Color Color { get; private set; } = Color.Blue;
+
+        public static void Tick()
+        {
+            Color = HsbToRgb(
+                (Color.GetHue() + Speed) / 360,
+                1, 
+                1
+                );
+            
+            RainbowColorUpdated?.Invoke(Color.ToArgb());
+        }
+
+        public static event Action<int> RainbowColorUpdated;
+
+        private static Color HsbToRgb(float hue, float saturation, float brightness)
+        {
+            int r = 0, g = 0, b = 0;
+            if (saturation == 0)
+            {
+                r = g = b = (int)(brightness * 255.0f + 0.5f);
+            }
+            else
+            {
+                float h = (hue - (float)Math.Floor(hue)) * 6.0f;
+                float f = h - (float)Math.Floor(h);
+                float p = brightness * (1.0f - saturation);
+                float q = brightness * (1.0f - saturation * f);
+                float t = brightness * (1.0f - (saturation * (1.0f - f)));
+                switch ((int)h)
+                {
+                    case 0:
+                        r = (int)(brightness * 255.0f + 0.5f);
+                        g = (int)(t * 255.0f + 0.5f);
+                        b = (int)(p * 255.0f + 0.5f);
+                        break;
+                    case 1:
+                        r = (int)(q * 255.0f + 0.5f);
+                        g = (int)(brightness * 255.0f + 0.5f);
+                        b = (int)(p * 255.0f + 0.5f);
+                        break;
+                    case 2:
+                        r = (int)(p * 255.0f + 0.5f);
+                        g = (int)(brightness * 255.0f + 0.5f);
+                        b = (int)(t * 255.0f + 0.5f);
+                        break;
+                    case 3:
+                        r = (int)(p * 255.0f + 0.5f);
+                        g = (int)(q * 255.0f + 0.5f);
+                        b = (int)(brightness * 255.0f + 0.5f);
+                        break;
+                    case 4:
+                        r = (int)(t * 255.0f + 0.5f);
+                        g = (int)(p * 255.0f + 0.5f);
+                        b = (int)(brightness * 255.0f + 0.5f);
+                        break;
+                    case 5:
+                        r = (int)(brightness * 255.0f + 0.5f);
+                        g = (int)(p * 255.0f + 0.5f);
+                        b = (int)(q * 255.0f + 0.5f);
+                        break;
+                }
+            }
+            return Color.FromArgb(Convert.ToByte(255), Convert.ToByte(r), Convert.ToByte(g), Convert.ToByte(b));
+        }
+    }
+}

+ 27 - 4
src/Ryujinx.Input.SDL2/SDL2Gamepad.cs

@@ -1,6 +1,8 @@
 using Ryujinx.Common.Configuration.Hid;
 using Ryujinx.Common.Configuration.Hid.Controller;
 using Ryujinx.Common.Logging;
+using Ryujinx.Common.Utilities;
+using Ryujinx.HLE.HOS.Services.Hid;
 using SDL2;
 using System;
 using System.Collections.Generic;
@@ -86,7 +88,7 @@ namespace Ryujinx.Input.SDL2
             Id = driverId;
             Features = GetFeaturesFlag();
             _triggerThreshold = 0.0f;
-
+            
             // Enable motion tracking
             if (Features.HasFlag(GamepadFeaturesFlag.Motion))
             {
@@ -102,6 +104,18 @@ namespace Ryujinx.Input.SDL2
             }
         }
 
+        public void SetLed(uint packedRgb)
+        {
+            if (!Features.HasFlag(GamepadFeaturesFlag.Led)) return;
+
+            byte red = packedRgb > 0 ? (byte)(packedRgb >> 16) : (byte)0;
+            byte green = packedRgb > 0 ? (byte)(packedRgb >> 8) : (byte)0;
+            byte blue = packedRgb > 0 ? (byte)(packedRgb % 256) : (byte)0;
+            
+            if (SDL_GameControllerSetLED(_gamepadHandle, red, green, blue) != 0)
+                Logger.Error?.Print(LogClass.Hid, "LED is not supported on this game controller.");
+        }
+
         private GamepadFeaturesFlag GetFeaturesFlag()
         {
             GamepadFeaturesFlag result = GamepadFeaturesFlag.None;
@@ -112,9 +126,7 @@ namespace Ryujinx.Input.SDL2
                 result |= GamepadFeaturesFlag.Motion;
             }
 
-            int error = SDL_GameControllerRumble(_gamepadHandle, 0, 0, 100);
-
-            if (error == 0)
+            if (SDL_GameControllerHasRumble(_gamepadHandle) == SDL_bool.SDL_TRUE)
             {
                 result |= GamepadFeaturesFlag.Rumble;
             }
@@ -220,6 +232,17 @@ namespace Ryujinx.Input.SDL2
             {
                 _configuration = (StandardControllerInputConfig)configuration;
 
+                if (Features.HasFlag(GamepadFeaturesFlag.Led) && _configuration.Led.EnableLed)
+                {
+                    if (_configuration.Led.TurnOffLed)
+                        (this as IGamepad).ClearLed();
+                    else if (_configuration.Led.UseRainbow)
+                        Rainbow.RainbowColorUpdated += clr => SetLed((uint)clr);
+                    else
+                        SetLed(_configuration.Led.LedColor);
+                    
+                }
+                
                 _buttonsUserMapping.Clear();
 
                 // First update sticks

+ 11 - 0
src/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs

@@ -173,5 +173,16 @@ namespace Ryujinx.Input.SDL2
 
             return new SDL2Gamepad(gamepadHandle, id);
         }
+
+        public IEnumerable<IGamepad> GetGamepads()
+        {
+            lock (_gamepadsIds)
+            {
+                foreach (string gamepadId in _gamepadsIds)
+                {
+                    yield return GetGamepad(gamepadId);
+                }
+            }
+        }
     }
 }

+ 6 - 0
src/Ryujinx.Input.SDL2/SDL2Keyboard.cs

@@ -1,5 +1,6 @@
 using Ryujinx.Common.Configuration.Hid;
 using Ryujinx.Common.Configuration.Hid.Keyboard;
+using Ryujinx.Common.Logging;
 using System;
 using System.Collections.Generic;
 using System.Numerics;
@@ -385,6 +386,11 @@ namespace Ryujinx.Input.SDL2
             }
         }
 
+        public void SetLed(uint packedRgb)
+        {
+            Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL2Keyboard");
+        }
+
         public void SetTriggerThreshold(float triggerThreshold)
         {
             // No operations

+ 6 - 0
src/Ryujinx.Input.SDL2/SDL2Mouse.cs

@@ -1,4 +1,5 @@
 using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Logging;
 using System;
 using System.Drawing;
 using System.Numerics;
@@ -76,6 +77,11 @@ namespace Ryujinx.Input.SDL2
             throw new NotImplementedException();
         }
 
+        public void SetLed(uint packedRgb)
+        {
+            Logger.Info?.Print(LogClass.UI, "SetLed called on an SDL2Mouse");
+        }
+
         public void SetTriggerThreshold(float triggerThreshold)
         {
             throw new NotImplementedException();

+ 3 - 0
src/Ryujinx.Input.SDL2/SDL2MouseDriver.cs

@@ -1,6 +1,7 @@
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
 using System;
+using System.Collections.Generic;
 using System.Diagnostics;
 using System.Drawing;
 using System.Numerics;
@@ -164,6 +165,8 @@ namespace Ryujinx.Input.SDL2
             return new SDL2Mouse(this);
         }
 
+        public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
+
         public void Dispose()
         {
             if (_isDisposed)

+ 9 - 0
src/Ryujinx.Input.SDL2/SDLKeyboardDriver.cs

@@ -1,5 +1,6 @@
 using Ryujinx.SDL2.Common;
 using System;
+using System.Collections.Generic;
 
 namespace Ryujinx.Input.SDL2
 {
@@ -51,5 +52,13 @@ namespace Ryujinx.Input.SDL2
 
             return new SDL2Keyboard(this, _keyboardIdentifers[0], "All keyboards");
         }
+
+        public IEnumerable<IGamepad> GetGamepads()
+        {
+            foreach (var keyboardId in _keyboardIdentifers)
+            {
+                yield return GetGamepad(keyboardId);
+            }
+        }
     }
 }

+ 9 - 0
src/Ryujinx.Input/IGamepad.cs

@@ -65,6 +65,15 @@ namespace Ryujinx.Input
         /// <param name="configuration">The configuration of the gamepad</param>
         void SetConfiguration(InputConfig configuration);
 
+        /// <summary>
+        /// Set the LED on the gamepad to a given color.
+        /// </summary>
+        /// <remarks>Does nothing on a controller without LED functionality.</remarks>
+        /// <param name="packedRgb">The packed RGB integer.</param>
+        void SetLed(uint packedRgb);
+
+        public void ClearLed() => SetLed(0);
+
         /// <summary>
         /// Starts a rumble effect on the gamepad.
         /// </summary>

+ 6 - 0
src/Ryujinx.Input/IGamepadDriver.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 
 namespace Ryujinx.Input
 {
@@ -33,6 +34,11 @@ namespace Ryujinx.Input
         /// <param name="id">The unique id of the gamepad</param>
         /// <returns>An instance of <see cref="IGamepad"/> associated to the gamepad id given or null if not found</returns>
         IGamepad GetGamepad(string id);
+        
+        /// <summary>
+        /// Returns an <see cref="IEnumerable{T}"/> of the connected gamepads.
+        /// </summary>
+        IEnumerable<IGamepad> GetGamepads();
 
         /// <summary>
         /// Clear the internal state of the driver.

+ 3 - 0
src/Ryujinx.SDL2.Common/SDL2Driver.cs

@@ -1,5 +1,6 @@
 using Ryujinx.Common.Configuration;
 using Ryujinx.Common.Logging;
+using Ryujinx.Common.Utilities;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -167,6 +168,8 @@ namespace Ryujinx.SDL2.Common
                         HandleSDLEvent(ref evnt);
                     }
                 });
+                
+                Rainbow.Tick();
 
                 waitHandle.Wait(WaitTimeMs);
             }

+ 5 - 0
src/Ryujinx/AppHost.cs

@@ -587,6 +587,11 @@ namespace Ryujinx.Ava
                 return;
             }
 
+            foreach (IGamepad gamepad in RyujinxApp.MainWindow.InputManager.GamepadDriver.GetGamepads())
+            {
+                gamepad?.ClearLed();
+            }
+
             _isStopped = true;
             Stop();
         }

+ 51 - 1
src/Ryujinx/Assets/locales.json

@@ -7628,7 +7628,57 @@
         "ar_SA": "",
         "de_DE": "",
         "el_GR": "",
-        "en_US": "Custom LED",
+        "en_US": "LED",
+        "es_ES": "",
+        "fr_FR": "",
+        "he_IL": "",
+        "it_IT": "",
+        "ja_JP": "",
+        "ko_KR": "",
+        "no_NO": "",
+        "pl_PL": "",
+        "pt_BR": "",
+        "ru_RU": "",
+        "sv_SE": "",
+        "th_TH": "",
+        "tr_TR": "",
+        "uk_UA": "",
+        "zh_CN": "",
+        "zh_TW": ""
+      }
+    },
+    {
+      "ID": "ControllerSettingsLedColorDisable",
+      "Translations": {
+        "ar_SA": "",
+        "de_DE": "",
+        "el_GR": "",
+        "en_US": "Disable",
+        "es_ES": "",
+        "fr_FR": "",
+        "he_IL": "",
+        "it_IT": "",
+        "ja_JP": "",
+        "ko_KR": "",
+        "no_NO": "",
+        "pl_PL": "",
+        "pt_BR": "",
+        "ru_RU": "",
+        "sv_SE": "",
+        "th_TH": "",
+        "tr_TR": "",
+        "uk_UA": "",
+        "zh_CN": "",
+        "zh_TW": ""
+      }
+    },
+    {
+      "ID": "ControllerSettingsLedColorRainbow",
+      "Translations": {
+        "ar_SA": "",
+        "de_DE": "",
+        "el_GR": "",
+        "en_US": "Rainbow",
         "es_ES": "",
         "fr_FR": "",
         "he_IL": "",

+ 6 - 0
src/Ryujinx/Input/AvaloniaKeyboard.cs

@@ -1,5 +1,6 @@
 using Ryujinx.Common.Configuration.Hid;
 using Ryujinx.Common.Configuration.Hid.Keyboard;
+using Ryujinx.Common.Logging;
 using Ryujinx.Input;
 using System;
 using System.Collections.Generic;
@@ -143,6 +144,11 @@ namespace Ryujinx.Ava.Input
             }
         }
 
+        public void SetLed(uint packedRgb)
+        {
+            Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaKeyboard");
+        }
+
         public void SetTriggerThreshold(float triggerThreshold) { }
 
         public void Rumble(float lowFrequency, float highFrequency, uint durationMs) { }

+ 2 - 0
src/Ryujinx/Input/AvaloniaKeyboardDriver.cs

@@ -59,6 +59,8 @@ namespace Ryujinx.Ava.Input
             return new AvaloniaKeyboard(this, _keyboardIdentifers[0], LocaleManager.Instance[LocaleKeys.AllKeyboards]);
         }
 
+        public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
+
         protected virtual void Dispose(bool disposing)
         {
             if (disposing)

+ 6 - 0
src/Ryujinx/Input/AvaloniaMouse.cs

@@ -1,4 +1,5 @@
 using Ryujinx.Common.Configuration.Hid;
+using Ryujinx.Common.Logging;
 using Ryujinx.Input;
 using System;
 using System.Drawing;
@@ -74,6 +75,11 @@ namespace Ryujinx.Ava.Input
             throw new NotImplementedException();
         }
 
+        public void SetLed(uint packedRgb)
+        {
+            Logger.Info?.Print(LogClass.UI, "SetLed called on an AvaloniaMouse");
+        }
+
         public void SetTriggerThreshold(float triggerThreshold)
         {
             throw new NotImplementedException();

+ 3 - 0
src/Ryujinx/Input/AvaloniaMouseDriver.cs

@@ -3,6 +3,7 @@ using Avalonia.Controls;
 using Avalonia.Input;
 using Ryujinx.Input;
 using System;
+using System.Collections.Generic;
 using System.Numerics;
 using MouseButton = Ryujinx.Input.MouseButton;
 using Size = System.Drawing.Size;
@@ -134,6 +135,8 @@ namespace Ryujinx.Ava.Input
             return new AvaloniaMouse(this);
         }
 
+        public IEnumerable<IGamepad> GetGamepads() => [GetGamepad("0")];
+
         public void Dispose()
         {
             if (_isDisposed)

+ 46 - 14
src/Ryujinx/UI/Models/Input/GamepadInputConfig.cs

@@ -388,6 +388,28 @@ namespace Ryujinx.Ava.UI.Models.Input
             }
         }
 
+        private bool _enableMotion;
+        public bool EnableMotion
+        {
+            get => _enableMotion;
+            set
+            {
+                _enableMotion = value;
+                OnPropertyChanged();
+            }
+        }
+
+        private bool _enableRumble;
+        public bool EnableRumble
+        {
+            get => _enableRumble;
+            set
+            {
+                _enableRumble = value;
+                OnPropertyChanged();
+            }
+        }
+        
         private bool _enableLedChanging;
 
         public bool EnableLedChanging
@@ -400,36 +422,42 @@ namespace Ryujinx.Ava.UI.Models.Input
             }
         }
         
-        private Color _ledColor;
-
-        public Color LedColor
+        public bool ShowLedColorPicker => !TurnOffLed && !UseRainbowLed;
+        
+        private bool _turnOffLed;
+        
+        public bool TurnOffLed
         {
-            get => _ledColor;
+            get => _turnOffLed;
             set
             {
-                _ledColor = value;
+                _turnOffLed = value;
                 OnPropertyChanged();
+                OnPropertyChanged(nameof(ShowLedColorPicker));
             }
         }
-
-        private bool _enableMotion;
-        public bool EnableMotion
+        
+        private bool _useRainbowLed;
+        
+        public bool UseRainbowLed
         {
-            get => _enableMotion;
+            get => _useRainbowLed;
             set
             {
-                _enableMotion = value;
+                _useRainbowLed = value;
                 OnPropertyChanged();
+                OnPropertyChanged(nameof(ShowLedColorPicker));
             }
         }
+        
+        private Color _ledColor;
 
-        private bool _enableRumble;
-        public bool EnableRumble
+        public Color LedColor
         {
-            get => _enableRumble;
+            get => _ledColor;
             set
             {
-                _enableRumble = value;
+                _ledColor = value;
                 OnPropertyChanged();
             }
         }
@@ -512,6 +540,8 @@ namespace Ryujinx.Ava.UI.Models.Input
                 if (controllerInput.Led != null)
                 {
                     EnableLedChanging = controllerInput.Led.EnableLed;
+                    TurnOffLed = controllerInput.Led.TurnOffLed;
+                    UseRainbowLed = controllerInput.Led.UseRainbow;
                     uint rawColor = controllerInput.Led.LedColor;
                     byte alpha = (byte)(rawColor >> 24);
                     byte red = (byte)(rawColor >> 16);
@@ -579,6 +609,8 @@ namespace Ryujinx.Ava.UI.Models.Input
                 Led = new LedConfigController
                 {
                     EnableLed = EnableLedChanging,
+                    TurnOffLed = this.TurnOffLed,
+                    UseRainbow = UseRainbowLed,
                     LedColor = LedColor.ToUInt32()
                 },
                 Version = InputConfig.CurrentVersion,

+ 13 - 0
src/Ryujinx/UI/ViewModels/Input/ControllerInputViewModel.cs

@@ -1,5 +1,8 @@
 using Avalonia.Svg.Skia;
 using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using FluentAvalonia.UI.Controls;
+using Ryujinx.Ava.UI.Helpers;
 using Ryujinx.Ava.UI.Models.Input;
 using Ryujinx.Ava.UI.Views.Input;
 
@@ -57,6 +60,16 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
             await RumbleInputView.Show(this);
         }
 
+        public RelayCommand LedDisabledChanged => Commands.Create(() =>
+        {
+            if (!Config.EnableLedChanging) return;
+
+            if (Config.TurnOffLed)
+                ParentModel.SelectedGamepad.ClearLed();
+            else
+                ParentModel.SelectedGamepad.SetLed(Config.LedColor.ToUInt32());
+        });
+
         public void OnParentModelChanged()
         {
             IsLeft = ParentModel.IsLeft;

+ 15 - 3
src/Ryujinx/UI/ViewModels/Input/InputViewModel.cs

@@ -3,6 +3,7 @@ using Avalonia.Controls;
 using Avalonia.Svg.Skia;
 using Avalonia.Threading;
 using CommunityToolkit.Mvvm.ComponentModel;
+using Gommon;
 using Ryujinx.Ava.Common.Locale;
 using Ryujinx.Ava.Input;
 using Ryujinx.Ava.UI.Helpers;
@@ -54,7 +55,18 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
         private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions());
 
         public IGamepadDriver AvaloniaKeyboardDriver { get; }
-        public IGamepad SelectedGamepad { get; private set; }
+
+        private IGamepad _selectedGamepad;
+
+        public IGamepad SelectedGamepad
+        {
+            get => _selectedGamepad;
+            private set
+            {
+                _selectedGamepad = value;
+                OnPropertiesChanged(nameof(HasLed), nameof(CanClearLed));
+            }
+        }
 
         public ObservableCollection<PlayerModel> PlayerIndexes { get; set; }
         public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; }
@@ -69,8 +81,8 @@ namespace Ryujinx.Ava.UI.ViewModels.Input
         public bool IsRight { get; set; }
         public bool IsLeft { get; set; }
 
-        public bool HasLed => false; //temporary
-            //SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
+        public bool HasLed => SelectedGamepad.Features.HasFlag(GamepadFeaturesFlag.Led);
+        public bool CanClearLed => SelectedGamepad.Name.ContainsIgnoreCase("DualSense");
 
         public bool IsModified { get; set; }
         public event Action NotifyChangesEvent;

+ 26 - 4
src/Ryujinx/UI/Views/Input/ControllerInputView.axaml

@@ -429,7 +429,7 @@
                         </StackPanel>
                     </StackPanel>
                 </Border>
-                <!-- Motion + Rumble -->
+                <!-- Motion, Rumble, LED -->
                 <StackPanel
                     Margin="0,10,0,0"
                     Spacing="5"
@@ -495,25 +495,47 @@
                         Margin="0,-1,0,0">
                         <Grid IsVisible="{Binding ParentModel.HasLed}">
                             <Grid.ColumnDefinitions>
+                                <ColumnDefinition Width="Auto" />
+                                <ColumnDefinition Width="*" />
                                 <ColumnDefinition Width="*" />
                                 <ColumnDefinition Width="Auto" />
                             </Grid.ColumnDefinitions>
                             <CheckBox
-                                Margin="10"
+                                Margin="10, 10, 5, 10"
                                 MinWidth="0"
                                 Grid.Column="0"
                                 IsChecked="{Binding Config.EnableLedChanging, Mode=TwoWay}">
                                 <TextBlock Text="{ext:Locale ControllerSettingsLedColor}" />
                             </CheckBox>
-                            <ui:ColorPickerButton 
+                            <CheckBox
+                                Margin="5, 10, 5, 10"
+                                MinWidth="0"
                                 Grid.Column="1"
-                                Margin="10"
+                                IsVisible="{Binding ParentModel.CanClearLed}"
+                                IsChecked="{Binding Config.TurnOffLed, Mode=TwoWay}"
+                                Command="{Binding LedDisabledChanged}">
+                                <TextBlock Text="{ext:Locale ControllerSettingsLedColorDisable}" />
+                            </CheckBox>
+                            <CheckBox
+                                Margin="5, 10 5,10"
+                                MinWidth="0"
+                                Grid.Column="2"
+                                IsEnabled="{Binding !Config.TurnOffLed}"
+                                IsChecked="{Binding Config.UseRainbowLed, Mode=TwoWay}">
+                                <TextBlock Text="{ext:Locale ControllerSettingsLedColorRainbow}" />
+                            </CheckBox>
+                            <ui:ColorPickerButton 
+                                Grid.Column="3"
+                                IsEnabled="{Binding Config.ShowLedColorPicker}"
+                                Margin="5, 10, 10, 10"
                                 IsMoreButtonVisible="False"
                                 UseColorPalette="False"
                                 UseColorTriangle="False"
                                 UseColorWheel="False"
                                 ShowAcceptDismissButtons="False"
                                 IsAlphaEnabled="False"
+                                AttachedToVisualTree="ColorPickerButton_OnAttachedToVisualTree"
+                                ColorChanged="ColorPickerButton_OnColorChanged"
                                 Color="{Binding Config.LedColor, Mode=TwoWay}">
                             </ui:ColorPickerButton>
                         </Grid>

+ 30 - 2
src/Ryujinx/UI/Views/Input/ControllerInputView.axaml.cs

@@ -4,11 +4,14 @@ using Avalonia.Controls.Primitives;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.LogicalTree;
+using FluentAvalonia.UI.Controls;
 using Ryujinx.Ava.UI.Helpers;
+using Ryujinx.Ava.UI.Models;
 using Ryujinx.Ava.UI.ViewModels.Input;
 using Ryujinx.Common.Configuration.Hid.Controller;
 using Ryujinx.Input;
 using Ryujinx.Input.Assigner;
+using System.Linq;
 using StickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId;
 
 namespace Ryujinx.Ava.UI.Views.Input
@@ -82,7 +85,7 @@ namespace Ryujinx.Ava.UI.Views.Input
 
         private void Button_IsCheckedChanged(object sender, RoutedEventArgs e)
         {
-            if (sender is ToggleButton button) 
+            if (sender is ToggleButton button)
             {
                 if (button.IsChecked is true)
                 {
@@ -103,7 +106,9 @@ namespace Ryujinx.Ava.UI.Views.Input
 
                         var viewModel = (DataContext as ControllerInputViewModel);
 
-                        IKeyboard keyboard = (IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver.GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
+                        IKeyboard keyboard =
+                            (IKeyboard)viewModel.ParentModel.AvaloniaKeyboardDriver
+                                .GetGamepad("0"); // Open Avalonia keyboard for cancel operations.
                         IButtonAssigner assigner = CreateButtonAssigner(isStick);
 
                         _currentAssigner.ButtonAssigned += (sender, e) =>
@@ -231,8 +236,31 @@ namespace Ryujinx.Ava.UI.Views.Input
         protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
         {
             base.OnDetachedFromVisualTree(e);
+            
+            foreach (IGamepad gamepad in RyujinxApp.MainWindow.InputManager.GamepadDriver.GetGamepads())
+            {
+                gamepad?.ClearLed();
+            }
+            
             _currentAssigner?.Cancel();
             _currentAssigner = null;
         }
+
+        private void ColorPickerButton_OnColorChanged(ColorPickerButton sender, ColorButtonColorChangedEventArgs args)
+        {
+            if (!args.NewColor.HasValue) return;
+            if (DataContext is not ControllerInputViewModel cVm) return;
+            if (!cVm.Config.EnableLedChanging) return;
+
+            cVm.ParentModel.SelectedGamepad.SetLed(args.NewColor.Value.ToUInt32());
+        }
+
+        private void ColorPickerButton_OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs e)
+        {
+            if (DataContext is not ControllerInputViewModel cVm) return;
+            if (!cVm.Config.EnableLedChanging) return;
+
+            cVm.ParentModel.SelectedGamepad.SetLed(cVm.Config.LedColor.ToUInt32());
+        }
     }
 }

+ 11 - 0
src/Ryujinx/UI/Windows/SettingsWindow.axaml.cs

@@ -4,9 +4,14 @@ using Avalonia.Input;
 using FluentAvalonia.Core;
 using FluentAvalonia.UI.Controls;
 using Ryujinx.Ava.Common.Locale;
+using Ryujinx.Ava.UI.Models;
 using Ryujinx.Ava.UI.ViewModels;
+using Ryujinx.Ava.UI.ViewModels.Input;
 using Ryujinx.HLE.FileSystem;
+using Ryujinx.Input;
 using System;
+using System.Linq;
+using Key = Avalonia.Input.Key;
 
 namespace Ryujinx.Ava.UI.Windows
 {
@@ -106,6 +111,12 @@ namespace Ryujinx.Ava.UI.Windows
         protected override void OnClosing(WindowClosingEventArgs e)
         {
             HotkeysPage.Dispose();
+            
+            foreach (IGamepad gamepad in RyujinxApp.MainWindow.InputManager.GamepadDriver.GetGamepads())
+            {
+                gamepad?.ClearLed();
+            }
+            
             InputPage.Dispose();
             base.OnClosing(e);
         }

+ 4 - 1
src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs

@@ -1,3 +1,4 @@
+using Avalonia.Media;
 using Gommon;
 using Ryujinx.Ava.Utilities.Configuration.System;
 using Ryujinx.Ava.Utilities.Configuration.UI;
@@ -421,7 +422,9 @@ namespace Ryujinx.Ava.Utilities.Configuration
                         config.Led = new LedConfigController
                         {
                             EnableLed = false,
-                            LedColor = 328189
+                            TurnOffLed = false,
+                            UseRainbow = false,
+                            LedColor = new Color(255, 5, 1, 253).ToUInt32()
                         };
                     }
                 })