InputViewModel.cs
1 using Avalonia; 2 using Avalonia.Collections; 3 using Avalonia.Controls; 4 using Avalonia.Controls.ApplicationLifetimes; 5 using Avalonia.Svg.Skia; 6 using Avalonia.Threading; 7 using Ryujinx.Ava.Common.Locale; 8 using Ryujinx.Ava.Input; 9 using Ryujinx.Ava.UI.Helpers; 10 using Ryujinx.Ava.UI.Models; 11 using Ryujinx.Ava.UI.Models.Input; 12 using Ryujinx.Ava.UI.Windows; 13 using Ryujinx.Common; 14 using Ryujinx.Common.Configuration; 15 using Ryujinx.Common.Configuration.Hid; 16 using Ryujinx.Common.Configuration.Hid.Controller; 17 using Ryujinx.Common.Configuration.Hid.Controller.Motion; 18 using Ryujinx.Common.Configuration.Hid.Keyboard; 19 using Ryujinx.Common.Logging; 20 using Ryujinx.Common.Utilities; 21 using Ryujinx.Input; 22 using Ryujinx.UI.Common.Configuration; 23 using System; 24 using System.Collections.Generic; 25 using System.Collections.ObjectModel; 26 using System.IO; 27 using System.Linq; 28 using System.Text.Json; 29 using ConfigGamepadInputId = Ryujinx.Common.Configuration.Hid.Controller.GamepadInputId; 30 using ConfigStickInputId = Ryujinx.Common.Configuration.Hid.Controller.StickInputId; 31 using Key = Ryujinx.Common.Configuration.Hid.Key; 32 33 namespace Ryujinx.Ava.UI.ViewModels.Input 34 { 35 public class InputViewModel : BaseModel, IDisposable 36 { 37 private const string Disabled = "disabled"; 38 private const string ProControllerResource = "Ryujinx.UI.Common/Resources/Controller_ProCon.svg"; 39 private const string JoyConPairResource = "Ryujinx.UI.Common/Resources/Controller_JoyConPair.svg"; 40 private const string JoyConLeftResource = "Ryujinx.UI.Common/Resources/Controller_JoyConLeft.svg"; 41 private const string JoyConRightResource = "Ryujinx.UI.Common/Resources/Controller_JoyConRight.svg"; 42 private const string KeyboardString = "keyboard"; 43 private const string ControllerString = "controller"; 44 private readonly MainWindow _mainWindow; 45 46 private PlayerIndex _playerId; 47 private int _controller; 48 private int _controllerNumber; 49 private string _controllerImage; 50 private int _device; 51 private object _configViewModel; 52 private string _profileName; 53 private bool _isLoaded; 54 55 private static readonly InputConfigJsonSerializerContext _serializerContext = new(JsonHelper.GetDefaultSerializerOptions()); 56 57 public IGamepadDriver AvaloniaKeyboardDriver { get; } 58 public IGamepad SelectedGamepad { get; private set; } 59 60 public ObservableCollection<PlayerModel> PlayerIndexes { get; set; } 61 public ObservableCollection<(DeviceType Type, string Id, string Name)> Devices { get; set; } 62 internal ObservableCollection<ControllerModel> Controllers { get; set; } 63 public AvaloniaList<string> ProfilesList { get; set; } 64 public AvaloniaList<string> DeviceList { get; set; } 65 66 // XAML Flags 67 public bool ShowSettings => _device > 0; 68 public bool IsController => _device > 1; 69 public bool IsKeyboard => !IsController; 70 public bool IsRight { get; set; } 71 public bool IsLeft { get; set; } 72 73 public bool IsModified { get; set; } 74 public event Action NotifyChangesEvent; 75 76 public object ConfigViewModel 77 { 78 get => _configViewModel; 79 set 80 { 81 _configViewModel = value; 82 83 OnPropertyChanged(); 84 } 85 } 86 87 public PlayerIndex PlayerId 88 { 89 get => _playerId; 90 set 91 { 92 if (IsModified) 93 { 94 return; 95 } 96 97 IsModified = false; 98 _playerId = value; 99 100 if (!Enum.IsDefined(typeof(PlayerIndex), _playerId)) 101 { 102 _playerId = PlayerIndex.Player1; 103 } 104 105 LoadConfiguration(); 106 LoadDevice(); 107 LoadProfiles(); 108 109 _isLoaded = true; 110 111 OnPropertyChanged(); 112 } 113 } 114 115 public int Controller 116 { 117 get => _controller; 118 set 119 { 120 _controller = value; 121 122 if (_controller == -1) 123 { 124 _controller = 0; 125 } 126 127 if (Controllers.Count > 0 && value < Controllers.Count && _controller > -1) 128 { 129 ControllerType controller = Controllers[_controller].Type; 130 131 IsLeft = true; 132 IsRight = true; 133 134 switch (controller) 135 { 136 case ControllerType.Handheld: 137 ControllerImage = JoyConPairResource; 138 break; 139 case ControllerType.ProController: 140 ControllerImage = ProControllerResource; 141 break; 142 case ControllerType.JoyconPair: 143 ControllerImage = JoyConPairResource; 144 break; 145 case ControllerType.JoyconLeft: 146 ControllerImage = JoyConLeftResource; 147 IsRight = false; 148 break; 149 case ControllerType.JoyconRight: 150 ControllerImage = JoyConRightResource; 151 IsLeft = false; 152 break; 153 } 154 155 LoadInputDriver(); 156 LoadProfiles(); 157 } 158 159 OnPropertyChanged(); 160 NotifyChanges(); 161 } 162 } 163 164 public string ControllerImage 165 { 166 get => _controllerImage; 167 set 168 { 169 _controllerImage = value; 170 171 OnPropertyChanged(); 172 OnPropertyChanged(nameof(Image)); 173 } 174 } 175 176 public SvgImage Image 177 { 178 get 179 { 180 SvgImage image = new(); 181 182 if (!string.IsNullOrWhiteSpace(_controllerImage)) 183 { 184 SvgSource source = SvgSource.LoadFromStream(EmbeddedResources.GetStream(_controllerImage)); 185 186 image.Source = source; 187 } 188 189 return image; 190 } 191 } 192 193 public string ProfileName 194 { 195 get => _profileName; set 196 { 197 _profileName = value; 198 199 OnPropertyChanged(); 200 } 201 } 202 203 public int Device 204 { 205 get => _device; 206 set 207 { 208 _device = value < 0 ? 0 : value; 209 210 if (_device >= Devices.Count) 211 { 212 return; 213 } 214 215 var selected = Devices[_device].Type; 216 217 if (selected != DeviceType.None) 218 { 219 LoadControllers(); 220 221 if (_isLoaded) 222 { 223 LoadConfiguration(LoadDefaultConfiguration()); 224 } 225 } 226 227 OnPropertyChanged(); 228 NotifyChanges(); 229 } 230 } 231 232 public InputConfig Config { get; set; } 233 234 public InputViewModel(UserControl owner) : this() 235 { 236 if (Program.PreviewerDetached) 237 { 238 _mainWindow = 239 (MainWindow)((IClassicDesktopStyleApplicationLifetime)Application.Current 240 .ApplicationLifetime).MainWindow; 241 242 AvaloniaKeyboardDriver = new AvaloniaKeyboardDriver(owner); 243 244 _mainWindow.InputManager.GamepadDriver.OnGamepadConnected += HandleOnGamepadConnected; 245 _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected += HandleOnGamepadDisconnected; 246 247 _mainWindow.ViewModel.AppHost?.NpadManager.BlockInputUpdates(); 248 249 _isLoaded = false; 250 251 LoadDevices(); 252 253 PlayerId = PlayerIndex.Player1; 254 } 255 } 256 257 public InputViewModel() 258 { 259 PlayerIndexes = new ObservableCollection<PlayerModel>(); 260 Controllers = new ObservableCollection<ControllerModel>(); 261 Devices = new ObservableCollection<(DeviceType Type, string Id, string Name)>(); 262 ProfilesList = new AvaloniaList<string>(); 263 DeviceList = new AvaloniaList<string>(); 264 265 ControllerImage = ProControllerResource; 266 267 PlayerIndexes.Add(new(PlayerIndex.Player1, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer1])); 268 PlayerIndexes.Add(new(PlayerIndex.Player2, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer2])); 269 PlayerIndexes.Add(new(PlayerIndex.Player3, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer3])); 270 PlayerIndexes.Add(new(PlayerIndex.Player4, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer4])); 271 PlayerIndexes.Add(new(PlayerIndex.Player5, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer5])); 272 PlayerIndexes.Add(new(PlayerIndex.Player6, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer6])); 273 PlayerIndexes.Add(new(PlayerIndex.Player7, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer7])); 274 PlayerIndexes.Add(new(PlayerIndex.Player8, LocaleManager.Instance[LocaleKeys.ControllerSettingsPlayer8])); 275 PlayerIndexes.Add(new(PlayerIndex.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsHandheld])); 276 } 277 278 private void LoadConfiguration(InputConfig inputConfig = null) 279 { 280 Config = inputConfig ?? ConfigurationState.Instance.Hid.InputConfig.Value.Find(inputConfig => inputConfig.PlayerIndex == _playerId); 281 282 if (Config is StandardKeyboardInputConfig keyboardInputConfig) 283 { 284 ConfigViewModel = new KeyboardInputViewModel(this, new KeyboardInputConfig(keyboardInputConfig)); 285 } 286 287 if (Config is StandardControllerInputConfig controllerInputConfig) 288 { 289 ConfigViewModel = new ControllerInputViewModel(this, new GamepadInputConfig(controllerInputConfig)); 290 } 291 } 292 293 public void LoadDevice() 294 { 295 if (Config == null || Config.Backend == InputBackendType.Invalid) 296 { 297 Device = 0; 298 } 299 else 300 { 301 var type = DeviceType.None; 302 303 if (Config is StandardKeyboardInputConfig) 304 { 305 type = DeviceType.Keyboard; 306 } 307 308 if (Config is StandardControllerInputConfig) 309 { 310 type = DeviceType.Controller; 311 } 312 313 var item = Devices.FirstOrDefault(x => x.Type == type && x.Id == Config.Id); 314 if (item != default) 315 { 316 Device = Devices.ToList().FindIndex(x => x.Id == item.Id); 317 } 318 else 319 { 320 Device = 0; 321 } 322 } 323 } 324 325 private void LoadInputDriver() 326 { 327 if (_device < 0) 328 { 329 return; 330 } 331 332 string id = GetCurrentGamepadId(); 333 var type = Devices[Device].Type; 334 335 if (type == DeviceType.None) 336 { 337 return; 338 } 339 340 if (type == DeviceType.Keyboard) 341 { 342 if (_mainWindow.InputManager.KeyboardDriver is AvaloniaKeyboardDriver) 343 { 344 // 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... 345 SelectedGamepad = AvaloniaKeyboardDriver.GetGamepad(id); 346 } 347 else 348 { 349 SelectedGamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); 350 } 351 } 352 else 353 { 354 SelectedGamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); 355 } 356 } 357 358 private void HandleOnGamepadDisconnected(string id) 359 { 360 Dispatcher.UIThread.Post(() => 361 { 362 LoadDevices(); 363 }); 364 } 365 366 private void HandleOnGamepadConnected(string id) 367 { 368 Dispatcher.UIThread.Post(() => 369 { 370 LoadDevices(); 371 }); 372 } 373 374 private string GetCurrentGamepadId() 375 { 376 if (_device < 0) 377 { 378 return string.Empty; 379 } 380 381 var device = Devices[Device]; 382 383 if (device.Type == DeviceType.None) 384 { 385 return null; 386 } 387 388 return device.Id.Split(" ")[0]; 389 } 390 391 public void LoadControllers() 392 { 393 Controllers.Clear(); 394 395 if (_playerId == PlayerIndex.Handheld) 396 { 397 Controllers.Add(new(ControllerType.Handheld, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeHandheld])); 398 399 Controller = 0; 400 } 401 else 402 { 403 Controllers.Add(new(ControllerType.ProController, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeProController])); 404 Controllers.Add(new(ControllerType.JoyconPair, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConPair])); 405 Controllers.Add(new(ControllerType.JoyconLeft, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConLeft])); 406 Controllers.Add(new(ControllerType.JoyconRight, LocaleManager.Instance[LocaleKeys.ControllerSettingsControllerTypeJoyConRight])); 407 408 if (Config != null && Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType) != -1) 409 { 410 Controller = Controllers.ToList().FindIndex(x => x.Type == Config.ControllerType); 411 } 412 else 413 { 414 Controller = 0; 415 } 416 } 417 } 418 419 private static string GetShortGamepadName(string str) 420 { 421 const string Ellipsis = "..."; 422 const int MaxSize = 50; 423 424 if (str.Length > MaxSize) 425 { 426 return $"{str.AsSpan(0, MaxSize - Ellipsis.Length)}{Ellipsis}"; 427 } 428 429 return str; 430 } 431 432 private static string GetShortGamepadId(string str) 433 { 434 const string Hyphen = "-"; 435 const int Offset = 1; 436 437 return str[(str.IndexOf(Hyphen) + Offset)..]; 438 } 439 440 public void LoadDevices() 441 { 442 lock (Devices) 443 { 444 Devices.Clear(); 445 DeviceList.Clear(); 446 Devices.Add((DeviceType.None, Disabled, LocaleManager.Instance[LocaleKeys.ControllerSettingsDeviceDisabled])); 447 448 foreach (string id in _mainWindow.InputManager.KeyboardDriver.GamepadsIds) 449 { 450 using IGamepad gamepad = _mainWindow.InputManager.KeyboardDriver.GetGamepad(id); 451 452 if (gamepad != null) 453 { 454 Devices.Add((DeviceType.Keyboard, id, $"{GetShortGamepadName(gamepad.Name)}")); 455 } 456 } 457 458 foreach (string id in _mainWindow.InputManager.GamepadDriver.GamepadsIds) 459 { 460 using IGamepad gamepad = _mainWindow.InputManager.GamepadDriver.GetGamepad(id); 461 462 if (gamepad != null) 463 { 464 if (Devices.Any(controller => GetShortGamepadId(controller.Id) == GetShortGamepadId(gamepad.Id))) 465 { 466 _controllerNumber++; 467 } 468 469 Devices.Add((DeviceType.Controller, id, $"{GetShortGamepadName(gamepad.Name)} ({_controllerNumber})")); 470 } 471 } 472 473 _controllerNumber = 0; 474 475 DeviceList.AddRange(Devices.Select(x => x.Name)); 476 Device = Math.Min(Device, DeviceList.Count); 477 } 478 } 479 480 private string GetProfileBasePath() 481 { 482 string path = AppDataManager.ProfilesDirPath; 483 var type = Devices[Device == -1 ? 0 : Device].Type; 484 485 if (type == DeviceType.Keyboard) 486 { 487 path = Path.Combine(path, KeyboardString); 488 } 489 else if (type == DeviceType.Controller) 490 { 491 path = Path.Combine(path, ControllerString); 492 } 493 494 return path; 495 } 496 497 private void LoadProfiles() 498 { 499 ProfilesList.Clear(); 500 501 string basePath = GetProfileBasePath(); 502 503 if (!Directory.Exists(basePath)) 504 { 505 Directory.CreateDirectory(basePath); 506 } 507 508 ProfilesList.Add((LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault])); 509 510 foreach (string profile in Directory.GetFiles(basePath, "*.json", SearchOption.AllDirectories)) 511 { 512 ProfilesList.Add(Path.GetFileNameWithoutExtension(profile)); 513 } 514 515 if (string.IsNullOrWhiteSpace(ProfileName)) 516 { 517 ProfileName = LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]; 518 } 519 } 520 521 public InputConfig LoadDefaultConfiguration() 522 { 523 var activeDevice = Devices.FirstOrDefault(); 524 525 if (Devices.Count > 0 && Device < Devices.Count && Device >= 0) 526 { 527 activeDevice = Devices[Device]; 528 } 529 530 InputConfig config; 531 if (activeDevice.Type == DeviceType.Keyboard) 532 { 533 string id = activeDevice.Id; 534 535 config = new StandardKeyboardInputConfig 536 { 537 Version = InputConfig.CurrentVersion, 538 Backend = InputBackendType.WindowKeyboard, 539 Id = id, 540 ControllerType = ControllerType.ProController, 541 LeftJoycon = new LeftJoyconCommonConfig<Key> 542 { 543 DpadUp = Key.Up, 544 DpadDown = Key.Down, 545 DpadLeft = Key.Left, 546 DpadRight = Key.Right, 547 ButtonMinus = Key.Minus, 548 ButtonL = Key.E, 549 ButtonZl = Key.Q, 550 ButtonSl = Key.Unbound, 551 ButtonSr = Key.Unbound, 552 }, 553 LeftJoyconStick = 554 new JoyconConfigKeyboardStick<Key> 555 { 556 StickUp = Key.W, 557 StickDown = Key.S, 558 StickLeft = Key.A, 559 StickRight = Key.D, 560 StickButton = Key.F, 561 }, 562 RightJoycon = new RightJoyconCommonConfig<Key> 563 { 564 ButtonA = Key.Z, 565 ButtonB = Key.X, 566 ButtonX = Key.C, 567 ButtonY = Key.V, 568 ButtonPlus = Key.Plus, 569 ButtonR = Key.U, 570 ButtonZr = Key.O, 571 ButtonSl = Key.Unbound, 572 ButtonSr = Key.Unbound, 573 }, 574 RightJoyconStick = new JoyconConfigKeyboardStick<Key> 575 { 576 StickUp = Key.I, 577 StickDown = Key.K, 578 StickLeft = Key.J, 579 StickRight = Key.L, 580 StickButton = Key.H, 581 }, 582 }; 583 } 584 else if (activeDevice.Type == DeviceType.Controller) 585 { 586 bool isNintendoStyle = Devices.ToList().Find(x => x.Id == activeDevice.Id).Name.Contains("Nintendo"); 587 588 string id = activeDevice.Id.Split(" ")[0]; 589 590 config = new StandardControllerInputConfig 591 { 592 Version = InputConfig.CurrentVersion, 593 Backend = InputBackendType.GamepadSDL2, 594 Id = id, 595 ControllerType = ControllerType.ProController, 596 DeadzoneLeft = 0.1f, 597 DeadzoneRight = 0.1f, 598 RangeLeft = 1.0f, 599 RangeRight = 1.0f, 600 TriggerThreshold = 0.5f, 601 LeftJoycon = new LeftJoyconCommonConfig<ConfigGamepadInputId> 602 { 603 DpadUp = ConfigGamepadInputId.DpadUp, 604 DpadDown = ConfigGamepadInputId.DpadDown, 605 DpadLeft = ConfigGamepadInputId.DpadLeft, 606 DpadRight = ConfigGamepadInputId.DpadRight, 607 ButtonMinus = ConfigGamepadInputId.Minus, 608 ButtonL = ConfigGamepadInputId.LeftShoulder, 609 ButtonZl = ConfigGamepadInputId.LeftTrigger, 610 ButtonSl = ConfigGamepadInputId.Unbound, 611 ButtonSr = ConfigGamepadInputId.Unbound, 612 }, 613 LeftJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> 614 { 615 Joystick = ConfigStickInputId.Left, 616 StickButton = ConfigGamepadInputId.LeftStick, 617 InvertStickX = false, 618 InvertStickY = false, 619 }, 620 RightJoycon = new RightJoyconCommonConfig<ConfigGamepadInputId> 621 { 622 ButtonA = isNintendoStyle ? ConfigGamepadInputId.A : ConfigGamepadInputId.B, 623 ButtonB = isNintendoStyle ? ConfigGamepadInputId.B : ConfigGamepadInputId.A, 624 ButtonX = isNintendoStyle ? ConfigGamepadInputId.X : ConfigGamepadInputId.Y, 625 ButtonY = isNintendoStyle ? ConfigGamepadInputId.Y : ConfigGamepadInputId.X, 626 ButtonPlus = ConfigGamepadInputId.Plus, 627 ButtonR = ConfigGamepadInputId.RightShoulder, 628 ButtonZr = ConfigGamepadInputId.RightTrigger, 629 ButtonSl = ConfigGamepadInputId.Unbound, 630 ButtonSr = ConfigGamepadInputId.Unbound, 631 }, 632 RightJoyconStick = new JoyconConfigControllerStick<ConfigGamepadInputId, ConfigStickInputId> 633 { 634 Joystick = ConfigStickInputId.Right, 635 StickButton = ConfigGamepadInputId.RightStick, 636 InvertStickX = false, 637 InvertStickY = false, 638 }, 639 Motion = new StandardMotionConfigController 640 { 641 MotionBackend = MotionInputBackendType.GamepadDriver, 642 EnableMotion = true, 643 Sensitivity = 100, 644 GyroDeadzone = 1, 645 }, 646 Rumble = new RumbleConfigController 647 { 648 StrongRumble = 1f, 649 WeakRumble = 1f, 650 EnableRumble = false, 651 }, 652 }; 653 } 654 else 655 { 656 config = new InputConfig(); 657 } 658 659 config.PlayerIndex = _playerId; 660 661 return config; 662 } 663 664 public async void LoadProfile() 665 { 666 if (Device == 0) 667 { 668 return; 669 } 670 671 InputConfig config = null; 672 673 if (string.IsNullOrWhiteSpace(ProfileName)) 674 { 675 return; 676 } 677 678 if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) 679 { 680 config = LoadDefaultConfiguration(); 681 } 682 else 683 { 684 string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); 685 686 if (!File.Exists(path)) 687 { 688 var index = ProfilesList.IndexOf(ProfileName); 689 if (index != -1) 690 { 691 ProfilesList.RemoveAt(index); 692 } 693 return; 694 } 695 696 try 697 { 698 config = JsonHelper.DeserializeFromFile(path, _serializerContext.InputConfig); 699 } 700 catch (JsonException) { } 701 catch (InvalidOperationException) 702 { 703 Logger.Error?.Print(LogClass.Configuration, $"Profile {ProfileName} is incompatible with the current input configuration system."); 704 705 await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogProfileInvalidProfileErrorMessage, ProfileName)); 706 707 return; 708 } 709 } 710 711 if (config != null) 712 { 713 _isLoaded = false; 714 715 LoadConfiguration(config); 716 717 LoadDevice(); 718 719 _isLoaded = true; 720 721 NotifyChanges(); 722 } 723 } 724 725 public async void SaveProfile() 726 { 727 if (Device == 0) 728 { 729 return; 730 } 731 732 if (ConfigViewModel == null) 733 { 734 return; 735 } 736 737 if (ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault]) 738 { 739 await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileDefaultProfileOverwriteErrorMessage]); 740 741 return; 742 } 743 else 744 { 745 bool validFileName = ProfileName.IndexOfAny(Path.GetInvalidFileNameChars()) == -1; 746 747 if (validFileName) 748 { 749 string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); 750 751 InputConfig config = null; 752 753 if (IsKeyboard) 754 { 755 config = (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig(); 756 } 757 else if (IsController) 758 { 759 config = (ConfigViewModel as ControllerInputViewModel).Config.GetConfig(); 760 } 761 762 config.ControllerType = Controllers[_controller].Type; 763 764 string jsonString = JsonHelper.Serialize(config, _serializerContext.InputConfig); 765 766 await File.WriteAllTextAsync(path, jsonString); 767 768 LoadProfiles(); 769 } 770 else 771 { 772 await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogProfileInvalidProfileNameErrorMessage]); 773 } 774 } 775 } 776 777 public async void RemoveProfile() 778 { 779 if (Device == 0 || ProfileName == LocaleManager.Instance[LocaleKeys.ControllerSettingsProfileDefault] || ProfilesList.IndexOf(ProfileName) == -1) 780 { 781 return; 782 } 783 784 UserResult result = await ContentDialogHelper.CreateConfirmationDialog( 785 LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileTitle], 786 LocaleManager.Instance[LocaleKeys.DialogProfileDeleteProfileMessage], 787 LocaleManager.Instance[LocaleKeys.InputDialogYes], 788 LocaleManager.Instance[LocaleKeys.InputDialogNo], 789 LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); 790 791 if (result == UserResult.Yes) 792 { 793 string path = Path.Combine(GetProfileBasePath(), ProfileName + ".json"); 794 795 if (File.Exists(path)) 796 { 797 File.Delete(path); 798 } 799 800 LoadProfiles(); 801 } 802 } 803 804 public void Save() 805 { 806 IsModified = false; 807 808 List<InputConfig> newConfig = new(); 809 810 newConfig.AddRange(ConfigurationState.Instance.Hid.InputConfig.Value); 811 812 newConfig.Remove(newConfig.Find(x => x == null)); 813 814 if (Device == 0) 815 { 816 newConfig.Remove(newConfig.Find(x => x.PlayerIndex == this.PlayerId)); 817 } 818 else 819 { 820 var device = Devices[Device]; 821 822 if (device.Type == DeviceType.Keyboard) 823 { 824 var inputConfig = (ConfigViewModel as KeyboardInputViewModel).Config; 825 inputConfig.Id = device.Id; 826 } 827 else 828 { 829 var inputConfig = (ConfigViewModel as ControllerInputViewModel).Config; 830 inputConfig.Id = device.Id.Split(" ")[0]; 831 } 832 833 var config = !IsController 834 ? (ConfigViewModel as KeyboardInputViewModel).Config.GetConfig() 835 : (ConfigViewModel as ControllerInputViewModel).Config.GetConfig(); 836 config.ControllerType = Controllers[_controller].Type; 837 config.PlayerIndex = _playerId; 838 839 int i = newConfig.FindIndex(x => x.PlayerIndex == PlayerId); 840 if (i == -1) 841 { 842 newConfig.Add(config); 843 } 844 else 845 { 846 newConfig[i] = config; 847 } 848 } 849 850 _mainWindow.ViewModel.AppHost?.NpadManager.ReloadConfiguration(newConfig, ConfigurationState.Instance.Hid.EnableKeyboard, ConfigurationState.Instance.Hid.EnableMouse); 851 852 // Atomically replace and signal input change. 853 // NOTE: Do not modify InputConfig.Value directly as other code depends on the on-change event. 854 ConfigurationState.Instance.Hid.InputConfig.Value = newConfig; 855 856 ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 857 } 858 859 public void NotifyChange(string property) 860 { 861 OnPropertyChanged(property); 862 } 863 864 public void NotifyChanges() 865 { 866 OnPropertyChanged(nameof(ConfigViewModel)); 867 OnPropertyChanged(nameof(IsController)); 868 OnPropertyChanged(nameof(ShowSettings)); 869 OnPropertyChanged(nameof(IsKeyboard)); 870 OnPropertyChanged(nameof(IsRight)); 871 OnPropertyChanged(nameof(IsLeft)); 872 NotifyChangesEvent?.Invoke(); 873 } 874 875 public void Dispose() 876 { 877 GC.SuppressFinalize(this); 878 879 _mainWindow.InputManager.GamepadDriver.OnGamepadConnected -= HandleOnGamepadConnected; 880 _mainWindow.InputManager.GamepadDriver.OnGamepadDisconnected -= HandleOnGamepadDisconnected; 881 882 _mainWindow.ViewModel.AppHost?.NpadManager.UnblockInputUpdates(); 883 884 SelectedGamepad?.Dispose(); 885 886 AvaloniaKeyboardDriver.Dispose(); 887 } 888 } 889 }