using Gtk; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Configuration.Hid.Keyboard; using Ryujinx.Common.Logging; using Ryujinx.Common.Utilities; using Ryujinx.Input; using Ryujinx.Input.Assigner; using Ryujinx.Input.GTK3; using Ryujinx.UI.Common.Configuration; using Ryujinx.UI.Widgets; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Text.Json; using System.Threading; using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; using GUI = Gtk.Builder.ObjectAttribute; using Key = Ryujinx.Common.Configuration.Hid.Key; namespace Ryujinx.UI.Windows { public class ControllerWindow : Window { private readonly PlayerIndex _playerIndex; private readonly InputConfig _inputConfig; private bool _isWaitingForInput; #pragma warning disable CS0649, IDE0044 // Field is never assigned to, Add readonly modifier [GUI] Adjustment _controllerStrongRumble; [GUI] Adjustment _controllerWeakRumble; [GUI] Adjustment _controllerDeadzoneLeft; [GUI] Adjustment _controllerDeadzoneRight; [GUI] Adjustment _controllerRangeLeft; [GUI] Adjustment _controllerRangeRight; [GUI] Adjustment _controllerTriggerThreshold; [GUI] Adjustment _slotNumber; [GUI] Adjustment _altSlotNumber; [GUI] Adjustment _sensitivity; [GUI] Adjustment _gyroDeadzone; [GUI] CheckButton _enableMotion; [GUI] CheckButton _enableCemuHook; [GUI] CheckButton _mirrorInput; [GUI] Entry _dsuServerHost; [GUI] Entry _dsuServerPort; [GUI] ComboBoxText _inputDevice; [GUI] ComboBoxText _profile; [GUI] Box _settingsBox; [GUI] Box _motionAltBox; [GUI] Box _motionBox; [GUI] Box _dsuServerHostBox; [GUI] Box _dsuServerPortBox; [GUI] Box _motionControllerSlot; [GUI] Grid _leftStickKeyboard; [GUI] Grid _leftStickController; [GUI] Box _deadZoneLeftBox; [GUI] Box _rangeLeftBox; [GUI] Grid _rightStickKeyboard; [GUI] Grid _rightStickController; [GUI] Box _deadZoneRightBox; [GUI] Box _rangeRightBox; [GUI] Grid _leftSideTriggerBox; [GUI] Grid _rightSideTriggerBox; [GUI] Box _triggerThresholdBox; [GUI] ComboBoxText _controllerType; [GUI] ToggleButton _lStick; [GUI] CheckButton _invertLStickX; [GUI] CheckButton _invertLStickY; [GUI] CheckButton _rotateL90CW; [GUI] ToggleButton _lStickUp; [GUI] ToggleButton _lStickDown; [GUI] ToggleButton _lStickLeft; [GUI] ToggleButton _lStickRight; [GUI] ToggleButton _lStickButton; [GUI] ToggleButton _dpadUp; [GUI] ToggleButton _dpadDown; [GUI] ToggleButton _dpadLeft; [GUI] ToggleButton _dpadRight; [GUI] ToggleButton _minus; [GUI] ToggleButton _l; [GUI] ToggleButton _zL; [GUI] ToggleButton _rStick; [GUI] CheckButton _invertRStickX; [GUI] CheckButton _invertRStickY; [GUI] CheckButton _rotateR90CW; [GUI] ToggleButton _rStickUp; [GUI] ToggleButton _rStickDown; [GUI] ToggleButton _rStickLeft; [GUI] ToggleButton _rStickRight; [GUI] ToggleButton _rStickButton; [GUI] ToggleButton _a; [GUI] ToggleButton _b; [GUI] ToggleButton _x; [GUI] ToggleButton _y; [GUI] ToggleButton _plus; [GUI] ToggleButton _r; [GUI] ToggleButton _zR; [GUI] ToggleButton _lSl; [GUI] ToggleButton _lSr; [GUI] ToggleButton _rSl; [GUI] ToggleButton _rSr; [GUI] Image _controllerImage; [GUI] CheckButton _enableRumble; [GUI] Box _rumbleBox; #pragma warning restore CS0649, IDE0044 private readonly MainWindow _mainWindow; private readonly IGamepadDriver _gtk3KeyboardDriver; private IGamepad _selectedGamepad; private bool _mousePressed; private bool _middleMousePressed; private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); public ControllerWindow(MainWindow mainWindow, PlayerIndex controllerId) : this(mainWindow, new Builder("Ryujinx.Gtk3.UI.Windows.ControllerWindow.glade"), controllerId) { } private ControllerWindow(MainWindow mainWindow, Builder builder, PlayerIndex controllerId) : base(builder.GetRawOwnedObject("_controllerWin")) { _mainWindow = mainWindow; _selectedGamepad = null; // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... _gtk3KeyboardDriver = new GTK3KeyboardDriver(this); Icon = new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Logo_Ryujinx.png"); builder.Autoconnect(this); _playerIndex = controllerId; _inputConfig = ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerIndex); Title = $"Ryujinx - Controller Settings - {_playerIndex}"; if (_playerIndex == PlayerIndex.Handheld) { _controllerType.Append(ControllerType.Handheld.ToString(), "Handheld"); _controllerType.Sensitive = false; } else { _controllerType.Append(ControllerType.ProController.ToString(), "Pro Controller"); _controllerType.Append(ControllerType.JoyconPair.ToString(), "Joycon Pair"); _controllerType.Append(ControllerType.JoyconLeft.ToString(), "Joycon Left"); _controllerType.Append(ControllerType.JoyconRight.ToString(), "Joycon Right"); } _controllerType.Active = 0; // Set initial value to first in list. // Bind Events. _lStick.Clicked += ButtonForStick_Pressed; _lStickUp.Clicked += Button_Pressed; _lStickDown.Clicked += Button_Pressed; _lStickLeft.Clicked += Button_Pressed; _lStickRight.Clicked += Button_Pressed; _lStickButton.Clicked += Button_Pressed; _dpadUp.Clicked += Button_Pressed; _dpadDown.Clicked += Button_Pressed; _dpadLeft.Clicked += Button_Pressed; _dpadRight.Clicked += Button_Pressed; _minus.Clicked += Button_Pressed; _l.Clicked += Button_Pressed; _zL.Clicked += Button_Pressed; _lSl.Clicked += Button_Pressed; _lSr.Clicked += Button_Pressed; _rStick.Clicked += ButtonForStick_Pressed; _rStickUp.Clicked += Button_Pressed; _rStickDown.Clicked += Button_Pressed; _rStickLeft.Clicked += Button_Pressed; _rStickRight.Clicked += Button_Pressed; _rStickButton.Clicked += Button_Pressed; _a.Clicked += Button_Pressed; _b.Clicked += Button_Pressed; _x.Clicked += Button_Pressed; _y.Clicked += Button_Pressed; _plus.Clicked += Button_Pressed; _r.Clicked += Button_Pressed; _zR.Clicked += Button_Pressed; _rSl.Clicked += Button_Pressed; _rSr.Clicked += Button_Pressed; _enableCemuHook.Clicked += CemuHookCheckButtonPressed; // Setup current values. UpdateInputDeviceList(); SetAvailableOptions(); ClearValues(); if (_inputDevice.ActiveId != null) { SetCurrentValues(); } mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; _mainWindow.RendererWidget?.NpadManager.BlockInputUpdates(); } private void CemuHookCheckButtonPressed(object sender, EventArgs e) { UpdateCemuHookSpecificFieldsVisibility(); } private void HandleOnGamepadDisconnected(string id) { Application.Invoke(delegate { UpdateInputDeviceList(); }); } private void HandleOnGamepadConnected(string id) { Application.Invoke(delegate { UpdateInputDeviceList(); }); } protected override void OnDestroyed() { _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; _mainWindow.RendererWidget?.NpadManager.UnblockInputUpdates(); _selectedGamepad?.Dispose(); _gtk3KeyboardDriver.Dispose(); } private static string GetShortGamepadName(string str) { const string ShrinkChars = "..."; const int MaxSize = 50; if (str.Length > MaxSize) { return $"{str.AsSpan(0, MaxSize - ShrinkChars.Length)}{ShrinkChars}"; } return str; } private void UpdateInputDeviceList() { _inputDevice.RemoveAll(); _inputDevice.Append("disabled", "Disabled"); _inputDevice.SetActiveId("disabled"); foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds) { IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); if (gamepad != null) { _inputDevice.Append($"keyboard/{id}", GetShortGamepadName($"{gamepad.Name} ({id})")); gamepad.Dispose(); } } foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds) { IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); if (gamepad != null) { _inputDevice.Append($"controller/{id}", GetShortGamepadName($"{gamepad.Name} ({id})")); gamepad.Dispose(); } } switch (_inputConfig) { case StandardKeyboardInputConfig keyboard: _inputDevice.SetActiveId($"keyboard/{keyboard.Id}"); break; case StandardControllerInputConfig controller: _inputDevice.SetActiveId($"controller/{controller.Id}"); break; } } private void UpdateCemuHookSpecificFieldsVisibility() { if (_enableCemuHook.Active) { _dsuServerHostBox.Show(); _dsuServerPortBox.Show(); _motionControllerSlot.Show(); _motionAltBox.Show(); _mirrorInput.Show(); } else { _dsuServerHostBox.Hide(); _dsuServerPortBox.Hide(); _motionControllerSlot.Hide(); _motionAltBox.Hide(); _mirrorInput.Hide(); } } private void SetAvailableOptions() { if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("keyboard")) { ShowAll(); _leftStickController.Hide(); _rightStickController.Hide(); _deadZoneLeftBox.Hide(); _deadZoneRightBox.Hide(); _rangeLeftBox.Hide(); _rangeRightBox.Hide(); _triggerThresholdBox.Hide(); _motionBox.Hide(); _rumbleBox.Hide(); } else if (_inputDevice.ActiveId != null && _inputDevice.ActiveId.StartsWith("controller")) { ShowAll(); _leftStickKeyboard.Hide(); _rightStickKeyboard.Hide(); UpdateCemuHookSpecificFieldsVisibility(); } else { _settingsBox.Hide(); } ClearValues(); } private void SetCurrentValues() { SetControllerSpecificFields(); SetProfiles(); if (_inputDevice.ActiveId.StartsWith("keyboard") && _inputConfig is StandardKeyboardInputConfig) { SetValues(_inputConfig); } else if (_inputDevice.ActiveId.StartsWith("controller") && _inputConfig is StandardControllerInputConfig) { SetValues(_inputConfig); } } private void SetControllerSpecificFields() { _leftSideTriggerBox.Hide(); _rightSideTriggerBox.Hide(); _motionAltBox.Hide(); switch (_controllerType.ActiveId) { case "JoyconLeft": _leftSideTriggerBox.Show(); break; case "JoyconRight": _rightSideTriggerBox.Show(); break; case "JoyconPair": _motionAltBox.Show(); break; } if (!OperatingSystem.IsMacOS()) { _controllerImage.Pixbuf = _controllerType.ActiveId switch { "ProController" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_ProCon.svg", 400, 400), "JoyconLeft" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConLeft.svg", 400, 500), "JoyconRight" => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConRight.svg", 400, 500), _ => new Gdk.Pixbuf(Assembly.GetAssembly(typeof(ConfigurationState)), "Ryujinx.UI.Common.Resources.Controller_JoyConPair.svg", 400, 500), }; } } private void ClearValues() { _lStick.Label = "Unbound"; _lStickUp.Label = "Unbound"; _lStickDown.Label = "Unbound"; _lStickLeft.Label = "Unbound"; _lStickRight.Label = "Unbound"; _lStickButton.Label = "Unbound"; _dpadUp.Label = "Unbound"; _dpadDown.Label = "Unbound"; _dpadLeft.Label = "Unbound"; _dpadRight.Label = "Unbound"; _minus.Label = "Unbound"; _l.Label = "Unbound"; _zL.Label = "Unbound"; _lSl.Label = "Unbound"; _lSr.Label = "Unbound"; _rStick.Label = "Unbound"; _rStickUp.Label = "Unbound"; _rStickDown.Label = "Unbound"; _rStickLeft.Label = "Unbound"; _rStickRight.Label = "Unbound"; _rStickButton.Label = "Unbound"; _a.Label = "Unbound"; _b.Label = "Unbound"; _x.Label = "Unbound"; _y.Label = "Unbound"; _plus.Label = "Unbound"; _r.Label = "Unbound"; _zR.Label = "Unbound"; _rSl.Label = "Unbound"; _rSr.Label = "Unbound"; _controllerStrongRumble.Value = 1; _controllerWeakRumble.Value = 1; _controllerDeadzoneLeft.Value = 0; _controllerDeadzoneRight.Value = 0; _controllerRangeLeft.Value = 1; _controllerRangeRight.Value = 1; _controllerTriggerThreshold.Value = 0; _mirrorInput.Active = false; _enableMotion.Active = false; _enableCemuHook.Active = false; _slotNumber.Value = 0; _altSlotNumber.Value = 0; _sensitivity.Value = 100; _gyroDeadzone.Value = 1; _dsuServerHost.Buffer.Text = ""; _dsuServerPort.Buffer.Text = ""; _enableRumble.Active = false; } private void SetValues(InputConfig config) { switch (config) { case StandardKeyboardInputConfig keyboardConfig: if (!_controllerType.SetActiveId(keyboardConfig.ControllerType.ToString())) { _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld ? ControllerType.Handheld.ToString() : ControllerType.ProController.ToString()); } _lStickUp.Label = keyboardConfig.LeftJoyconStick.StickUp.ToString(); _lStickDown.Label = keyboardConfig.LeftJoyconStick.StickDown.ToString(); _lStickLeft.Label = keyboardConfig.LeftJoyconStick.StickLeft.ToString(); _lStickRight.Label = keyboardConfig.LeftJoyconStick.StickRight.ToString(); _lStickButton.Label = keyboardConfig.LeftJoyconStick.StickButton.ToString(); _dpadUp.Label = keyboardConfig.LeftJoycon.DpadUp.ToString(); _dpadDown.Label = keyboardConfig.LeftJoycon.DpadDown.ToString(); _dpadLeft.Label = keyboardConfig.LeftJoycon.DpadLeft.ToString(); _dpadRight.Label = keyboardConfig.LeftJoycon.DpadRight.ToString(); _minus.Label = keyboardConfig.LeftJoycon.ButtonMinus.ToString(); _l.Label = keyboardConfig.LeftJoycon.ButtonL.ToString(); _zL.Label = keyboardConfig.LeftJoycon.ButtonZl.ToString(); _lSl.Label = keyboardConfig.LeftJoycon.ButtonSl.ToString(); _lSr.Label = keyboardConfig.LeftJoycon.ButtonSr.ToString(); _rStickUp.Label = keyboardConfig.RightJoyconStick.StickUp.ToString(); _rStickDown.Label = keyboardConfig.RightJoyconStick.StickDown.ToString(); _rStickLeft.Label = keyboardConfig.RightJoyconStick.StickLeft.ToString(); _rStickRight.Label = keyboardConfig.RightJoyconStick.StickRight.ToString(); _rStickButton.Label = keyboardConfig.RightJoyconStick.StickButton.ToString(); _a.Label = keyboardConfig.RightJoycon.ButtonA.ToString(); _b.Label = keyboardConfig.RightJoycon.ButtonB.ToString(); _x.Label = keyboardConfig.RightJoycon.ButtonX.ToString(); _y.Label = keyboardConfig.RightJoycon.ButtonY.ToString(); _plus.Label = keyboardConfig.RightJoycon.ButtonPlus.ToString(); _r.Label = keyboardConfig.RightJoycon.ButtonR.ToString(); _zR.Label = keyboardConfig.RightJoycon.ButtonZr.ToString(); _rSl.Label = keyboardConfig.RightJoycon.ButtonSl.ToString(); _rSr.Label = keyboardConfig.RightJoycon.ButtonSr.ToString(); break; case StandardControllerInputConfig controllerConfig: if (!_controllerType.SetActiveId(controllerConfig.ControllerType.ToString())) { _controllerType.SetActiveId(_playerIndex == PlayerIndex.Handheld ? ControllerType.Handheld.ToString() : ControllerType.ProController.ToString()); } _lStick.Label = controllerConfig.LeftJoyconStick.Joystick.ToString(); _invertLStickX.Active = controllerConfig.LeftJoyconStick.InvertStickX; _invertLStickY.Active = controllerConfig.LeftJoyconStick.InvertStickY; _rotateL90CW.Active = controllerConfig.LeftJoyconStick.Rotate90CW; _lStickButton.Label = controllerConfig.LeftJoyconStick.StickButton.ToString(); _dpadUp.Label = controllerConfig.LeftJoycon.DpadUp.ToString(); _dpadDown.Label = controllerConfig.LeftJoycon.DpadDown.ToString(); _dpadLeft.Label = controllerConfig.LeftJoycon.DpadLeft.ToString(); _dpadRight.Label = controllerConfig.LeftJoycon.DpadRight.ToString(); _minus.Label = controllerConfig.LeftJoycon.ButtonMinus.ToString(); _l.Label = controllerConfig.LeftJoycon.ButtonL.ToString(); _zL.Label = controllerConfig.LeftJoycon.ButtonZl.ToString(); _lSl.Label = controllerConfig.LeftJoycon.ButtonSl.ToString(); _lSr.Label = controllerConfig.LeftJoycon.ButtonSr.ToString(); _rStick.Label = controllerConfig.RightJoyconStick.Joystick.ToString(); _invertRStickX.Active = controllerConfig.RightJoyconStick.InvertStickX; _invertRStickY.Active = controllerConfig.RightJoyconStick.InvertStickY; _rotateR90CW.Active = controllerConfig.RightJoyconStick.Rotate90CW; _rStickButton.Label = controllerConfig.RightJoyconStick.StickButton.ToString(); _a.Label = controllerConfig.RightJoycon.ButtonA.ToString(); _b.Label = controllerConfig.RightJoycon.ButtonB.ToString(); _x.Label = controllerConfig.RightJoycon.ButtonX.ToString(); _y.Label = controllerConfig.RightJoycon.ButtonY.ToString(); _plus.Label = controllerConfig.RightJoycon.ButtonPlus.ToString(); _r.Label = controllerConfig.RightJoycon.ButtonR.ToString(); _zR.Label = controllerConfig.RightJoycon.ButtonZr.ToString(); _rSl.Label = controllerConfig.RightJoycon.ButtonSl.ToString(); _rSr.Label = controllerConfig.RightJoycon.ButtonSr.ToString(); _controllerStrongRumble.Value = controllerConfig.Rumble.StrongRumble; _controllerWeakRumble.Value = controllerConfig.Rumble.WeakRumble; _enableRumble.Active = controllerConfig.Rumble.EnableRumble; _controllerDeadzoneLeft.Value = controllerConfig.DeadzoneLeft; _controllerDeadzoneRight.Value = controllerConfig.DeadzoneRight; _controllerRangeLeft.Value = controllerConfig.RangeLeft; _controllerRangeRight.Value = controllerConfig.RangeRight; _controllerTriggerThreshold.Value = controllerConfig.TriggerThreshold; _sensitivity.Value = controllerConfig.Motion.Sensitivity; _gyroDeadzone.Value = controllerConfig.Motion.GyroDeadzone; _enableMotion.Active = controllerConfig.Motion.EnableMotion; _enableCemuHook.Active = controllerConfig.Motion.MotionBackend == MotionInputBackendType.CemuHook; // If both stick ranges are 0 (usually indicative of an outdated profile load) then both sticks will be set to 1.0. if (_controllerRangeLeft.Value <= 0.0 && _controllerRangeRight.Value <= 0.0) { _controllerRangeLeft.Value = 1.0; _controllerRangeRight.Value = 1.0; Logger.Info?.Print(LogClass.Application, $"{config.PlayerIndex} stick range reset. Save the profile now to update your configuration"); } if (controllerConfig.Motion is CemuHookMotionConfigController cemuHookMotionConfig) { _slotNumber.Value = cemuHookMotionConfig.Slot; _altSlotNumber.Value = cemuHookMotionConfig.AltSlot; _mirrorInput.Active = cemuHookMotionConfig.MirrorInput; _dsuServerHost.Buffer.Text = cemuHookMotionConfig.DsuServerHost; _dsuServerPort.Buffer.Text = cemuHookMotionConfig.DsuServerPort.ToString(); } break; } } private InputConfig GetValues() { if (_inputDevice.ActiveId.StartsWith("keyboard")) { #pragma warning disable CA1806, IDE0055 // Disable formatting Enum.TryParse(_lStickUp.Label, out Key lStickUp); Enum.TryParse(_lStickDown.Label, out Key lStickDown); Enum.TryParse(_lStickLeft.Label, out Key lStickLeft); Enum.TryParse(_lStickRight.Label, out Key lStickRight); Enum.TryParse(_lStickButton.Label, out Key lStickButton); Enum.TryParse(_dpadUp.Label, out Key lDPadUp); Enum.TryParse(_dpadDown.Label, out Key lDPadDown); Enum.TryParse(_dpadLeft.Label, out Key lDPadLeft); Enum.TryParse(_dpadRight.Label, out Key lDPadRight); Enum.TryParse(_minus.Label, out Key lButtonMinus); Enum.TryParse(_l.Label, out Key lButtonL); Enum.TryParse(_zL.Label, out Key lButtonZl); Enum.TryParse(_lSl.Label, out Key lButtonSl); Enum.TryParse(_lSr.Label, out Key lButtonSr); Enum.TryParse(_rStickUp.Label, out Key rStickUp); Enum.TryParse(_rStickDown.Label, out Key rStickDown); Enum.TryParse(_rStickLeft.Label, out Key rStickLeft); Enum.TryParse(_rStickRight.Label, out Key rStickRight); Enum.TryParse(_rStickButton.Label, out Key rStickButton); Enum.TryParse(_a.Label, out Key rButtonA); Enum.TryParse(_b.Label, out Key rButtonB); Enum.TryParse(_x.Label, out Key rButtonX); Enum.TryParse(_y.Label, out Key rButtonY); Enum.TryParse(_plus.Label, out Key rButtonPlus); Enum.TryParse(_r.Label, out Key rButtonR); Enum.TryParse(_zR.Label, out Key rButtonZr); Enum.TryParse(_rSl.Label, out Key rButtonSl); Enum.TryParse(_rSr.Label, out Key rButtonSr); #pragma warning restore CA1806, IDE0055 return new StandardKeyboardInputConfig { Backend = InputBackendType.WindowKeyboard, Version = InputConfig.CurrentVersion, Id = _inputDevice.ActiveId.Split("/")[1], ControllerType = Enum.Parse(_controllerType.ActiveId), PlayerIndex = _playerIndex, LeftJoycon = new LeftJoyconCommonConfig { ButtonMinus = lButtonMinus, ButtonL = lButtonL, ButtonZl = lButtonZl, ButtonSl = lButtonSl, ButtonSr = lButtonSr, DpadUp = lDPadUp, DpadDown = lDPadDown, DpadLeft = lDPadLeft, DpadRight = lDPadRight, }, LeftJoyconStick = new JoyconConfigKeyboardStick { StickUp = lStickUp, StickDown = lStickDown, StickLeft = lStickLeft, StickRight = lStickRight, StickButton = lStickButton, }, RightJoycon = new RightJoyconCommonConfig { ButtonA = rButtonA, ButtonB = rButtonB, ButtonX = rButtonX, ButtonY = rButtonY, ButtonPlus = rButtonPlus, ButtonR = rButtonR, ButtonZr = rButtonZr, ButtonSl = rButtonSl, ButtonSr = rButtonSr, }, RightJoyconStick = new JoyconConfigKeyboardStick { StickUp = rStickUp, StickDown = rStickDown, StickLeft = rStickLeft, StickRight = rStickRight, StickButton = rStickButton, }, }; } if (_inputDevice.ActiveId.StartsWith("controller")) { #pragma warning disable CA1806, IDE0055 // Disable formatting Enum.TryParse(_lStick.Label, out ConfigStickInputId lStick); Enum.TryParse(_lStickButton.Label, out ConfigGamepadInputId lStickButton); Enum.TryParse(_minus.Label, out ConfigGamepadInputId lButtonMinus); Enum.TryParse(_l.Label, out ConfigGamepadInputId lButtonL); Enum.TryParse(_zL.Label, out ConfigGamepadInputId lButtonZl); Enum.TryParse(_lSl.Label, out ConfigGamepadInputId lButtonSl); Enum.TryParse(_lSr.Label, out ConfigGamepadInputId lButtonSr); Enum.TryParse(_dpadUp.Label, out ConfigGamepadInputId lDPadUp); Enum.TryParse(_dpadDown.Label, out ConfigGamepadInputId lDPadDown); Enum.TryParse(_dpadLeft.Label, out ConfigGamepadInputId lDPadLeft); Enum.TryParse(_dpadRight.Label, out ConfigGamepadInputId lDPadRight); Enum.TryParse(_rStick.Label, out ConfigStickInputId rStick); Enum.TryParse(_rStickButton.Label, out ConfigGamepadInputId rStickButton); Enum.TryParse(_a.Label, out ConfigGamepadInputId rButtonA); Enum.TryParse(_b.Label, out ConfigGamepadInputId rButtonB); Enum.TryParse(_x.Label, out ConfigGamepadInputId rButtonX); Enum.TryParse(_y.Label, out ConfigGamepadInputId rButtonY); Enum.TryParse(_plus.Label, out ConfigGamepadInputId rButtonPlus); Enum.TryParse(_r.Label, out ConfigGamepadInputId rButtonR); Enum.TryParse(_zR.Label, out ConfigGamepadInputId rButtonZr); Enum.TryParse(_rSl.Label, out ConfigGamepadInputId rButtonSl); Enum.TryParse(_rSr.Label, out ConfigGamepadInputId rButtonSr); int.TryParse(_dsuServerPort.Buffer.Text, out int port); #pragma warning restore CA1806, IDE0055 MotionConfigController motionConfig; if (_enableCemuHook.Active) { motionConfig = new CemuHookMotionConfigController { MotionBackend = MotionInputBackendType.CemuHook, EnableMotion = _enableMotion.Active, Sensitivity = (int)_sensitivity.Value, GyroDeadzone = _gyroDeadzone.Value, MirrorInput = _mirrorInput.Active, Slot = (int)_slotNumber.Value, AltSlot = (int)_altSlotNumber.Value, DsuServerHost = _dsuServerHost.Buffer.Text, DsuServerPort = port, }; } else { motionConfig = new StandardMotionConfigController { MotionBackend = MotionInputBackendType.GamepadDriver, EnableMotion = _enableMotion.Active, Sensitivity = (int)_sensitivity.Value, GyroDeadzone = _gyroDeadzone.Value, }; } return new StandardControllerInputConfig { Backend = InputBackendType.GamepadSDL2, Version = InputConfig.CurrentVersion, Id = _inputDevice.ActiveId.Split("/")[1].Split(" ")[0], ControllerType = Enum.Parse(_controllerType.ActiveId), PlayerIndex = _playerIndex, DeadzoneLeft = (float)_controllerDeadzoneLeft.Value, DeadzoneRight = (float)_controllerDeadzoneRight.Value, RangeLeft = (float)_controllerRangeLeft.Value, RangeRight = (float)_controllerRangeRight.Value, TriggerThreshold = (float)_controllerTriggerThreshold.Value, LeftJoycon = new LeftJoyconCommonConfig { ButtonMinus = lButtonMinus, ButtonL = lButtonL, ButtonZl = lButtonZl, ButtonSl = lButtonSl, ButtonSr = lButtonSr, DpadUp = lDPadUp, DpadDown = lDPadDown, DpadLeft = lDPadLeft, DpadRight = lDPadRight, }, LeftJoyconStick = new JoyconConfigControllerStick { InvertStickX = _invertLStickX.Active, Joystick = lStick, InvertStickY = _invertLStickY.Active, StickButton = lStickButton, Rotate90CW = _rotateL90CW.Active, }, RightJoycon = new RightJoyconCommonConfig { ButtonA = rButtonA, ButtonB = rButtonB, ButtonX = rButtonX, ButtonY = rButtonY, ButtonPlus = rButtonPlus, ButtonR = rButtonR, ButtonZr = rButtonZr, ButtonSl = rButtonSl, ButtonSr = rButtonSr, }, RightJoyconStick = new JoyconConfigControllerStick { InvertStickX = _invertRStickX.Active, Joystick = rStick, InvertStickY = _invertRStickY.Active, StickButton = rStickButton, Rotate90CW = _rotateR90CW.Active, }, Motion = motionConfig, Rumble = new RumbleConfigController { StrongRumble = (float)_controllerStrongRumble.Value, WeakRumble = (float)_controllerWeakRumble.Value, EnableRumble = _enableRumble.Active, }, }; } if (!_inputDevice.ActiveId.StartsWith("disabled")) { GtkDialog.CreateErrorDialog("Invalid data detected in one or more fields; the configuration was not saved."); } return null; } private string GetProfileBasePath() { if (_inputDevice.ActiveId.StartsWith("keyboard")) { return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "keyboard"); } else if (_inputDevice.ActiveId.StartsWith("controller")) { return System.IO.Path.Combine(AppDataManager.ProfilesDirPath, "controller"); } return AppDataManager.ProfilesDirPath; } // // Events // private void InputDevice_Changed(object sender, EventArgs args) { SetAvailableOptions(); SetControllerSpecificFields(); _selectedGamepad?.Dispose(); _selectedGamepad = null; if (_inputDevice.ActiveId != null) { SetProfiles(); string id = GetCurrentGamepadId(); if (_inputDevice.ActiveId.StartsWith("keyboard")) { if (_inputConfig is StandardKeyboardInputConfig) { SetValues(_inputConfig); } if (_mainWindow.InputManager.KeyboardDriver is GTK3KeyboardDriver) { // NOTE: To get input in this window, we need to bind a custom keyboard driver instead of using the InputManager one as the main window isn't focused... _selectedGamepad = _gtk3KeyboardDriver.GetGamepad(id); } else { _selectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); } } else if (_inputDevice.ActiveId.StartsWith("controller")) { if (_inputConfig is StandardControllerInputConfig) { SetValues(_inputConfig); } _selectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); } } } private string GetCurrentGamepadId() { if (_inputDevice.ActiveId == null || _inputDevice.ActiveId == "disabled") { return null; } return _inputDevice.ActiveId.Split("/")[1].Split(" ")[0]; } private void Controller_Changed(object sender, EventArgs args) { SetControllerSpecificFields(); } private IButtonAssigner CreateButtonAssigner(bool forStick) { IButtonAssigner assigner; if (_inputDevice.ActiveId.StartsWith("keyboard")) { assigner = new KeyboardKeyAssigner((IKeyboard)_selectedGamepad); } else if (_inputDevice.ActiveId.StartsWith("controller")) { assigner = new GamepadButtonAssigner(_selectedGamepad, (float)_controllerTriggerThreshold.Value, forStick); } else { throw new Exception("Controller not supported"); } return assigner; } private void HandleButtonPressed(ToggleButton button, bool forStick) { if (_isWaitingForInput) { button.Active = false; return; } _mousePressed = false; ButtonPressEvent += MouseClick; IButtonAssigner assigner = CreateButtonAssigner(forStick); _isWaitingForInput = true; // Open GTK3 keyboard for cancel operations IKeyboard keyboard = (IKeyboard)_gtk3KeyboardDriver.GetGamepad("0"); Thread inputThread = new(() => { assigner.Initialize(); while (true) { Thread.Sleep(10); assigner.ReadInput(); if (_mousePressed || keyboard.IsPressed(Ryujinx.Input.Key.Escape) || assigner.HasAnyButtonPressed() || assigner.ShouldCancel()) { break; } } string pressedButton = assigner.GetPressedButton(); Application.Invoke(delegate { if (_middleMousePressed) { button.Label = "Unbound"; } else if (pressedButton != "") { button.Label = pressedButton; } _middleMousePressed = false; ButtonPressEvent -= MouseClick; keyboard.Dispose(); button.Active = false; _isWaitingForInput = false; }); }) { Name = "GUI.InputThread", IsBackground = true, }; inputThread.Start(); } private void Button_Pressed(object sender, EventArgs args) { HandleButtonPressed((ToggleButton)sender, false); } private void ButtonForStick_Pressed(object sender, EventArgs args) { HandleButtonPressed((ToggleButton)sender, true); } private void MouseClick(object sender, ButtonPressEventArgs args) { _mousePressed = true; _middleMousePressed = args.Event.Button == 2; } private void SetProfiles() { _profile.RemoveAll(); string basePath = GetProfileBasePath(); if (!Directory.Exists(basePath)) { Directory.CreateDirectory(basePath); } if (_inputDevice.ActiveId == null || _inputDevice.ActiveId.Equals("disabled")) { _profile.Append("default", "None"); } else { _profile.Append("default", "Default"); foreach (string profile in Directory.GetFiles(basePath, "*.*", SearchOption.AllDirectories)) { _profile.Append(System.IO.Path.GetFileName(profile), System.IO.Path.GetFileNameWithoutExtension(profile)); } } _profile.SetActiveId("default"); } private void ProfileLoad_Activated(object sender, EventArgs args) { ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == null) { return; } InputConfig config = null; int pos = _profile.Active; if (_profile.ActiveId == "default") { if (_inputDevice.ActiveId.StartsWith("keyboard")) { config = new StandardKeyboardInputConfig { Version = InputConfig.CurrentVersion, Backend = InputBackendType.WindowKeyboard, Id = null, ControllerType = ControllerType.ProController, LeftJoycon = new LeftJoyconCommonConfig { DpadUp = Key.Up, DpadDown = Key.Down, DpadLeft = Key.Left, DpadRight = Key.Right, ButtonMinus = Key.Minus, ButtonL = Key.E, ButtonZl = Key.Q, ButtonSl = Key.Unbound, ButtonSr = Key.Unbound, }, LeftJoyconStick = new JoyconConfigKeyboardStick { StickUp = Key.W, StickDown = Key.S, StickLeft = Key.A, StickRight = Key.D, StickButton = Key.F, }, RightJoycon = new RightJoyconCommonConfig { ButtonA = Key.Z, ButtonB = Key.X, ButtonX = Key.C, ButtonY = Key.V, ButtonPlus = Key.Plus, ButtonR = Key.U, ButtonZr = Key.O, ButtonSl = Key.Unbound, ButtonSr = Key.Unbound, }, RightJoyconStick = new JoyconConfigKeyboardStick { StickUp = Key.I, StickDown = Key.K, StickLeft = Key.J, StickRight = Key.L, StickButton = Key.H, }, }; } else if (_inputDevice.ActiveId.StartsWith("controller")) { bool isNintendoStyle = _inputDevice.ActiveText.Contains("Nintendo"); config = new StandardControllerInputConfig { Version = InputConfig.CurrentVersion, Backend = InputBackendType.GamepadSDL2, Id = null, ControllerType = ControllerType.JoyconPair, DeadzoneLeft = 0.1f, DeadzoneRight = 0.1f, RangeLeft = 1.0f, RangeRight = 1.0f, TriggerThreshold = 0.5f, LeftJoycon = new LeftJoyconCommonConfig { DpadUp = ConfigGamepadInputId.DpadUp, DpadDown = ConfigGamepadInputId.DpadDown, DpadLeft = ConfigGamepadInputId.DpadLeft, DpadRight = ConfigGamepadInputId.DpadRight, ButtonMinus = ConfigGamepadInputId.Minus, ButtonL = ConfigGamepadInputId.LeftShoulder, ButtonZl = ConfigGamepadInputId.LeftTrigger, ButtonSl = ConfigGamepadInputId.Unbound, ButtonSr = ConfigGamepadInputId.Unbound, }, LeftJoyconStick = new JoyconConfigControllerStick { Joystick = ConfigStickInputId.Left, StickButton = ConfigGamepadInputId.LeftStick, InvertStickX = false, InvertStickY = false, Rotate90CW = false, }, RightJoycon = new RightJoyconCommonConfig { ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, ButtonPlus = ConfigGamepadInputId.Plus, ButtonR = ConfigGamepadInputId.RightShoulder, ButtonZr = ConfigGamepadInputId.RightTrigger, ButtonSl = ConfigGamepadInputId.Unbound, ButtonSr = ConfigGamepadInputId.Unbound, }, RightJoyconStick = new JoyconConfigControllerStick { Joystick = ConfigStickInputId.Right, StickButton = ConfigGamepadInputId.RightStick, InvertStickX = false, InvertStickY = false, Rotate90CW = false, }, Motion = new StandardMotionConfigController { MotionBackend = MotionInputBackendType.GamepadDriver, EnableMotion = true, Sensitivity = 100, GyroDeadzone = 1, }, Rumble = new RumbleConfigController { StrongRumble = 1f, WeakRumble = 1f, EnableRumble = false, }, }; } } else { string path = System.IO.Path.Combine(GetProfileBasePath(), _profile.ActiveId); if (!File.Exists(path)) { if (pos >= 0) { _profile.Remove(pos); } return; } try { config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); } catch (JsonException) { } } SetValues(config); } private void ProfileAdd_Activated(object sender, EventArgs args) { ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); if (_inputDevice.ActiveId == "disabled") { return; } InputConfig inputConfig = GetValues(); ProfileDialog profileDialog = new(); if (inputConfig == null) { return; } if (profileDialog.Run() == (int)ResponseType.Ok) { string path = System.IO.Path.Combine(GetProfileBasePath(), profileDialog.FileName); string jsonString = JsonHelper.Serialize(inputConfig, _serializerContext.InputConfig); File.WriteAllText(path, jsonString); } profileDialog.Dispose(); SetProfiles(); } private void ProfileRemove_Activated(object sender, EventArgs args) { ((ToggleButton)sender).SetStateFlags(StateFlags.Normal, true); if (_inputDevice.ActiveId == "disabled" || _profile.ActiveId == "default" || _profile.ActiveId == null) { return; } MessageDialog confirmDialog = GtkDialog.CreateConfirmationDialog("Deleting Profile", "This action is irreversible, are you sure you want to continue?"); if (confirmDialog.Run() == (int)ResponseType.Yes) { string path = System.IO.Path.Combine(GetProfileBasePath(), _profile.ActiveId); if (File.Exists(path)) { File.Delete(path); } SetProfiles(); } } private void SaveToggle_Activated(object sender, EventArgs args) { InputConfig inputConfig = GetValues(); var newConfig = new List(); newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); if (_inputConfig == null && inputConfig != null) { newConfig.Add(inputConfig); } else { if (_inputDevice.ActiveId == "disabled") { newConfig.Remove(_inputConfig); } else if (inputConfig != null) { int index = newConfig.IndexOf(_inputConfig); newConfig[index] = inputConfig; } } _mainWindow.RendererWidget?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); // Atomically replace and signal input change. // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); Dispose(); } private void CloseToggle_Activated(object sender, EventArgs args) { Dispose(); } } }