MainWindowViewModel.cs
1 using Avalonia; 2 using Avalonia.Controls; 3 using Avalonia.Controls.ApplicationLifetimes; 4 using Avalonia.Input; 5 using Avalonia.Media; 6 using Avalonia.Platform.Storage; 7 using Avalonia.Threading; 8 using DynamicData; 9 using DynamicData.Binding; 10 using LibHac.Common; 11 using Ryujinx.Ava.Common; 12 using Ryujinx.Ava.Common.Locale; 13 using Ryujinx.Ava.Input; 14 using Ryujinx.Ava.UI.Controls; 15 using Ryujinx.Ava.UI.Helpers; 16 using Ryujinx.Ava.UI.Models; 17 using Ryujinx.Ava.UI.Models.Generic; 18 using Ryujinx.Ava.UI.Renderer; 19 using Ryujinx.Ava.UI.Windows; 20 using Ryujinx.Common; 21 using Ryujinx.Common.Configuration; 22 using Ryujinx.Common.Logging; 23 using Ryujinx.Cpu; 24 using Ryujinx.HLE; 25 using Ryujinx.HLE.FileSystem; 26 using Ryujinx.HLE.HOS; 27 using Ryujinx.HLE.HOS.Services.Account.Acc; 28 using Ryujinx.HLE.UI; 29 using Ryujinx.Input.HLE; 30 using Ryujinx.Modules; 31 using Ryujinx.UI.App.Common; 32 using Ryujinx.UI.Common; 33 using Ryujinx.UI.Common.Configuration; 34 using Ryujinx.UI.Common.Helper; 35 using SkiaSharp; 36 using System; 37 using System.Collections.Generic; 38 using System.Collections.ObjectModel; 39 using System.Globalization; 40 using System.IO; 41 using System.Threading; 42 using System.Threading.Tasks; 43 using Key = Ryujinx.Input.Key; 44 using MissingKeyException = LibHac.Common.Keys.MissingKeyException; 45 using ShaderCacheLoadingState = Ryujinx.Graphics.Gpu.Shader.ShaderCacheState; 46 47 namespace Ryujinx.Ava.UI.ViewModels 48 { 49 public class MainWindowViewModel : BaseModel 50 { 51 private const int HotKeyPressDelayMs = 500; 52 53 private ObservableCollection<ApplicationData> _applications; 54 private string _aspectStatusText; 55 56 private string _loadHeading; 57 private string _cacheLoadStatus; 58 private string _searchText; 59 private Timer _searchTimer; 60 private string _dockedStatusText; 61 private string _fifoStatusText; 62 private string _gameStatusText; 63 private string _volumeStatusText; 64 private string _gpuStatusText; 65 private bool _isAmiiboRequested; 66 private bool _isGameRunning; 67 private bool _isFullScreen; 68 private int _progressMaximum; 69 private int _progressValue; 70 private long _lastFullscreenToggle = Environment.TickCount64; 71 private bool _showLoadProgress; 72 private bool _showMenuAndStatusBar = true; 73 private bool _showStatusSeparator; 74 private Brush _progressBarForegroundColor; 75 private Brush _progressBarBackgroundColor; 76 private Brush _vsyncColor; 77 private byte[] _selectedIcon; 78 private bool _isAppletMenuActive; 79 private int _statusBarProgressMaximum; 80 private int _statusBarProgressValue; 81 private bool _isPaused; 82 private bool _showContent = true; 83 private bool _isLoadingIndeterminate = true; 84 private bool _showAll; 85 private string _lastScannedAmiiboId; 86 private bool _statusBarVisible; 87 private ReadOnlyObservableCollection<ApplicationData> _appsObservableList; 88 89 private string _showUiKey = "F4"; 90 private string _pauseKey = "F5"; 91 private string _screenshotKey = "F8"; 92 private float _volume; 93 private float _volumeBeforeMute; 94 private string _backendText; 95 96 private bool _canUpdate = true; 97 private Cursor _cursor; 98 private string _title; 99 private ApplicationData _currentApplicationData; 100 private readonly AutoResetEvent _rendererWaitEvent; 101 private WindowState _windowState; 102 private double _windowWidth; 103 private double _windowHeight; 104 105 private bool _isActive; 106 private bool _isSubMenuOpen; 107 108 public ApplicationData ListSelectedApplication; 109 public ApplicationData GridSelectedApplication; 110 111 internal AppHost AppHost { get; set; } 112 113 public MainWindowViewModel() 114 { 115 Applications = new ObservableCollection<ApplicationData>(); 116 117 Applications.ToObservableChangeSet() 118 .Filter(Filter) 119 .Sort(GetComparer()) 120 .Bind(out _appsObservableList).AsObservableList(); 121 122 _rendererWaitEvent = new AutoResetEvent(false); 123 124 if (Program.PreviewerDetached) 125 { 126 LoadConfigurableHotKeys(); 127 128 Volume = ConfigurationState.Instance.System.AudioVolume; 129 } 130 } 131 132 public void Initialize( 133 ContentManager contentManager, 134 IStorageProvider storageProvider, 135 ApplicationLibrary applicationLibrary, 136 VirtualFileSystem virtualFileSystem, 137 AccountManager accountManager, 138 InputManager inputManager, 139 UserChannelPersistence userChannelPersistence, 140 LibHacHorizonManager libHacHorizonManager, 141 IHostUIHandler uiHandler, 142 Action<bool> showLoading, 143 Action<bool> switchToGameControl, 144 Action<Control> setMainContent, 145 TopLevel topLevel) 146 { 147 ContentManager = contentManager; 148 StorageProvider = storageProvider; 149 ApplicationLibrary = applicationLibrary; 150 VirtualFileSystem = virtualFileSystem; 151 AccountManager = accountManager; 152 InputManager = inputManager; 153 UserChannelPersistence = userChannelPersistence; 154 LibHacHorizonManager = libHacHorizonManager; 155 UiHandler = uiHandler; 156 157 ShowLoading = showLoading; 158 SwitchToGameControl = switchToGameControl; 159 SetMainContent = setMainContent; 160 TopLevel = topLevel; 161 } 162 163 #region Properties 164 165 public string SearchText 166 { 167 get => _searchText; 168 set 169 { 170 _searchText = value; 171 172 _searchTimer?.Dispose(); 173 174 _searchTimer = new Timer(TimerCallback, null, 1000, 0); 175 } 176 } 177 178 private void TimerCallback(object obj) 179 { 180 RefreshView(); 181 182 _searchTimer.Dispose(); 183 _searchTimer = null; 184 } 185 186 public bool CanUpdate 187 { 188 get => _canUpdate && EnableNonGameRunningControls && Updater.CanUpdate(false); 189 set 190 { 191 _canUpdate = value; 192 OnPropertyChanged(); 193 } 194 } 195 196 public Cursor Cursor 197 { 198 get => _cursor; 199 set 200 { 201 _cursor = value; 202 OnPropertyChanged(); 203 } 204 } 205 206 public ReadOnlyObservableCollection<ApplicationData> AppsObservableList 207 { 208 get => _appsObservableList; 209 set 210 { 211 _appsObservableList = value; 212 213 OnPropertyChanged(); 214 } 215 } 216 217 public bool IsPaused 218 { 219 get => _isPaused; 220 set 221 { 222 _isPaused = value; 223 224 OnPropertyChanged(); 225 } 226 } 227 228 public long LastFullscreenToggle 229 { 230 get => _lastFullscreenToggle; 231 set 232 { 233 _lastFullscreenToggle = value; 234 235 OnPropertyChanged(); 236 } 237 } 238 239 public bool StatusBarVisible 240 { 241 get => _statusBarVisible && EnableNonGameRunningControls; 242 set 243 { 244 _statusBarVisible = value; 245 246 OnPropertyChanged(); 247 } 248 } 249 250 public bool EnableNonGameRunningControls => !IsGameRunning; 251 252 public bool ShowFirmwareStatus => !ShowLoadProgress; 253 254 public bool IsGameRunning 255 { 256 get => _isGameRunning; 257 set 258 { 259 _isGameRunning = value; 260 261 if (!value) 262 { 263 ShowMenuAndStatusBar = false; 264 } 265 266 OnPropertyChanged(); 267 OnPropertyChanged(nameof(EnableNonGameRunningControls)); 268 OnPropertyChanged(nameof(IsAppletMenuActive)); 269 OnPropertyChanged(nameof(StatusBarVisible)); 270 OnPropertyChanged(nameof(ShowFirmwareStatus)); 271 } 272 } 273 274 public bool IsAmiiboRequested 275 { 276 get => _isAmiiboRequested && _isGameRunning; 277 set 278 { 279 _isAmiiboRequested = value; 280 281 OnPropertyChanged(); 282 } 283 } 284 285 public bool ShowLoadProgress 286 { 287 get => _showLoadProgress; 288 set 289 { 290 _showLoadProgress = value; 291 292 OnPropertyChanged(); 293 OnPropertyChanged(nameof(ShowFirmwareStatus)); 294 } 295 } 296 297 public string GameStatusText 298 { 299 get => _gameStatusText; 300 set 301 { 302 _gameStatusText = value; 303 304 OnPropertyChanged(); 305 } 306 } 307 308 public bool IsFullScreen 309 { 310 get => _isFullScreen; 311 set 312 { 313 _isFullScreen = value; 314 315 OnPropertyChanged(); 316 } 317 } 318 319 public bool IsSubMenuOpen 320 { 321 get => _isSubMenuOpen; 322 set 323 { 324 _isSubMenuOpen = value; 325 326 OnPropertyChanged(); 327 } 328 } 329 330 public bool ShowAll 331 { 332 get => _showAll; 333 set 334 { 335 _showAll = value; 336 337 OnPropertyChanged(); 338 } 339 } 340 341 public string LastScannedAmiiboId 342 { 343 get => _lastScannedAmiiboId; 344 set 345 { 346 _lastScannedAmiiboId = value; 347 348 OnPropertyChanged(); 349 } 350 } 351 352 public ApplicationData SelectedApplication 353 { 354 get 355 { 356 return Glyph switch 357 { 358 Glyph.List => ListSelectedApplication, 359 Glyph.Grid => GridSelectedApplication, 360 _ => null, 361 }; 362 } 363 } 364 365 public bool OpenUserSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.UserAccountSaveDataSize > 0; 366 367 public bool OpenDeviceSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.DeviceSaveDataSize > 0; 368 369 public bool OpenBcatSaveDirectoryEnabled => !SelectedApplication.ControlHolder.ByteSpan.IsZeros() && SelectedApplication.ControlHolder.Value.BcatDeliveryCacheStorageSize > 0; 370 371 public bool CreateShortcutEnabled => !ReleaseInformation.IsFlatHubBuild; 372 373 public string LoadHeading 374 { 375 get => _loadHeading; 376 set 377 { 378 _loadHeading = value; 379 380 OnPropertyChanged(); 381 } 382 } 383 384 public string CacheLoadStatus 385 { 386 get => _cacheLoadStatus; 387 set 388 { 389 _cacheLoadStatus = value; 390 391 OnPropertyChanged(); 392 } 393 } 394 395 public Brush ProgressBarBackgroundColor 396 { 397 get => _progressBarBackgroundColor; 398 set 399 { 400 _progressBarBackgroundColor = value; 401 402 OnPropertyChanged(); 403 } 404 } 405 406 public Brush ProgressBarForegroundColor 407 { 408 get => _progressBarForegroundColor; 409 set 410 { 411 _progressBarForegroundColor = value; 412 413 OnPropertyChanged(); 414 } 415 } 416 417 public Brush VsyncColor 418 { 419 get => _vsyncColor; 420 set 421 { 422 _vsyncColor = value; 423 424 OnPropertyChanged(); 425 } 426 } 427 428 public byte[] SelectedIcon 429 { 430 get => _selectedIcon; 431 set 432 { 433 _selectedIcon = value; 434 435 OnPropertyChanged(); 436 } 437 } 438 439 public int ProgressMaximum 440 { 441 get => _progressMaximum; 442 set 443 { 444 _progressMaximum = value; 445 446 OnPropertyChanged(); 447 } 448 } 449 450 public int ProgressValue 451 { 452 get => _progressValue; 453 set 454 { 455 _progressValue = value; 456 457 OnPropertyChanged(); 458 } 459 } 460 461 public int StatusBarProgressMaximum 462 { 463 get => _statusBarProgressMaximum; 464 set 465 { 466 _statusBarProgressMaximum = value; 467 468 OnPropertyChanged(); 469 } 470 } 471 472 public int StatusBarProgressValue 473 { 474 get => _statusBarProgressValue; 475 set 476 { 477 _statusBarProgressValue = value; 478 479 OnPropertyChanged(); 480 } 481 } 482 483 public string FifoStatusText 484 { 485 get => _fifoStatusText; 486 set 487 { 488 _fifoStatusText = value; 489 490 OnPropertyChanged(); 491 } 492 } 493 494 public string GpuNameText 495 { 496 get => _gpuStatusText; 497 set 498 { 499 _gpuStatusText = value; 500 501 OnPropertyChanged(); 502 } 503 } 504 505 public string BackendText 506 { 507 get => _backendText; 508 set 509 { 510 _backendText = value; 511 512 OnPropertyChanged(); 513 } 514 } 515 516 public string DockedStatusText 517 { 518 get => _dockedStatusText; 519 set 520 { 521 _dockedStatusText = value; 522 523 OnPropertyChanged(); 524 } 525 } 526 527 public string AspectRatioStatusText 528 { 529 get => _aspectStatusText; 530 set 531 { 532 _aspectStatusText = value; 533 534 OnPropertyChanged(); 535 } 536 } 537 538 public string VolumeStatusText 539 { 540 get => _volumeStatusText; 541 set 542 { 543 _volumeStatusText = value; 544 545 OnPropertyChanged(); 546 } 547 } 548 549 public bool VolumeMuted => _volume == 0; 550 551 public float Volume 552 { 553 get => _volume; 554 set 555 { 556 _volume = value; 557 558 if (_isGameRunning) 559 { 560 AppHost.Device.SetVolume(_volume); 561 } 562 563 OnPropertyChanged(nameof(VolumeStatusText)); 564 OnPropertyChanged(nameof(VolumeMuted)); 565 OnPropertyChanged(); 566 } 567 } 568 569 public float VolumeBeforeMute 570 { 571 get => _volumeBeforeMute; 572 set 573 { 574 _volumeBeforeMute = value; 575 576 OnPropertyChanged(); 577 } 578 } 579 580 public bool ShowStatusSeparator 581 { 582 get => _showStatusSeparator; 583 set 584 { 585 _showStatusSeparator = value; 586 587 OnPropertyChanged(); 588 } 589 } 590 591 public bool ShowMenuAndStatusBar 592 { 593 get => _showMenuAndStatusBar; 594 set 595 { 596 _showMenuAndStatusBar = value; 597 598 OnPropertyChanged(); 599 } 600 } 601 602 public bool IsLoadingIndeterminate 603 { 604 get => _isLoadingIndeterminate; 605 set 606 { 607 _isLoadingIndeterminate = value; 608 609 OnPropertyChanged(); 610 } 611 } 612 613 public bool IsActive 614 { 615 get => _isActive; 616 set 617 { 618 _isActive = value; 619 620 OnPropertyChanged(); 621 } 622 } 623 624 625 public bool ShowContent 626 { 627 get => _showContent; 628 set 629 { 630 _showContent = value; 631 632 OnPropertyChanged(); 633 } 634 } 635 636 public bool IsAppletMenuActive 637 { 638 get => _isAppletMenuActive && EnableNonGameRunningControls; 639 set 640 { 641 _isAppletMenuActive = value; 642 643 OnPropertyChanged(); 644 } 645 } 646 647 public WindowState WindowState 648 { 649 get => _windowState; 650 internal set 651 { 652 _windowState = value; 653 654 OnPropertyChanged(); 655 } 656 } 657 658 public double WindowWidth 659 { 660 get => _windowWidth; 661 set 662 { 663 _windowWidth = value; 664 665 OnPropertyChanged(); 666 } 667 } 668 669 public double WindowHeight 670 { 671 get => _windowHeight; 672 set 673 { 674 _windowHeight = value; 675 676 OnPropertyChanged(); 677 } 678 } 679 680 public bool IsGrid => Glyph == Glyph.Grid; 681 public bool IsList => Glyph == Glyph.List; 682 683 internal void Sort(bool isAscending) 684 { 685 IsAscending = isAscending; 686 687 RefreshView(); 688 } 689 690 internal void Sort(ApplicationSort sort) 691 { 692 SortMode = sort; 693 694 RefreshView(); 695 } 696 697 public bool StartGamesInFullscreen 698 { 699 get => ConfigurationState.Instance.UI.StartFullscreen; 700 set 701 { 702 ConfigurationState.Instance.UI.StartFullscreen.Value = value; 703 704 ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 705 706 OnPropertyChanged(); 707 } 708 } 709 710 public bool ShowConsole 711 { 712 get => ConfigurationState.Instance.UI.ShowConsole; 713 set 714 { 715 ConfigurationState.Instance.UI.ShowConsole.Value = value; 716 717 ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 718 719 OnPropertyChanged(); 720 } 721 } 722 723 public string Title 724 { 725 get => _title; 726 set 727 { 728 _title = value; 729 730 OnPropertyChanged(); 731 } 732 } 733 734 public bool ShowConsoleVisible 735 { 736 get => ConsoleHelper.SetConsoleWindowStateSupported; 737 } 738 739 public bool ManageFileTypesVisible 740 { 741 get => FileAssociationHelper.IsTypeAssociationSupported; 742 } 743 744 public ObservableCollection<ApplicationData> Applications 745 { 746 get => _applications; 747 set 748 { 749 _applications = value; 750 751 OnPropertyChanged(); 752 } 753 } 754 755 public Glyph Glyph 756 { 757 get => (Glyph)ConfigurationState.Instance.UI.GameListViewMode.Value; 758 set 759 { 760 ConfigurationState.Instance.UI.GameListViewMode.Value = (int)value; 761 762 OnPropertyChanged(); 763 OnPropertyChanged(nameof(IsGrid)); 764 OnPropertyChanged(nameof(IsList)); 765 766 ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 767 } 768 } 769 770 public bool ShowNames 771 { 772 get => ConfigurationState.Instance.UI.ShowNames && ConfigurationState.Instance.UI.GridSize > 1; set 773 { 774 ConfigurationState.Instance.UI.ShowNames.Value = value; 775 776 OnPropertyChanged(); 777 OnPropertyChanged(nameof(GridSizeScale)); 778 OnPropertyChanged(nameof(GridItemSelectorSize)); 779 780 ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 781 } 782 } 783 784 internal ApplicationSort SortMode 785 { 786 get => (ApplicationSort)ConfigurationState.Instance.UI.ApplicationSort.Value; 787 private set 788 { 789 ConfigurationState.Instance.UI.ApplicationSort.Value = (int)value; 790 791 OnPropertyChanged(); 792 OnPropertyChanged(nameof(SortName)); 793 794 ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 795 } 796 } 797 798 public int ListItemSelectorSize 799 { 800 get 801 { 802 return ConfigurationState.Instance.UI.GridSize.Value switch 803 { 804 1 => 78, 805 2 => 100, 806 3 => 120, 807 4 => 140, 808 _ => 16, 809 }; 810 } 811 } 812 813 public int GridItemSelectorSize 814 { 815 get 816 { 817 return ConfigurationState.Instance.UI.GridSize.Value switch 818 { 819 1 => 120, 820 2 => ShowNames ? 210 : 150, 821 3 => ShowNames ? 240 : 180, 822 4 => ShowNames ? 280 : 220, 823 _ => 16, 824 }; 825 } 826 } 827 828 public int GridSizeScale 829 { 830 get => ConfigurationState.Instance.UI.GridSize; 831 set 832 { 833 ConfigurationState.Instance.UI.GridSize.Value = value; 834 835 if (value < 2) 836 { 837 ShowNames = false; 838 } 839 840 OnPropertyChanged(); 841 OnPropertyChanged(nameof(IsGridSmall)); 842 OnPropertyChanged(nameof(IsGridMedium)); 843 OnPropertyChanged(nameof(IsGridLarge)); 844 OnPropertyChanged(nameof(IsGridHuge)); 845 OnPropertyChanged(nameof(ListItemSelectorSize)); 846 OnPropertyChanged(nameof(GridItemSelectorSize)); 847 OnPropertyChanged(nameof(ShowNames)); 848 849 ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 850 } 851 } 852 853 public string SortName 854 { 855 get 856 { 857 return SortMode switch 858 { 859 ApplicationSort.Title => LocaleManager.Instance[LocaleKeys.GameListHeaderApplication], 860 ApplicationSort.Developer => LocaleManager.Instance[LocaleKeys.GameListHeaderDeveloper], 861 ApplicationSort.LastPlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderLastPlayed], 862 ApplicationSort.TotalTimePlayed => LocaleManager.Instance[LocaleKeys.GameListHeaderTimePlayed], 863 ApplicationSort.FileType => LocaleManager.Instance[LocaleKeys.GameListHeaderFileExtension], 864 ApplicationSort.FileSize => LocaleManager.Instance[LocaleKeys.GameListHeaderFileSize], 865 ApplicationSort.Path => LocaleManager.Instance[LocaleKeys.GameListHeaderPath], 866 ApplicationSort.Favorite => LocaleManager.Instance[LocaleKeys.CommonFavorite], 867 _ => string.Empty, 868 }; 869 } 870 } 871 872 public bool IsAscending 873 { 874 get => ConfigurationState.Instance.UI.IsAscendingOrder; 875 private set 876 { 877 ConfigurationState.Instance.UI.IsAscendingOrder.Value = value; 878 879 OnPropertyChanged(); 880 OnPropertyChanged(nameof(SortMode)); 881 OnPropertyChanged(nameof(SortName)); 882 883 ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 884 } 885 } 886 887 public KeyGesture ShowUiKey 888 { 889 get => KeyGesture.Parse(_showUiKey); 890 set 891 { 892 _showUiKey = value.ToString(); 893 894 OnPropertyChanged(); 895 } 896 } 897 898 public KeyGesture ScreenshotKey 899 { 900 get => KeyGesture.Parse(_screenshotKey); 901 set 902 { 903 _screenshotKey = value.ToString(); 904 905 OnPropertyChanged(); 906 } 907 } 908 909 public KeyGesture PauseKey 910 { 911 get => KeyGesture.Parse(_pauseKey); set 912 { 913 _pauseKey = value.ToString(); 914 915 OnPropertyChanged(); 916 } 917 } 918 919 public ContentManager ContentManager { get; private set; } 920 public IStorageProvider StorageProvider { get; private set; } 921 public ApplicationLibrary ApplicationLibrary { get; private set; } 922 public VirtualFileSystem VirtualFileSystem { get; private set; } 923 public AccountManager AccountManager { get; private set; } 924 public InputManager InputManager { get; private set; } 925 public UserChannelPersistence UserChannelPersistence { get; private set; } 926 public Action<bool> ShowLoading { get; private set; } 927 public Action<bool> SwitchToGameControl { get; private set; } 928 public Action<Control> SetMainContent { get; private set; } 929 public TopLevel TopLevel { get; private set; } 930 public RendererHost RendererHostControl { get; private set; } 931 public bool IsClosing { get; set; } 932 public LibHacHorizonManager LibHacHorizonManager { get; internal set; } 933 public IHostUIHandler UiHandler { get; internal set; } 934 public bool IsSortedByFavorite => SortMode == ApplicationSort.Favorite; 935 public bool IsSortedByTitle => SortMode == ApplicationSort.Title; 936 public bool IsSortedByDeveloper => SortMode == ApplicationSort.Developer; 937 public bool IsSortedByLastPlayed => SortMode == ApplicationSort.LastPlayed; 938 public bool IsSortedByTimePlayed => SortMode == ApplicationSort.TotalTimePlayed; 939 public bool IsSortedByType => SortMode == ApplicationSort.FileType; 940 public bool IsSortedBySize => SortMode == ApplicationSort.FileSize; 941 public bool IsSortedByPath => SortMode == ApplicationSort.Path; 942 public bool IsGridSmall => ConfigurationState.Instance.UI.GridSize == 1; 943 public bool IsGridMedium => ConfigurationState.Instance.UI.GridSize == 2; 944 public bool IsGridLarge => ConfigurationState.Instance.UI.GridSize == 3; 945 public bool IsGridHuge => ConfigurationState.Instance.UI.GridSize == 4; 946 947 #endregion 948 949 #region PrivateMethods 950 951 private IComparer<ApplicationData> GetComparer() 952 { 953 return SortMode switch 954 { 955 #pragma warning disable IDE0055 // Disable formatting 956 ApplicationSort.Title => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Name) 957 : SortExpressionComparer<ApplicationData>.Descending(app => app.Name), 958 ApplicationSort.Developer => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Developer) 959 : SortExpressionComparer<ApplicationData>.Descending(app => app.Developer), 960 ApplicationSort.LastPlayed => new LastPlayedSortComparer(IsAscending), 961 ApplicationSort.TotalTimePlayed => new TimePlayedSortComparer(IsAscending), 962 ApplicationSort.FileType => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileExtension) 963 : SortExpressionComparer<ApplicationData>.Descending(app => app.FileExtension), 964 ApplicationSort.FileSize => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.FileSize) 965 : SortExpressionComparer<ApplicationData>.Descending(app => app.FileSize), 966 ApplicationSort.Path => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => app.Path) 967 : SortExpressionComparer<ApplicationData>.Descending(app => app.Path), 968 ApplicationSort.Favorite => IsAscending ? SortExpressionComparer<ApplicationData>.Ascending(app => new AppListFavoriteComparable(app)) 969 : SortExpressionComparer<ApplicationData>.Descending(app => new AppListFavoriteComparable(app)), 970 _ => null, 971 #pragma warning restore IDE0055 972 }; 973 } 974 975 public void RefreshView() 976 { 977 RefreshGrid(); 978 } 979 980 private void RefreshGrid() 981 { 982 Applications.ToObservableChangeSet() 983 .Filter(Filter) 984 .Sort(GetComparer()) 985 .Bind(out _appsObservableList).AsObservableList(); 986 987 OnPropertyChanged(nameof(AppsObservableList)); 988 } 989 990 private bool Filter(object arg) 991 { 992 if (arg is ApplicationData app) 993 { 994 if (string.IsNullOrWhiteSpace(_searchText)) 995 { 996 return true; 997 } 998 999 CompareInfo compareInfo = CultureInfo.CurrentCulture.CompareInfo; 1000 1001 return compareInfo.IndexOf(app.Name, _searchText, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) >= 0; 1002 } 1003 1004 return false; 1005 } 1006 1007 private async Task HandleFirmwareInstallation(string filename) 1008 { 1009 try 1010 { 1011 SystemVersion firmwareVersion = ContentManager.VerifyFirmwarePackage(filename); 1012 1013 if (firmwareVersion == null) 1014 { 1015 await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareNotFoundErrorMessage, filename)); 1016 1017 return; 1018 } 1019 1020 string dialogTitle = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallTitle, firmwareVersion.VersionString); 1021 string dialogMessage = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallMessage, firmwareVersion.VersionString); 1022 1023 SystemVersion currentVersion = ContentManager.GetCurrentFirmwareVersion(); 1024 if (currentVersion != null) 1025 { 1026 dialogMessage += LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSubMessage, currentVersion.VersionString); 1027 } 1028 1029 dialogMessage += LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallConfirmMessage]; 1030 1031 UserResult result = await ContentDialogHelper.CreateConfirmationDialog( 1032 dialogTitle, 1033 dialogMessage, 1034 LocaleManager.Instance[LocaleKeys.InputDialogYes], 1035 LocaleManager.Instance[LocaleKeys.InputDialogNo], 1036 LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); 1037 1038 UpdateWaitWindow waitingDialog = new(dialogTitle, LocaleManager.Instance[LocaleKeys.DialogFirmwareInstallerFirmwareInstallWaitMessage]); 1039 1040 if (result == UserResult.Yes) 1041 { 1042 Logger.Info?.Print(LogClass.Application, $"Installing firmware {firmwareVersion.VersionString}"); 1043 1044 Thread thread = new(() => 1045 { 1046 Dispatcher.UIThread.InvokeAsync(delegate 1047 { 1048 waitingDialog.Show(); 1049 }); 1050 1051 try 1052 { 1053 ContentManager.InstallFirmware(filename); 1054 1055 Dispatcher.UIThread.InvokeAsync(async delegate 1056 { 1057 waitingDialog.Close(); 1058 1059 string message = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogFirmwareInstallerFirmwareInstallSuccessMessage, firmwareVersion.VersionString); 1060 1061 await ContentDialogHelper.CreateInfoDialog(dialogTitle, message, LocaleManager.Instance[LocaleKeys.InputDialogOk], "", LocaleManager.Instance[LocaleKeys.RyujinxInfo]); 1062 1063 Logger.Info?.Print(LogClass.Application, message); 1064 1065 // Purge Applet Cache. 1066 1067 DirectoryInfo miiEditorCacheFolder = new(Path.Combine(AppDataManager.GamesDirPath, "0100000000001009", "cache")); 1068 1069 if (miiEditorCacheFolder.Exists) 1070 { 1071 miiEditorCacheFolder.Delete(true); 1072 } 1073 }); 1074 } 1075 catch (Exception ex) 1076 { 1077 Dispatcher.UIThread.InvokeAsync(async () => 1078 { 1079 waitingDialog.Close(); 1080 1081 await ContentDialogHelper.CreateErrorDialog(ex.Message); 1082 }); 1083 } 1084 finally 1085 { 1086 RefreshFirmwareStatus(); 1087 } 1088 }) 1089 { 1090 Name = "GUI.FirmwareInstallerThread", 1091 }; 1092 1093 thread.Start(); 1094 } 1095 } 1096 catch (MissingKeyException ex) 1097 { 1098 if (Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) 1099 { 1100 Logger.Error?.Print(LogClass.Application, ex.ToString()); 1101 1102 await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys); 1103 } 1104 } 1105 catch (Exception ex) 1106 { 1107 await ContentDialogHelper.CreateErrorDialog(ex.Message); 1108 } 1109 } 1110 1111 private void ProgressHandler<T>(T state, int current, int total) where T : Enum 1112 { 1113 Dispatcher.UIThread.Post((() => 1114 { 1115 ProgressMaximum = total; 1116 ProgressValue = current; 1117 1118 switch (state) 1119 { 1120 case LoadState ptcState: 1121 CacheLoadStatus = $"{current} / {total}"; 1122 switch (ptcState) 1123 { 1124 case LoadState.Unloaded: 1125 case LoadState.Loading: 1126 LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingPPTC]; 1127 IsLoadingIndeterminate = false; 1128 break; 1129 case LoadState.Loaded: 1130 LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name); 1131 IsLoadingIndeterminate = true; 1132 CacheLoadStatus = ""; 1133 break; 1134 } 1135 break; 1136 case ShaderCacheLoadingState shaderCacheState: 1137 CacheLoadStatus = $"{current} / {total}"; 1138 switch (shaderCacheState) 1139 { 1140 case ShaderCacheLoadingState.Start: 1141 case ShaderCacheLoadingState.Loading: 1142 LoadHeading = LocaleManager.Instance[LocaleKeys.CompilingShaders]; 1143 IsLoadingIndeterminate = false; 1144 break; 1145 case ShaderCacheLoadingState.Packaging: 1146 LoadHeading = LocaleManager.Instance[LocaleKeys.PackagingShaders]; 1147 IsLoadingIndeterminate = false; 1148 break; 1149 case ShaderCacheLoadingState.Loaded: 1150 LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, _currentApplicationData.Name); 1151 IsLoadingIndeterminate = true; 1152 CacheLoadStatus = ""; 1153 break; 1154 } 1155 break; 1156 default: 1157 throw new ArgumentException($"Unknown Progress Handler type {typeof(T)}"); 1158 } 1159 })); 1160 } 1161 1162 private void PrepareLoadScreen() 1163 { 1164 using MemoryStream stream = new(SelectedIcon); 1165 using var gameIconBmp = SKBitmap.Decode(stream); 1166 1167 var dominantColor = IconColorPicker.GetFilteredColor(gameIconBmp); 1168 1169 const float ColorMultiple = 0.5f; 1170 1171 Color progressFgColor = Color.FromRgb(dominantColor.Red, dominantColor.Green, dominantColor.Blue); 1172 Color progressBgColor = Color.FromRgb( 1173 (byte)(dominantColor.Red * ColorMultiple), 1174 (byte)(dominantColor.Green * ColorMultiple), 1175 (byte)(dominantColor.Blue * ColorMultiple)); 1176 1177 ProgressBarForegroundColor = new SolidColorBrush(progressFgColor); 1178 ProgressBarBackgroundColor = new SolidColorBrush(progressBgColor); 1179 } 1180 1181 private void InitializeGame() 1182 { 1183 RendererHostControl.WindowCreated += RendererHost_Created; 1184 1185 AppHost.StatusInitEvent += Init_StatusBar; 1186 AppHost.StatusUpdatedEvent += Update_StatusBar; 1187 AppHost.AppExit += AppHost_AppExit; 1188 1189 _rendererWaitEvent.WaitOne(); 1190 1191 AppHost?.Start(); 1192 1193 AppHost?.DisposeContext(); 1194 } 1195 1196 private async Task HandleRelaunch() 1197 { 1198 if (UserChannelPersistence.PreviousIndex != -1 && UserChannelPersistence.ShouldRestart) 1199 { 1200 UserChannelPersistence.ShouldRestart = false; 1201 1202 await LoadApplication(_currentApplicationData); 1203 } 1204 else 1205 { 1206 // Otherwise, clear state. 1207 UserChannelPersistence = new UserChannelPersistence(); 1208 _currentApplicationData = null; 1209 } 1210 } 1211 1212 private void Init_StatusBar(object sender, StatusInitEventArgs args) 1213 { 1214 if (ShowMenuAndStatusBar && !ShowLoadProgress) 1215 { 1216 Dispatcher.UIThread.InvokeAsync(() => 1217 { 1218 GpuNameText = args.GpuName; 1219 BackendText = args.GpuBackend; 1220 }); 1221 } 1222 } 1223 1224 private void Update_StatusBar(object sender, StatusUpdatedEventArgs args) 1225 { 1226 if (ShowMenuAndStatusBar && !ShowLoadProgress) 1227 { 1228 Dispatcher.UIThread.InvokeAsync(() => 1229 { 1230 Application.Current.Styles.TryGetResource(args.VSyncEnabled 1231 ? "VsyncEnabled" 1232 : "VsyncDisabled", 1233 Application.Current.ActualThemeVariant, 1234 out object color); 1235 1236 if (color is not null) 1237 { 1238 VsyncColor = new SolidColorBrush((Color)color); 1239 } 1240 1241 DockedStatusText = args.DockedMode; 1242 AspectRatioStatusText = args.AspectRatio; 1243 GameStatusText = args.GameStatus; 1244 VolumeStatusText = args.VolumeStatus; 1245 FifoStatusText = args.FifoStatus; 1246 1247 ShowStatusSeparator = true; 1248 }); 1249 } 1250 } 1251 1252 private void RendererHost_Created(object sender, EventArgs e) 1253 { 1254 ShowLoading(false); 1255 1256 _rendererWaitEvent.Set(); 1257 } 1258 1259 #endregion 1260 1261 #region PublicMethods 1262 1263 public void SetUiProgressHandlers(Switch emulationContext) 1264 { 1265 if (emulationContext.Processes.ActiveApplication.DiskCacheLoadState != null) 1266 { 1267 emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged -= ProgressHandler; 1268 emulationContext.Processes.ActiveApplication.DiskCacheLoadState.StateChanged += ProgressHandler; 1269 } 1270 1271 emulationContext.Gpu.ShaderCacheStateChanged -= ProgressHandler; 1272 emulationContext.Gpu.ShaderCacheStateChanged += ProgressHandler; 1273 } 1274 1275 public void LoadConfigurableHotKeys() 1276 { 1277 if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.ShowUI, out var showUiKey)) 1278 { 1279 ShowUiKey = new KeyGesture(showUiKey); 1280 } 1281 1282 if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Screenshot, out var screenshotKey)) 1283 { 1284 ScreenshotKey = new KeyGesture(screenshotKey); 1285 } 1286 1287 if (AvaloniaKeyboardMappingHelper.TryGetAvaKey((Key)ConfigurationState.Instance.Hid.Hotkeys.Value.Pause, out var pauseKey)) 1288 { 1289 PauseKey = new KeyGesture(pauseKey); 1290 } 1291 } 1292 1293 public void TakeScreenshot() 1294 { 1295 AppHost.ScreenshotRequested = true; 1296 } 1297 1298 public void HideUi() 1299 { 1300 ShowMenuAndStatusBar = false; 1301 } 1302 1303 public void ToggleStartGamesInFullscreen() 1304 { 1305 StartGamesInFullscreen = !StartGamesInFullscreen; 1306 } 1307 1308 public void ToggleShowConsole() 1309 { 1310 ShowConsole = !ShowConsole; 1311 } 1312 1313 public void SetListMode() 1314 { 1315 Glyph = Glyph.List; 1316 } 1317 1318 public void SetGridMode() 1319 { 1320 Glyph = Glyph.Grid; 1321 } 1322 1323 public void SetAspectRatio(AspectRatio aspectRatio) 1324 { 1325 ConfigurationState.Instance.Graphics.AspectRatio.Value = aspectRatio; 1326 } 1327 1328 public async Task InstallFirmwareFromFile() 1329 { 1330 var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions 1331 { 1332 AllowMultiple = false, 1333 FileTypeFilter = new List<FilePickerFileType> 1334 { 1335 new(LocaleManager.Instance[LocaleKeys.FileDialogAllTypes]) 1336 { 1337 Patterns = new[] { "*.xci", "*.zip" }, 1338 AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci", "public.zip-archive" }, 1339 MimeTypes = new[] { "application/x-nx-xci", "application/zip" }, 1340 }, 1341 new("XCI") 1342 { 1343 Patterns = new[] { "*.xci" }, 1344 AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, 1345 MimeTypes = new[] { "application/x-nx-xci" }, 1346 }, 1347 new("ZIP") 1348 { 1349 Patterns = new[] { "*.zip" }, 1350 AppleUniformTypeIdentifiers = new[] { "public.zip-archive" }, 1351 MimeTypes = new[] { "application/zip" }, 1352 }, 1353 }, 1354 }); 1355 1356 if (result.Count > 0) 1357 { 1358 await HandleFirmwareInstallation(result[0].Path.LocalPath); 1359 } 1360 } 1361 1362 public async Task InstallFirmwareFromFolder() 1363 { 1364 var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions 1365 { 1366 AllowMultiple = false, 1367 }); 1368 1369 if (result.Count > 0) 1370 { 1371 await HandleFirmwareInstallation(result[0].Path.LocalPath); 1372 } 1373 } 1374 1375 public void OpenRyujinxFolder() 1376 { 1377 OpenHelper.OpenFolder(AppDataManager.BaseDirPath); 1378 } 1379 1380 public void OpenLogsFolder() 1381 { 1382 string logPath = AppDataManager.GetOrCreateLogsDir(); 1383 if (!string.IsNullOrEmpty(logPath)) 1384 { 1385 OpenHelper.OpenFolder(logPath); 1386 } 1387 } 1388 1389 public void ToggleDockMode() 1390 { 1391 if (IsGameRunning) 1392 { 1393 ConfigurationState.Instance.System.EnableDockedMode.Value = !ConfigurationState.Instance.System.EnableDockedMode.Value; 1394 } 1395 } 1396 1397 public async Task ExitCurrentState() 1398 { 1399 if (WindowState == WindowState.FullScreen) 1400 { 1401 ToggleFullscreen(); 1402 } 1403 else if (IsGameRunning) 1404 { 1405 await Task.Delay(100); 1406 1407 AppHost?.ShowExitPrompt(); 1408 } 1409 } 1410 1411 public static void ChangeLanguage(object languageCode) 1412 { 1413 LocaleManager.Instance.LoadLanguage((string)languageCode); 1414 1415 if (Program.PreviewerDetached) 1416 { 1417 ConfigurationState.Instance.UI.LanguageCode.Value = (string)languageCode; 1418 ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 1419 } 1420 } 1421 1422 public async Task ManageProfiles() 1423 { 1424 await NavigationDialogHost.Show(AccountManager, ContentManager, VirtualFileSystem, LibHacHorizonManager.RyujinxClient); 1425 } 1426 1427 public void SimulateWakeUpMessage() 1428 { 1429 AppHost.Device.System.SimulateWakeUpMessage(); 1430 } 1431 1432 public async Task OpenFile() 1433 { 1434 var result = await StorageProvider.OpenFilePickerAsync(new FilePickerOpenOptions 1435 { 1436 Title = LocaleManager.Instance[LocaleKeys.OpenFileDialogTitle], 1437 AllowMultiple = false, 1438 FileTypeFilter = new List<FilePickerFileType> 1439 { 1440 new(LocaleManager.Instance[LocaleKeys.AllSupportedFormats]) 1441 { 1442 Patterns = new[] { "*.nsp", "*.xci", "*.nca", "*.nro", "*.nso" }, 1443 AppleUniformTypeIdentifiers = new[] 1444 { 1445 "com.ryujinx.nsp", 1446 "com.ryujinx.xci", 1447 "com.ryujinx.nca", 1448 "com.ryujinx.nro", 1449 "com.ryujinx.nso", 1450 }, 1451 MimeTypes = new[] 1452 { 1453 "application/x-nx-nsp", 1454 "application/x-nx-xci", 1455 "application/x-nx-nca", 1456 "application/x-nx-nro", 1457 "application/x-nx-nso", 1458 }, 1459 }, 1460 new("NSP") 1461 { 1462 Patterns = new[] { "*.nsp" }, 1463 AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nsp" }, 1464 MimeTypes = new[] { "application/x-nx-nsp" }, 1465 }, 1466 new("XCI") 1467 { 1468 Patterns = new[] { "*.xci" }, 1469 AppleUniformTypeIdentifiers = new[] { "com.ryujinx.xci" }, 1470 MimeTypes = new[] { "application/x-nx-xci" }, 1471 }, 1472 new("NCA") 1473 { 1474 Patterns = new[] { "*.nca" }, 1475 AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nca" }, 1476 MimeTypes = new[] { "application/x-nx-nca" }, 1477 }, 1478 new("NRO") 1479 { 1480 Patterns = new[] { "*.nro" }, 1481 AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nro" }, 1482 MimeTypes = new[] { "application/x-nx-nro" }, 1483 }, 1484 new("NSO") 1485 { 1486 Patterns = new[] { "*.nso" }, 1487 AppleUniformTypeIdentifiers = new[] { "com.ryujinx.nso" }, 1488 MimeTypes = new[] { "application/x-nx-nso" }, 1489 }, 1490 }, 1491 }); 1492 1493 if (result.Count > 0) 1494 { 1495 if (ApplicationLibrary.TryGetApplicationsFromFile(result[0].Path.LocalPath, 1496 out List<ApplicationData> applications)) 1497 { 1498 await LoadApplication(applications[0]); 1499 } 1500 else 1501 { 1502 await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.MenuBarFileOpenFromFileError]); 1503 } 1504 } 1505 } 1506 1507 public async Task OpenFolder() 1508 { 1509 var result = await StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions 1510 { 1511 Title = LocaleManager.Instance[LocaleKeys.OpenFolderDialogTitle], 1512 AllowMultiple = false, 1513 }); 1514 1515 if (result.Count > 0) 1516 { 1517 ApplicationData applicationData = new() 1518 { 1519 Name = Path.GetFileNameWithoutExtension(result[0].Path.LocalPath), 1520 Path = result[0].Path.LocalPath, 1521 }; 1522 1523 await LoadApplication(applicationData); 1524 } 1525 } 1526 1527 public async Task LoadApplication(ApplicationData application, bool startFullscreen = false) 1528 { 1529 if (AppHost != null) 1530 { 1531 await ContentDialogHelper.CreateInfoDialog( 1532 LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedMessage], 1533 LocaleManager.Instance[LocaleKeys.DialogLoadAppGameAlreadyLoadedSubMessage], 1534 LocaleManager.Instance[LocaleKeys.InputDialogOk], 1535 "", 1536 LocaleManager.Instance[LocaleKeys.RyujinxInfo]); 1537 1538 return; 1539 } 1540 1541 #if RELEASE 1542 await PerformanceCheck(); 1543 #endif 1544 1545 Logger.RestartTime(); 1546 1547 SelectedIcon ??= ApplicationLibrary.GetApplicationIcon(application.Path, ConfigurationState.Instance.System.Language, application.Id); 1548 1549 PrepareLoadScreen(); 1550 1551 RendererHostControl = new RendererHost(); 1552 1553 AppHost = new AppHost( 1554 RendererHostControl, 1555 InputManager, 1556 application.Path, 1557 application.Id, 1558 VirtualFileSystem, 1559 ContentManager, 1560 AccountManager, 1561 UserChannelPersistence, 1562 this, 1563 TopLevel); 1564 1565 if (!await AppHost.LoadGuestApplication()) 1566 { 1567 AppHost.DisposeContext(); 1568 AppHost = null; 1569 1570 return; 1571 } 1572 1573 CanUpdate = false; 1574 1575 LoadHeading = application.Name; 1576 1577 if (string.IsNullOrWhiteSpace(application.Name)) 1578 { 1579 LoadHeading = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LoadingHeading, AppHost.Device.Processes.ActiveApplication.Name); 1580 application.Name = AppHost.Device.Processes.ActiveApplication.Name; 1581 } 1582 1583 SwitchToRenderer(startFullscreen); 1584 1585 _currentApplicationData = application; 1586 1587 Thread gameThread = new(InitializeGame) { Name = "GUI.WindowThread" }; 1588 gameThread.Start(); 1589 } 1590 1591 public void SwitchToRenderer(bool startFullscreen) 1592 { 1593 Dispatcher.UIThread.Post(() => 1594 { 1595 SwitchToGameControl(startFullscreen); 1596 1597 SetMainContent(RendererHostControl); 1598 1599 RendererHostControl.Focus(); 1600 }); 1601 } 1602 1603 public static void UpdateGameMetadata(string titleId) 1604 { 1605 ApplicationLibrary.LoadAndSaveMetaData(titleId, appMetadata => 1606 { 1607 appMetadata.UpdatePostGame(); 1608 }); 1609 } 1610 1611 public void RefreshFirmwareStatus() 1612 { 1613 SystemVersion version = null; 1614 try 1615 { 1616 version = ContentManager.GetCurrentFirmwareVersion(); 1617 } 1618 catch (Exception) { } 1619 1620 bool hasApplet = false; 1621 1622 if (version != null) 1623 { 1624 LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, version.VersionString); 1625 1626 hasApplet = version.Major > 3; 1627 } 1628 else 1629 { 1630 LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarSystemVersion, "0.0"); 1631 } 1632 1633 IsAppletMenuActive = hasApplet; 1634 } 1635 1636 public void AppHost_AppExit(object sender, EventArgs e) 1637 { 1638 if (IsClosing) 1639 { 1640 return; 1641 } 1642 1643 IsGameRunning = false; 1644 1645 Dispatcher.UIThread.InvokeAsync(async () => 1646 { 1647 ShowMenuAndStatusBar = true; 1648 ShowContent = true; 1649 ShowLoadProgress = false; 1650 IsLoadingIndeterminate = false; 1651 CanUpdate = true; 1652 Cursor = Cursor.Default; 1653 1654 SetMainContent(null); 1655 1656 AppHost = null; 1657 1658 await HandleRelaunch(); 1659 }); 1660 1661 RendererHostControl.WindowCreated -= RendererHost_Created; 1662 RendererHostControl = null; 1663 1664 SelectedIcon = null; 1665 1666 Dispatcher.UIThread.InvokeAsync(() => 1667 { 1668 Title = $"Ryujinx {Program.Version}"; 1669 }); 1670 } 1671 1672 public void ToggleFullscreen() 1673 { 1674 if (Environment.TickCount64 - LastFullscreenToggle < HotKeyPressDelayMs) 1675 { 1676 return; 1677 } 1678 1679 LastFullscreenToggle = Environment.TickCount64; 1680 1681 if (WindowState == WindowState.FullScreen) 1682 { 1683 WindowState = WindowState.Normal; 1684 1685 if (IsGameRunning) 1686 { 1687 ShowMenuAndStatusBar = true; 1688 } 1689 } 1690 else 1691 { 1692 WindowState = WindowState.FullScreen; 1693 1694 if (IsGameRunning) 1695 { 1696 ShowMenuAndStatusBar = false; 1697 } 1698 } 1699 1700 IsFullScreen = WindowState == WindowState.FullScreen; 1701 } 1702 1703 public static void SaveConfig() 1704 { 1705 ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath); 1706 } 1707 1708 public static async Task PerformanceCheck() 1709 { 1710 if (ConfigurationState.Instance.Logger.EnableTrace.Value) 1711 { 1712 string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledMessage]; 1713 string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckLoggingEnabledConfirmMessage]; 1714 1715 UserResult result = await ContentDialogHelper.CreateConfirmationDialog( 1716 mainMessage, 1717 secondaryMessage, 1718 LocaleManager.Instance[LocaleKeys.InputDialogYes], 1719 LocaleManager.Instance[LocaleKeys.InputDialogNo], 1720 LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); 1721 1722 if (result == UserResult.Yes) 1723 { 1724 ConfigurationState.Instance.Logger.EnableTrace.Value = false; 1725 1726 SaveConfig(); 1727 } 1728 } 1729 1730 if (!string.IsNullOrWhiteSpace(ConfigurationState.Instance.Graphics.ShadersDumpPath.Value)) 1731 { 1732 string mainMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledMessage]; 1733 string secondaryMessage = LocaleManager.Instance[LocaleKeys.DialogPerformanceCheckShaderDumpEnabledConfirmMessage]; 1734 1735 UserResult result = await ContentDialogHelper.CreateConfirmationDialog( 1736 mainMessage, 1737 secondaryMessage, 1738 LocaleManager.Instance[LocaleKeys.InputDialogYes], 1739 LocaleManager.Instance[LocaleKeys.InputDialogNo], 1740 LocaleManager.Instance[LocaleKeys.RyujinxConfirm]); 1741 1742 if (result == UserResult.Yes) 1743 { 1744 ConfigurationState.Instance.Graphics.ShadersDumpPath.Value = ""; 1745 1746 SaveConfig(); 1747 } 1748 } 1749 } 1750 #endregion 1751 } 1752 }