MainWindow.axaml.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  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. Dispatcher.UIThread.Post(() => RyujinxApp.MainWindow.ViewModel.UpdateAvailable = versions.Current < versions.Incoming);
  348. }
  349. break;
  350. }
  351. }
  352. private void Load()
  353. {
  354. StatusBarView.VolumeStatus.Click += VolumeStatus_CheckedChanged;
  355. ApplicationGrid.DataContext = ApplicationList.DataContext = ViewModel;
  356. ApplicationGrid.ApplicationOpened += Application_Opened;
  357. ApplicationList.ApplicationOpened += Application_Opened;
  358. }
  359. private void SetWindowSizePosition()
  360. {
  361. if (!ConfigurationState.Instance.RememberWindowState)
  362. {
  363. // Correctly size window when 'TitleBar' is enabled (Nov. 14, 2024)
  364. ViewModel.WindowHeight = (720 + StatusBarHeight + MenuBarHeight + TitleBarHeight) * Program.WindowScaleFactor;
  365. ViewModel.WindowWidth = 1280 * Program.WindowScaleFactor;
  366. WindowState = WindowState.Normal;
  367. WindowStartupLocation = WindowStartupLocation.CenterScreen;
  368. return;
  369. }
  370. PixelPoint savedPoint = new(ConfigurationState.Instance.UI.WindowStartup.WindowPositionX,
  371. ConfigurationState.Instance.UI.WindowStartup.WindowPositionY);
  372. ViewModel.WindowHeight = ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight * Program.WindowScaleFactor;
  373. ViewModel.WindowWidth = ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth * Program.WindowScaleFactor;
  374. ViewModel.WindowState = ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value ? WindowState.Maximized : WindowState.Normal;
  375. if (Screens.All.Any(screen => screen.Bounds.Contains(savedPoint)))
  376. {
  377. Position = savedPoint;
  378. }
  379. else
  380. {
  381. Logger.Warning?.Print(LogClass.Application, "Failed to find valid start-up coordinates. Defaulting to primary monitor center.");
  382. WindowStartupLocation = WindowStartupLocation.CenterScreen;
  383. }
  384. }
  385. private void SaveWindowSizePosition()
  386. {
  387. ConfigurationState.Instance.UI.WindowStartup.WindowMaximized.Value = WindowState == WindowState.Maximized;
  388. // Only save rectangle properties if the window is not in a maximized state.
  389. if (WindowState != WindowState.Maximized)
  390. {
  391. // 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
  392. // 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)
  393. ConfigurationState.Instance.UI.WindowStartup.WindowSizeHeight.Value = (int)(Height / Program.WindowScaleFactor);
  394. ConfigurationState.Instance.UI.WindowStartup.WindowSizeWidth.Value = (int)(Width / Program.WindowScaleFactor);
  395. ConfigurationState.Instance.UI.WindowStartup.WindowPositionX.Value = Position.X;
  396. ConfigurationState.Instance.UI.WindowStartup.WindowPositionY.Value = Position.Y;
  397. }
  398. MainWindowViewModel.SaveConfig();
  399. }
  400. protected override void OnOpened(EventArgs e)
  401. {
  402. base.OnOpened(e);
  403. Initialize();
  404. PlatformSettings!.ColorValuesChanged += OnPlatformColorValuesChanged;
  405. ViewModel.Initialize(
  406. ContentManager,
  407. StorageProvider,
  408. ApplicationLibrary,
  409. VirtualFileSystem,
  410. AccountManager,
  411. InputManager,
  412. _userChannelPersistence,
  413. LibHacHorizonManager,
  414. UiHandler,
  415. ShowLoading,
  416. SwitchToGameControl,
  417. SetMainContent,
  418. this);
  419. ApplicationLibrary.ApplicationCountUpdated += ApplicationLibrary_ApplicationCountUpdated;
  420. _appLibraryAppsSubscription?.Dispose();
  421. _appLibraryAppsSubscription = ApplicationLibrary.Applications
  422. .Connect()
  423. .ObserveOn(SynchronizationContext.Current!)
  424. .Bind(ViewModel.Applications)
  425. .OnItemAdded(UpdateApplicationWithLdnData)
  426. .Subscribe();
  427. ApplicationLibrary.LdnGameDataReceived += ApplicationLibrary_LdnGameDataReceived;
  428. ConfigurationState.Instance.Multiplayer.Mode.Event += (sender, evt) =>
  429. {
  430. _ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn);
  431. };
  432. ConfigurationState.Instance.Multiplayer.LdnServer.Event += (sender, evt) =>
  433. {
  434. _ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn);
  435. };
  436. _ = Task.Run(ViewModel.ApplicationLibrary.RefreshLdn);
  437. ViewModel.RefreshFirmwareStatus();
  438. // Load applications if no application was requested by the command line
  439. if (!_deferLoad)
  440. {
  441. LoadApplications();
  442. }
  443. _ = CheckLaunchState();
  444. }
  445. private void SetMainContent(Control content = null)
  446. {
  447. content ??= GameLibrary;
  448. if (MainContent.Content != content)
  449. {
  450. // Load applications while switching to the GameLibrary if we haven't done that yet
  451. if (!_applicationsLoadedOnce && content == GameLibrary)
  452. {
  453. LoadApplications();
  454. }
  455. MainContent.Content = content;
  456. }
  457. }
  458. public static void UpdateGraphicsConfig()
  459. {
  460. #pragma warning disable IDE0055 // Disable formatting
  461. GraphicsConfig.ResScale = ConfigurationState.Instance.Graphics.ResScale == -1
  462. ? ConfigurationState.Instance.Graphics.ResScaleCustom
  463. : ConfigurationState.Instance.Graphics.ResScale;
  464. GraphicsConfig.MaxAnisotropy = ConfigurationState.Instance.Graphics.MaxAnisotropy;
  465. GraphicsConfig.ShadersDumpPath = ConfigurationState.Instance.Graphics.ShadersDumpPath;
  466. GraphicsConfig.EnableShaderCache = ConfigurationState.Instance.Graphics.EnableShaderCache;
  467. GraphicsConfig.EnableTextureRecompression = ConfigurationState.Instance.Graphics.EnableTextureRecompression;
  468. GraphicsConfig.EnableMacroHLE = ConfigurationState.Instance.Graphics.EnableMacroHLE;
  469. #pragma warning restore IDE0055
  470. }
  471. private void VolumeStatus_CheckedChanged(object sender, RoutedEventArgs e)
  472. {
  473. if (ViewModel.IsGameRunning && sender is ToggleSplitButton volumeSplitButton)
  474. {
  475. if (!volumeSplitButton.IsChecked)
  476. {
  477. ViewModel.AppHost.Device.SetVolume(ViewModel.VolumeBeforeMute);
  478. }
  479. else
  480. {
  481. ViewModel.VolumeBeforeMute = ViewModel.AppHost.Device.GetVolume();
  482. ViewModel.AppHost.Device.SetVolume(0);
  483. }
  484. ViewModel.Volume = ViewModel.AppHost.Device.GetVolume();
  485. }
  486. }
  487. protected override void OnClosing(WindowClosingEventArgs e)
  488. {
  489. if (!ViewModel.IsClosing && ViewModel.AppHost != null && ConfigurationState.Instance.ShowConfirmExit)
  490. {
  491. e.Cancel = true;
  492. ConfirmExit();
  493. return;
  494. }
  495. ViewModel.IsClosing = true;
  496. if (ViewModel.AppHost != null)
  497. {
  498. ViewModel.AppHost.AppExit -= ViewModel.AppHost_AppExit;
  499. ViewModel.AppHost.AppExit += (_, _) =>
  500. {
  501. ViewModel.AppHost = null;
  502. Dispatcher.UIThread.Post(() =>
  503. {
  504. MainContent = null;
  505. Close();
  506. });
  507. };
  508. ViewModel.AppHost?.Stop();
  509. e.Cancel = true;
  510. return;
  511. }
  512. if (ConfigurationState.Instance.RememberWindowState)
  513. {
  514. SaveWindowSizePosition();
  515. }
  516. ApplicationLibrary.CancelLoading();
  517. InputManager.Dispose();
  518. _appLibraryAppsSubscription?.Dispose();
  519. Program.Exit();
  520. base.OnClosing(e);
  521. }
  522. private void ConfirmExit()
  523. {
  524. Dispatcher.UIThread.InvokeAsync(async () =>
  525. {
  526. ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
  527. if (ViewModel.IsClosing)
  528. {
  529. Close();
  530. }
  531. });
  532. }
  533. public void LoadApplications()
  534. {
  535. _applicationsLoadedOnce = true;
  536. StatusBarView.LoadProgressBar.IsVisible = true;
  537. ViewModel.StatusBarProgressMaximum = 0;
  538. ViewModel.StatusBarProgressValue = 0;
  539. LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.StatusBarGamesLoaded, 0, 0);
  540. ReloadGameList();
  541. }
  542. public void ToggleFileType(string fileType)
  543. {
  544. switch (fileType)
  545. {
  546. case "NSP":
  547. ConfigurationState.Instance.UI.ShownFileTypes.NSP.Toggle();
  548. break;
  549. case "PFS0":
  550. ConfigurationState.Instance.UI.ShownFileTypes.PFS0.Toggle();
  551. break;
  552. case "XCI":
  553. ConfigurationState.Instance.UI.ShownFileTypes.XCI.Toggle();
  554. break;
  555. case "NCA":
  556. ConfigurationState.Instance.UI.ShownFileTypes.NCA.Toggle();
  557. break;
  558. case "NRO":
  559. ConfigurationState.Instance.UI.ShownFileTypes.NRO.Toggle();
  560. break;
  561. case "NSO":
  562. ConfigurationState.Instance.UI.ShownFileTypes.NSO.Toggle();
  563. break;
  564. default:
  565. throw new ArgumentOutOfRangeException(fileType);
  566. }
  567. ConfigurationState.Instance.ToFileFormat().SaveConfig(Program.ConfigurationPath);
  568. LoadApplications();
  569. }
  570. private void ReloadGameList()
  571. {
  572. if (_isLoading)
  573. {
  574. return;
  575. }
  576. _isLoading = true;
  577. Thread applicationLibraryThread = new(() =>
  578. {
  579. ApplicationLibrary.DesiredLanguage = ConfigurationState.Instance.System.Language;
  580. ApplicationLibrary.LoadApplications(ConfigurationState.Instance.UI.GameDirs);
  581. List<string> autoloadDirs = ConfigurationState.Instance.UI.AutoloadDirs.Value;
  582. autoloadDirs.ForEach(dir => Logger.Info?.Print(LogClass.Application, $"Auto loading DLC & updates from: {dir}"));
  583. if (autoloadDirs.Count > 0)
  584. {
  585. int updatesLoaded = ApplicationLibrary.AutoLoadTitleUpdates(autoloadDirs, out int updatesRemoved);
  586. int dlcLoaded = ApplicationLibrary.AutoLoadDownloadableContents(autoloadDirs, out int dlcRemoved);
  587. ShowNewContentAddedDialog(dlcLoaded, dlcRemoved, updatesLoaded, updatesRemoved);
  588. }
  589. _isLoading = false;
  590. })
  591. {
  592. Name = "GUI.ApplicationLibraryThread",
  593. IsBackground = true,
  594. };
  595. applicationLibraryThread.Start();
  596. }
  597. private void ShowNewContentAddedDialog(int numDlcAdded, int numDlcRemoved, int numUpdatesAdded, int numUpdatesRemoved)
  598. {
  599. string[] messages =
  600. [
  601. numDlcRemoved > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcRemovedMessage], numDlcRemoved): null,
  602. numDlcAdded > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadDlcAddedMessage], numDlcAdded): null,
  603. numUpdatesRemoved > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateRemovedMessage], numUpdatesRemoved): null,
  604. numUpdatesAdded > 0 ? string.Format(LocaleManager.Instance[LocaleKeys.AutoloadUpdateAddedMessage], numUpdatesAdded) : null
  605. ];
  606. string msg = String.Join("\r\n", messages);
  607. if (String.IsNullOrWhiteSpace(msg))
  608. return;
  609. Dispatcher.UIThread.InvokeAsync(async () =>
  610. {
  611. await ContentDialogHelper.ShowTextDialog(
  612. LocaleManager.Instance[LocaleKeys.DialogConfirmationTitle],
  613. msg,
  614. string.Empty,
  615. string.Empty,
  616. string.Empty,
  617. LocaleManager.Instance[LocaleKeys.InputDialogOk],
  618. (int)Symbol.Checkmark);
  619. });
  620. }
  621. private static bool _intelMacWarningShown = !RunningPlatform.IsIntelMac;
  622. public static async Task ShowIntelMacWarningAsync()
  623. {
  624. if (_intelMacWarningShown) return;
  625. await Dispatcher.UIThread.InvokeAsync(async () => await ContentDialogHelper.CreateWarningDialog(
  626. "Intel Mac Warning",
  627. "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."));
  628. _intelMacWarningShown = true;
  629. }
  630. }
  631. }