MainWindow.axaml.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. using Avalonia;
  2. using Avalonia.Controls;
  3. using Avalonia.Controls.Primitives;
  4. using Avalonia.Interactivity;
  5. using Avalonia.Platform;
  6. using Avalonia.Threading;
  7. using DynamicData;
  8. using FluentAvalonia.UI.Controls;
  9. using FluentAvalonia.UI.Windowing;
  10. using Gommon;
  11. using LibHac.Ns;
  12. using LibHac.Tools.FsSystem;
  13. using Ryujinx.Ava.Common;
  14. using Ryujinx.Ava.Common.Locale;
  15. using Ryujinx.Ava.Input;
  16. using Ryujinx.Ava.UI.Applet;
  17. using Ryujinx.Ava.UI.Helpers;
  18. using Ryujinx.Ava.UI.ViewModels;
  19. using Ryujinx.Ava.Utilities;
  20. using Ryujinx.Ava.Utilities.AppLibrary;
  21. using Ryujinx.Ava.Utilities.Configuration;
  22. using Ryujinx.Ava.Utilities.Configuration.UI;
  23. using Ryujinx.Common;
  24. using Ryujinx.Common.Helper;
  25. using Ryujinx.Common.Logging;
  26. using Ryujinx.Common.UI;
  27. using Ryujinx.Graphics.Gpu;
  28. using Ryujinx.HLE.FileSystem;
  29. using Ryujinx.HLE.HOS;
  30. using Ryujinx.HLE.HOS.Services.Account.Acc;
  31. using Ryujinx.Input.HLE;
  32. using Ryujinx.Input.SDL2;
  33. using System;
  34. using System.Collections.Generic;
  35. using System.Linq;
  36. using System.Reactive.Linq;
  37. using System.Runtime.Versioning;
  38. using System.Threading;
  39. using System.Threading.Tasks;
  40. namespace Ryujinx.Ava.UI.Windows
  41. {
  42. public partial class MainWindow : StyleableAppWindow
  43. {
  44. public MainWindowViewModel ViewModel { get; }
  45. internal readonly AvaHostUIHandler UiHandler;
  46. private bool _isLoading;
  47. private bool _applicationsLoadedOnce;
  48. private UserChannelPersistence _userChannelPersistence;
  49. private static bool _deferLoad;
  50. private static string _launchPath;
  51. private static string _launchApplicationId;
  52. private static bool _startFullscreen;
  53. private IDisposable _appLibraryAppsSubscription;
  54. public VirtualFileSystem VirtualFileSystem { get; private set; }
  55. public ContentManager ContentManager { get; private set; }
  56. public AccountManager AccountManager { get; private set; }
  57. public LibHacHorizonManager LibHacHorizonManager { get; private set; }
  58. public InputManager InputManager { get; private set; }
  59. public SettingsWindow SettingsWindow { get; set; }
  60. public static bool ShowKeyErrorOnLoad { get; set; }
  61. public ApplicationLibrary ApplicationLibrary { get; set; }
  62. // Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
  63. public readonly double TitleBarHeight;
  64. public readonly double StatusBarHeight;
  65. public readonly double MenuBarHeight;
  66. public MainWindow()
  67. {
  68. DataContext = ViewModel = new MainWindowViewModel
  69. {
  70. Window = this
  71. };
  72. InitializeComponent();
  73. Load();
  74. UiHandler = new AvaHostUIHandler(this);
  75. ViewModel.Title = RyujinxApp.FormatTitle();
  76. TitleBar.ExtendsContentIntoTitleBar = !ConfigurationState.Instance.ShowTitleBar;
  77. TitleBar.TitleBarHitTestType = (ConfigurationState.Instance.ShowTitleBar) ? TitleBarHitTestType.Simple : TitleBarHitTestType.Complex;
  78. // NOTE: Height of MenuBar and StatusBar is not usable here, since it would still be 0 at this point.
  79. StatusBarHeight = StatusBarView.StatusBar.MinHeight;
  80. MenuBarHeight = MenuBar.MinHeight;
  81. TitleBar.Height = MenuBarHeight;
  82. // Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
  83. TitleBarHeight = (ConfigurationState.Instance.ShowTitleBar ? TitleBar.Height : 0);
  84. ApplicationList.DataContext = DataContext;
  85. ApplicationGrid.DataContext = DataContext;
  86. SetWindowSizePosition();
  87. if (Program.PreviewerDetached)
  88. {
  89. InputManager = new InputManager(new AvaloniaKeyboardDriver(this), new SDL2GamepadDriver());
  90. _ = this.GetObservable(IsActiveProperty).Subscribe(it => ViewModel.IsActive = it);
  91. this.ScalingChanged += OnScalingChanged;
  92. }
  93. }
  94. /// <summary>
  95. /// Event handler for detecting OS theme change when using "Follow OS theme" option
  96. /// </summary>
  97. private static void OnPlatformColorValuesChanged(object sender, PlatformColorValues e)
  98. {
  99. if (Application.Current is RyujinxApp app)
  100. app.ApplyConfiguredTheme(ConfigurationState.Instance.UI.BaseStyle);
  101. }
  102. protected override void OnClosed(EventArgs e)
  103. {
  104. base.OnClosed(e);
  105. if (PlatformSettings != null)
  106. {
  107. PlatformSettings.ColorValuesChanged -= OnPlatformColorValuesChanged;
  108. }
  109. }
  110. protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
  111. {
  112. base.OnApplyTemplate(e);
  113. NotificationHelper.SetNotificationManager(this);
  114. Executor.ExecuteBackgroundAsync(ShowIntelMacWarningAsync);
  115. }
  116. private void OnScalingChanged(object sender, EventArgs e)
  117. {
  118. Program.DesktopScaleFactor = this.RenderScaling;
  119. }
  120. private void ApplicationLibrary_ApplicationCountUpdated(object sender, ApplicationCountUpdatedEventArgs e)
  121. {
  122. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, e.NumAppsLoaded, e.NumAppsFound);
  123. Dispatcher.UIThread.Post(() =>
  124. {
  125. ViewModel.StatusBarProgressValue = e.NumAppsLoaded;
  126. ViewModel.StatusBarProgressMaximum = e.NumAppsFound;
  127. if (e.NumAppsFound == 0)
  128. {
  129. StatusBarView.LoadProgressBar.IsVisible = false;
  130. }
  131. if (e.NumAppsLoaded == e.NumAppsFound)
  132. {
  133. StatusBarView.LoadProgressBar.IsVisible = false;
  134. }
  135. });
  136. }
  137. private void ApplicationLibrary_LdnGameDataReceived(LdnGameDataReceivedEventArgs e)
  138. {
  139. Dispatcher.UIThread.Post(() =>
  140. {
  141. List<LdnGameData> ldnGameDataArray = e.LdnData.ToList();
  142. ViewModel.LdnData.Clear();
  143. foreach (ApplicationData application in ViewModel.Applications.Where(it => it.HasControlHolder))
  144. {
  145. ref ApplicationControlProperty controlHolder = ref application.ControlHolder.Value;
  146. ViewModel.LdnData[application.IdString] =
  147. LdnGameData.GetArrayForApp(
  148. ldnGameDataArray,
  149. ref controlHolder
  150. );
  151. UpdateApplicationWithLdnData(application);
  152. }
  153. ViewModel.RefreshView();
  154. });
  155. }
  156. private void UpdateApplicationWithLdnData(ApplicationData application)
  157. {
  158. if (application.HasControlHolder && ViewModel.LdnData.TryGetValue(application.IdString, out LdnGameData.Array ldnGameDatas))
  159. {
  160. application.PlayerCount = ldnGameDatas.PlayerCount;
  161. application.GameCount = ldnGameDatas.GameCount;
  162. }
  163. else
  164. {
  165. application.PlayerCount = 0;
  166. application.GameCount = 0;
  167. }
  168. }
  169. public void Application_Opened(object sender, ApplicationOpenedEventArgs args)
  170. {
  171. if (args.Application != null)
  172. {
  173. ViewModel.SelectedIcon = args.Application.Icon;
  174. ViewModel.LoadApplication(args.Application).Wait();
  175. }
  176. args.Handled = true;
  177. }
  178. internal static void DeferLoadApplication(string launchPathArg, string launchApplicationId, bool startFullscreenArg)
  179. {
  180. _deferLoad = true;
  181. _launchPath = launchPathArg;
  182. _launchApplicationId = launchApplicationId;
  183. _startFullscreen = startFullscreenArg;
  184. }
  185. public void SwitchToGameControl(bool startFullscreen = false)
  186. {
  187. ViewModel.ShowLoadProgress = false;
  188. ViewModel.ShowContent = true;
  189. ViewModel.IsLoadingIndeterminate = false;
  190. if (startFullscreen && ViewModel.WindowState is not WindowState.FullScreen)
  191. {
  192. ViewModel.ToggleFullscreen();
  193. }
  194. }
  195. public void ShowLoading(bool startFullscreen = false)
  196. {
  197. ViewModel.ShowContent = false;
  198. ViewModel.ShowLoadProgress = true;
  199. ViewModel.IsLoadingIndeterminate = true;
  200. if (startFullscreen && ViewModel.WindowState is not WindowState.FullScreen)
  201. {
  202. ViewModel.ToggleFullscreen();
  203. }
  204. }
  205. private void Initialize()
  206. {
  207. _userChannelPersistence = new UserChannelPersistence();
  208. VirtualFileSystem = VirtualFileSystem.CreateInstance();
  209. LibHacHorizonManager = new LibHacHorizonManager();
  210. ContentManager = new ContentManager(VirtualFileSystem);
  211. LibHacHorizonManager.InitializeFsServer(VirtualFileSystem);
  212. LibHacHorizonManager.InitializeArpServer();
  213. LibHacHorizonManager.InitializeBcatServer();
  214. LibHacHorizonManager.InitializeSystemClients();
  215. IntegrityCheckLevel checkLevel = ConfigurationState.Instance.System.EnableFsIntegrityChecks
  216. ? IntegrityCheckLevel.ErrorOnInvalid
  217. : IntegrityCheckLevel.None;
  218. ApplicationLibrary = new ApplicationLibrary(VirtualFileSystem, checkLevel)
  219. {
  220. DesiredLanguage = ConfigurationState.Instance.System.Language,
  221. };
  222. // Save data created before we supported extra data in directory save data will not work properly if
  223. // given empty extra data. Luckily some of that extra data can be created using the data from the
  224. // save data indexer, which should be enough to check access permissions for user saves.
  225. // Every single save data's extra data will be checked and fixed if needed each time the emulator is opened.
  226. // Consider removing this at some point in the future when we don't need to worry about old saves.
  227. VirtualFileSystem.FixExtraData(LibHacHorizonManager.RyujinxClient);
  228. AccountManager = new AccountManager(LibHacHorizonManager.RyujinxClient, CommandLineState.Profile);
  229. VirtualFileSystem.ReloadKeySet();
  230. ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient);
  231. }
  232. [SupportedOSPlatform("linux")]
  233. private static async Task ShowVmMaxMapCountWarning()
  234. {
  235. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary,
  236. LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount);
  237. await ContentDialogHelper.CreateWarningDialog(
  238. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextPrimary],
  239. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary]
  240. );
  241. }
  242. [SupportedOSPlatform("linux")]
  243. private static async Task ShowVmMaxMapCountDialog()
  244. {
  245. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary,
  246. LinuxHelper.RecommendedVmMaxMapCount);
  247. UserResult response = await ContentDialogHelper.ShowTextDialog(
  248. RyujinxApp.FormatTitle(LocaleKeys.LinuxVmMaxMapCountDialogTitle, false),
  249. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary],
  250. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextSecondary],
  251. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonUntilRestart],
  252. LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonPersistent],
  253. LocaleManager.Instance[LocaleKeys.InputDialogNo],
  254. (int)Symbol.Help
  255. );
  256. int rc;
  257. switch (response)
  258. {
  259. case UserResult.Ok:
  260. rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}");
  261. if (rc == 0)
  262. {
  263. Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart.");
  264. }
  265. else
  266. {
  267. Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}");
  268. }
  269. break;
  270. case UserResult.No:
  271. rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}");
  272. if (rc == 0)
  273. {
  274. Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}");
  275. }
  276. else
  277. {
  278. Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}");
  279. }
  280. break;
  281. }
  282. }
  283. private async Task CheckLaunchState()
  284. {
  285. if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
  286. {
  287. Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");
  288. if (LinuxHelper.PkExecPath is not null)
  289. {
  290. await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountDialog);
  291. }
  292. else
  293. {
  294. await Dispatcher.UIThread.InvokeAsync(ShowVmMaxMapCountWarning);
  295. }
  296. }
  297. if (!ShowKeyErrorOnLoad)
  298. {
  299. if (_deferLoad)
  300. {
  301. _deferLoad = false;
  302. if (ApplicationLibrary.TryGetApplicationsFromFile(_launchPath, out List<ApplicationData> applications))
  303. {
  304. ApplicationData applicationData;
  305. if (_launchApplicationId != null)
  306. {
  307. applicationData = applications.FirstOrDefault(application => application.IdString == _launchApplicationId);
  308. if (applicationData != null)
  309. {
  310. await ViewModel.LoadApplication(applicationData, _startFullscreen);
  311. }
  312. else
  313. {
  314. Logger.Error?.Print(LogClass.Application, $"Couldn't find requested application id '{_launchApplicationId}' in '{_launchPath}'.");
  315. await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound));
  316. }
  317. }
  318. else
  319. {
  320. applicationData = applications[0];
  321. await ViewModel.LoadApplication(applicationData, _startFullscreen);
  322. }
  323. }
  324. else
  325. {
  326. Logger.Error?.Print(LogClass.Application, $"Couldn't find any application in '{_launchPath}'.");
  327. await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.ApplicationNotFound));
  328. }
  329. }
  330. }
  331. else
  332. {
  333. ShowKeyErrorOnLoad = false;
  334. await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys));
  335. }
  336. if (!Updater.CanUpdate() || CommandLineState.HideAvailableUpdates)
  337. return;
  338. switch (ConfigurationState.Instance.UpdateCheckerType.Value)
  339. {
  340. case UpdaterType.PromptAtStartup:
  341. await Updater.BeginUpdateAsync()
  342. .Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}"));
  343. break;
  344. case UpdaterType.CheckInBackground:
  345. if ((await Updater.CheckVersionAsync()).TryGet(out (Version Current, Version Incoming) versions))
  346. {
  347. if (versions.Current < versions.Incoming)
  348. Dispatcher.UIThread.Post(() => RyujinxApp.MainWindow.ViewModel.UpdateAvailable = true);
  349. }
  350. break;
  351. }
  352. }
  353. private void Load()
  354. {
  355. StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged;
  356. ApplicationGrid.DataContext = ApplicationList.DataContext = ViewModel;
  357. ApplicationGrid.ApplicationOpened += Application_Opened;
  358. ApplicationList.ApplicationOpened += Application_Opened;
  359. }
  360. private void SetWindowSizePosition()
  361. {
  362. if (!ConfigurationState.Instance.RememberWindowState)
  363. {
  364. // Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
  365. ViewModel.WindowHeight = (720 + StatusBarHeight + MenuBarHeight + TitleBarHeight) * Program.WindowScaleFactor;
  366. ViewModel.WindowWidth = 1280 * Program.WindowScaleFactor;
  367. WindowState = WindowState.Normal;
  368. WindowStartupLocation = WindowStartupLocation.CenterScreen;
  369. return;
  370. }
  371. PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX,
  372. ConfigurationState.Instance.UI.WindowStartup.WindowPositionY);
  373. ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
  374. ViewModel.WindowWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
  375. ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal;
  376. if (Screens.All.Any(screen => screen.Bounds.Contains(savedPoint)))
  377. {
  378. Position = savedPoint;
  379. }
  380. else
  381. {
  382. Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center.");
  383. WindowStartupLocation = WindowStartupLocation.CenterScreen;
  384. }
  385. }
  386. private void SaveWindowSizePosition()
  387. {
  388. ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
  389. // Only save rectangle properties if the window is not in a maximized state.
  390. if (WindowState != WindowState.Maximized)
  391. {
  392. // Since scaling is being applied to the loaded settings from disk (see SetWindowSizePosition() above), scaling should be removed from width/height before saving out to disk
  393. // as well - otherwise anyone not using a 1.0 scale factor their window will increase in size with every subsequent launch of the program when scaling is applied (Nov. 14, 2024)
  394. ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)(Height / Program.WindowScaleFactor);
  395. ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)(Width / Program.WindowScaleFactor);
  396. ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X;
  397. ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y;
  398. }
  399. MainWindowViewModel.SaveConfig();
  400. }
  401. protected override void OnOpened(EventArgs e)
  402. {
  403. base.OnOpened(e);
  404. Initialize();
  405. PlatformSettings!.ColorValuesChanged += OnPlatformColorValuesChanged;
  406. ViewModel.Initialize(
  407. ContentManager,
  408. StorageProvider,
  409. ApplicationLibrary,
  410. VirtualFileSystem,
  411. AccountManager,
  412. InputManager,
  413. _userChannelPersistence,
  414. LibHacHorizonManager,
  415. UiHandler,
  416. ShowLoading,
  417. SwitchToGameControl,
  418. SetMainContent,
  419. this);
  420. ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
  421. _appLibraryAppsSubscription?.Dispose();
  422. _appLibraryAppsSubscription = ApplicationLibrary.Applications
  423. .Connect()
  424. .ObserveOn(SynchronizationContext.Current!)
  425. .Bind(ViewModel.Applications)
  426. .OnItemAdded(UpdateApplicationWithLdnData)
  427. .Subscribe();
  428. ApplicationLibrary.LdnGameDataReceived += ApplicationLibrary_LdnGameDataReceived;
  429. ConfigurationState.Instance.Multiplayer.Mode.Event += (sender, evt) =>
  430. {
  431. _ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn);
  432. };
  433. ConfigurationState.Instance.Multiplayer.LdnServer.Event += (sender, evt) =>
  434. {
  435. _ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn);
  436. };
  437. _ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn);
  438. ViewModel.RefreshFirmwareStatus();
  439. // Load applications if no application was requested by the command line
  440. if (!_deferLoad)
  441. {
  442. LoadApplications();
  443. }
  444. _ = CheckLaunchState();
  445. }
  446. private void SetMainContent(Control content = null)
  447. {
  448. content ??= GameLibrary;
  449. if (MainContent.Content != content)
  450. {
  451. // Load applications while switching to the GameLibrary if we haven't done that yet
  452. if (!_applicationsLoadedOnce && content == GameLibrary)
  453. {
  454. LoadApplications();
  455. }
  456. MainContent.Content = content;
  457. }
  458. }
  459. public static void UpdateGraphicsConfig()
  460. {
  461. #pragma warning disable IDE0055 // Disable formatting
  462. GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1
  463. ? ConfigurationState.Instance.Graphics.ResScaleCustom
  464. : ConfigurationState.Instance.Graphics.ResScale;
  465. GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
  466. GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
  467. GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
  468. GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
  469. GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
  470. #pragma warning restore IDE0055
  471. }
  472. private void VolumeStatus_CheckedChanged(object sender, RoutedEventArgs e)
  473. {
  474. if (ViewModel.IsGameRunning && sender is ToggleSplitButton volumeSplitButton)
  475. {
  476. if (!volumeSplitButton.IsChecked)
  477. {
  478. ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute);
  479. }
  480. else
  481. {
  482. ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume();
  483. ViewModel.AppHost.Device.SetVolume(0);
  484. }
  485. ViewModel.Volume = ViewModel.AppHost.Device.GetVolume();
  486. }
  487. }
  488. protected override void OnClosing(WindowClosingEventArgs e)
  489. {
  490. if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
  491. {
  492. e.Cancel = true;
  493. ConfirmExit();
  494. return;
  495. }
  496. ViewModel.IsClosing = true;
  497. if (ViewModel.AppHost != null)
  498. {
  499. ViewModel.AppHost.AppExit -= ViewModel.AppHost_AppExit;
  500. ViewModel.AppHost.AppExit += (_, _) =>
  501. {
  502. ViewModel.AppHost = null;
  503. Dispatcher.UIThread.Post(() =>
  504. {
  505. MainContent = null;
  506. Close();
  507. });
  508. };
  509. ViewModel.AppHost?.Stop();
  510. e.Cancel = true;
  511. return;
  512. }
  513. if (ConfigurationState.Instance.RememberWindowState)
  514. {
  515. SaveWindowSizePosition();
  516. }
  517. ApplicationLibrary.CancelLoading();
  518. InputManager.Dispose();
  519. _appLibraryAppsSubscription?.Dispose();
  520. Program.Exit();
  521. base.OnClosing(e);
  522. }
  523. private void ConfirmExit()
  524. {
  525. Dispatcher.UIThread.InvokeAsync(async () =>
  526. {
  527. ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
  528. if (ViewModel.IsClosing)
  529. {
  530. Close();
  531. }
  532. });
  533. }
  534. public void LoadApplications()
  535. {
  536. _applicationsLoadedOnce = true;
  537. StatusBarView.LoadProgressBar.IsVisible = true;
  538. ViewModel.StatusBarProgressMaximum = 0;
  539. ViewModel.StatusBarProgressValue = 0;
  540. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
  541. ReloadGameList();
  542. }
  543. public void ToggleFileType(string fileType)
  544. {
  545. switch (fileType)
  546. {
  547. case "NSP":
  548. ConfigurationState.Instance.UI.ShownFileTypes.NSP.Toggle();
  549. break;
  550. case "PFS0":
  551. ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Toggle();
  552. break;
  553. case "XCI":
  554. ConfigurationState.Instance.UI.ShownFileTypes.XCI.Toggle();
  555. break;
  556. case "NCA":
  557. ConfigurationState.Instance.UI.ShownFileTypes.NCA.Toggle();
  558. break;
  559. case "NRO":
  560. ConfigurationState.Instance.UI.ShownFileTypes.NRO.Toggle();
  561. break;
  562. case "NSO":
  563. ConfigurationState.Instance.UI.ShownFileTypes.NSO.Toggle();
  564. break;
  565. default:
  566. throw new ArgumentOutOfRangeException(fileType);
  567. }
  568. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  569. LoadApplications();
  570. }
  571. private void ReloadGameList()
  572. {
  573. if (_isLoading)
  574. {
  575. return;
  576. }
  577. _isLoading = true;
  578. Thread applicationLibraryThread = new(() =>
  579. {
  580. ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
  581. ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
  582. List<string> autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value;
  583. autoloadDirs.ForEach(dir => Logger.Info?.Print(LogClass.Application, $"Auto loading DLC & updates from: {dir}"));
  584. if (autoloadDirs.Count > 0)
  585. {
  586. int updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs, out int updatesRemoved);
  587. int dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs, out int dlcRemoved);
  588. ShowNewContentAddedDialog(dlcLoaded, dlcRemoved, updatesLoaded, updatesRemoved);
  589. }
  590. _isLoading = false;
  591. })
  592. {
  593. Name = "GUI.ApplicationLibraryThread",
  594. IsBackground = true,
  595. };
  596. applicationLibraryThread.Start();
  597. }
  598. private void ShowNewContentAddedDialog(int numDlcAdded, int numDlcRemoved, int numUpdatesAdded, int numUpdatesRemoved)
  599. {
  600. string[] messages =
  601. [
  602. numDlcRemoved > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcRemovedMessage], numDlcRemoved): null,
  603. numDlcAdded > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded): null,
  604. numUpdatesRemoved > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateRemovedMessage], numUpdatesRemoved): null,
  605. numUpdatesAdded > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded) : null
  606. ];
  607. string msg = String.Join("\r\n", messages);
  608. if (String.IsNullOrWhiteSpace(msg))
  609. return;
  610. Dispatcher.UIThread.InvokeAsync(async () =>
  611. {
  612. await ContentDialogHelper.ShowTextDialog(
  613. LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
  614. msg,
  615. string.Empty,
  616. string.Empty,
  617. string.Empty,
  618. LocaleManager.Instance[LocaleKeys.InputDialogOk],
  619. (int)Symbol.Checkmark);
  620. });
  621. }
  622. private static bool _intelMacWarningShown = !RunningPlatform.IsIntelMac;
  623. public static async Task ShowIntelMacWarningAsync()
  624. {
  625. if (_intelMacWarningShown) return;
  626. await Dispatcher.UIThread.InvokeAsync(async () => await ContentDialogHelper.CreateWarningDialog(
  627. "Intel Mac Warning",
  628. "Intel Macs are not supported and will not work properly.\nIf you continue, do not come to our Discord asking for support;\nand do not report bugs on the GitHub. They will be closed."));
  629. _intelMacWarningShown = true;
  630. }
  631. }
  632. }