diff --git a/src/Ryujinx/Assets/locales.json b/src/Ryujinx/Assets/locales.json index ab114a893..ad7b4286f 100644 --- a/src/Ryujinx/Assets/locales.json +++ b/src/Ryujinx/Assets/locales.json @@ -3350,26 +3350,101 @@ { "ID": "SettingsTabGeneralCheckUpdatesOnLaunch", "Translations": { - "ar_SA": "التحقق من وجود تحديثات عند التشغيل", - "de_DE": "Beim Start nach Updates suchen", - "el_GR": "Έλεγχος για Ενημερώσεις στην Εκκίνηση", - "en_US": "Check for Updates on Launch", - "es_ES": "Buscar actualizaciones al iniciar", - "fr_FR": "Vérifier les mises à jour au démarrage", - "he_IL": "בדוק אם קיימים עדכונים בהפעלה", - "it_IT": "Controlla aggiornamenti all'avvio", - "ja_JP": "起動時にアップデートを確認する", - "ko_KR": "시작 시, 업데이트 확인", - "no_NO": "Se etter oppdateringer ved oppstart", - "pl_PL": "Sprawdzaj aktualizacje przy uruchomieniu", - "pt_BR": "Verificar se há atualizações ao iniciar", - "ru_RU": "Проверять наличие обновлений при запуске", - "sv_SE": "Leta efter uppdatering vid uppstart", - "th_TH": "ตรวจหาการอัปเดตเมื่อเปิดโปรแกรม", - "tr_TR": "Her Açılışta Güncellemeleri Denetle", - "uk_UA": "Перевіряти наявність оновлень під час запуску", - "zh_CN": "启动时检查更新", - "zh_TW": "啟動時檢查更新" + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Check for Updates:", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "SettingsTabGeneralCheckUpdatesOnLaunchOff", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Off", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "SettingsTabGeneralCheckUpdatesOnLaunchPromptAtStartup", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Prompt", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" + } + }, + { + "ID": "SettingsTabGeneralCheckUpdatesOnLaunchBackground", + "Translations": { + "ar_SA": "", + "de_DE": "", + "el_GR": "", + "en_US": "Background", + "es_ES": "", + "fr_FR": "", + "he_IL": "", + "it_IT": "", + "ja_JP": "", + "ko_KR": "", + "no_NO": "", + "pl_PL": "", + "pt_BR": "", + "ru_RU": "", + "sv_SE": "", + "th_TH": "", + "tr_TR": "", + "uk_UA": "", + "zh_CN": "", + "zh_TW": "" } }, { diff --git a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs index 0b3f8dcc6..b3df13e7f 100644 --- a/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/SettingsViewModel.cs @@ -13,6 +13,7 @@ using Ryujinx.Ava.UI.Models.Input; using Ryujinx.Ava.UI.Windows; using Ryujinx.Ava.Utilities.Configuration; using Ryujinx.Ava.Utilities.Configuration.System; +using Ryujinx.Ava.Utilities.Configuration.UI; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Multiplayer; using Ryujinx.Common.GraphicsDriver; @@ -121,6 +122,7 @@ namespace Ryujinx.Ava.UI.ViewModels public bool RememberWindowState { get; set; } public bool ShowTitleBar { get; set; } public int HideCursor { get; set; } + public int UpdateCheckerType { get; set; } public bool EnableDockedMode { get; set; } public bool EnableKeyboard { get; set; } public bool EnableMouse { get; set; } @@ -476,6 +478,7 @@ namespace Ryujinx.Ava.UI.ViewModels RememberWindowState = config.RememberWindowState; ShowTitleBar = config.ShowTitleBar; HideCursor = (int)config.HideCursor.Value; + UpdateCheckerType = (int)config.UpdateCheckerType.Value; GameDirectories.Clear(); GameDirectories.AddRange(config.UI.GameDirs.Value); @@ -582,6 +585,7 @@ namespace Ryujinx.Ava.UI.ViewModels config.RememberWindowState.Value = RememberWindowState; config.ShowTitleBar.Value = ShowTitleBar; config.HideCursor.Value = (HideCursorMode)HideCursor; + config.UpdateCheckerType.Value = (UpdaterType)UpdateCheckerType; if (GameDirectoryChanged) { diff --git a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml index 77f9b7bf5..d71a7d795 100644 --- a/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml +++ b/src/Ryujinx/UI/Views/Settings/SettingsUIView.axaml @@ -6,6 +6,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:ext="clr-namespace:Ryujinx.Ava.Common.Markup" xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels" + xmlns:helper="clr-namespace:Ryujinx.Common.Helper;assembly=Ryujinx.Common" mc:Ignorable="d" x:DataType="viewModels:SettingsViewModel"> @@ -30,18 +31,33 @@ ToolTip.Tip="{ext:Locale ToggleDiscordTooltip}" Text="{ext:Locale SettingsTabGeneralEnableDiscordRichPresence}" /> - - - - + + + + + + + + + + + + + + + AddDirButton(GameDirPathBox, ViewModel.GameDirectories, true)); AddAutoloadDirButton.Command = diff --git a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs index 669e338df..735455541 100644 --- a/src/Ryujinx/UI/Windows/MainWindow.axaml.cs +++ b/src/Ryujinx/UI/Windows/MainWindow.axaml.cs @@ -19,6 +19,7 @@ using Ryujinx.Ava.UI.ViewModels; using Ryujinx.Ava.Utilities; using Ryujinx.Ava.Utilities.AppLibrary; using Ryujinx.Ava.Utilities.Configuration; +using Ryujinx.Ava.Utilities.Configuration.UI; using Ryujinx.Common; using Ryujinx.Common.Helper; using Ryujinx.Common.Logging; @@ -400,10 +401,29 @@ namespace Ryujinx.Ava.UI.Windows await Dispatcher.UIThread.InvokeAsync(async () => await UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys)); } - if (ConfigurationState.Instance.CheckUpdatesOnStart && !CommandLineState.HideAvailableUpdates && Updater.CanUpdate()) + if (!Updater.CanUpdate() || CommandLineState.HideAvailableUpdates) + return; + + switch (ConfigurationState.Instance.UpdateCheckerType.Value) { - await Updater.BeginUpdateAsync() - .Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}")); + case UpdaterType.PromptAtStartup: + await Updater.BeginUpdateAsync() + .Catch(task => Logger.Error?.Print(LogClass.Application, $"Updater Error: {task.Exception}")); + break; + case UpdaterType.CheckInBackground: + if ((await Updater.CheckVersionAsync()).TryGet(out (Version Current, Version Incoming) versions)) + { + string newVersionString = ReleaseInformation.IsCanaryBuild + ? $"Canary {versions.Current} -> Canary {versions.Incoming}" + : $"{versions.Current} -> {versions.Incoming}"; + + if (versions.Current < versions.Incoming) + NotificationHelper.ShowInformation( + title: "Update Available", + text: newVersionString, + onClick: () => _ = Updater.BeginUpdateAsync()); + } + break; } } diff --git a/src/Ryujinx/Updater.cs b/src/Ryujinx/Updater.cs index f8b4b2beb..56c4acea2 100644 --- a/src/Ryujinx/Updater.cs +++ b/src/Ryujinx/Updater.cs @@ -43,7 +43,18 @@ namespace Ryujinx.Ava private const int ConnectionCount = 4; private static string _buildVer; - private static string _platformExt; + + private static readonly string _platformExt = + RunningPlatform.IsMacOS + ? "macos_universal.app.tar.gz" + : RunningPlatform.IsWindows + ? "win_x64.zip" + : RunningPlatform.IsX64Linux + ? "linux_x64.tar.gz" + : RunningPlatform.IsArmLinux + ? "linux_arm64.tar.gz" + : throw new PlatformNotSupportedException(); + private static string _buildUrl; private static long _buildSize; private static bool _updateSuccessful; @@ -51,30 +62,8 @@ namespace Ryujinx.Ava private static readonly string[] _windowsDependencyDirs = []; - public static async Task BeginUpdateAsync(bool showVersionUpToDate = false) + public static async Task> CheckVersionAsync(bool showVersionUpToDate = false) { - if (_running) - { - return; - } - - _running = true; - - // Detect current platform - if (OperatingSystem.IsMacOS()) - { - _platformExt = "macos_universal.app.tar.gz"; - } - else if (OperatingSystem.IsWindows()) - { - _platformExt = "win_x64.zip"; - } - else if (OperatingSystem.IsLinux()) - { - string arch = RuntimeInformation.OSArchitecture == Architecture.Arm64 ? "arm64" : "x64"; - _platformExt = $"linux_{arch}.tar.gz"; - } - if (!Version.TryParse(Program.Version, out Version currentVersion)) { Logger.Error?.Print(LogClass.Application, $"Failed to convert the current {RyujinxApp.FullAppName} version!"); @@ -85,7 +74,7 @@ namespace Ryujinx.Ava _running = false; - return; + return default; } Logger.Info?.Print(LogClass.Application, "Checking for updates."); @@ -123,7 +112,7 @@ namespace Ryujinx.Ava _running = false; - return; + return default; } break; @@ -149,7 +138,7 @@ namespace Ryujinx.Ava _running = false; - return; + return default; } } catch (Exception exception) @@ -161,7 +150,7 @@ namespace Ryujinx.Ava _running = false; - return; + return default; } if (!Version.TryParse(_buildVer, out Version newVersion)) @@ -174,9 +163,23 @@ namespace Ryujinx.Ava _running = false; + return (currentVersion, null); + } + + return (currentVersion, newVersion); + } + + public static async Task BeginUpdateAsync(bool showVersionUpToDate = false) + { + if (_running) + { return; } + _running = true; + + (Version currentVersion, Version newVersion) = (await CheckVersionAsync(showVersionUpToDate)).OrDefault(); + if (newVersion <= currentVersion) { if (showVersionUpToDate) diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs index 95364873b..d7235ef27 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationFileFormat.cs @@ -15,7 +15,7 @@ namespace Ryujinx.Ava.Utilities.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 64; + public const int CurrentVersion = 65; /// /// Version of the configuration file format @@ -163,9 +163,14 @@ namespace Ryujinx.Ava.Utilities.Configuration public bool EnableDiscordIntegration { get; set; } /// - /// Checks for updates when Ryujinx starts when enabled + /// DEPRECATED: Checks for updates when Ryujinx starts when enabled /// public bool CheckUpdatesOnStart { get; set; } + + /// + /// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification. + /// + public UpdaterType UpdateCheckerType { get; set; } /// /// Show "Confirm Exit" Dialog diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs index 36f42d8b3..d82e55791 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Migration.cs @@ -45,6 +45,7 @@ namespace Ryujinx.Ava.Utilities.Configuration EnableDiscordIntegration.Value = cff.EnableDiscordIntegration; CheckUpdatesOnStart.Value = cff.CheckUpdatesOnStart; + UpdateCheckerType.Value = cff.UpdateCheckerType; ShowConfirmExit.Value = cff.ShowConfirmExit; RememberWindowState.Value = cff.RememberWindowState; ShowTitleBar.Value = cff.ShowTitleBar; @@ -431,7 +432,8 @@ namespace Ryujinx.Ava.Utilities.Configuration }), (62, static cff => cff.RainbowSpeed = 1f), (63, static cff => cff.MatchSystemTime = false), - (64, static cff => cff.LoggingEnableAvalonia = false) + (64, static cff => cff.LoggingEnableAvalonia = false), + (65, static cff => cff.UpdateCheckerType = cff.CheckUpdatesOnStart ? UpdaterType.PromptAtStartup : UpdaterType.Off) ); } } diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs index 8fbe20e05..b511b32dd 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.Model.cs @@ -1,6 +1,7 @@ using ARMeilleure; using Gommon; using Ryujinx.Ava.Utilities.Configuration.System; +using Ryujinx.Ava.Utilities.Configuration.UI; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; @@ -767,6 +768,11 @@ namespace Ryujinx.Ava.Utilities.Configuration /// Checks for updates when Ryujinx starts when enabled /// public ReactiveObject CheckUpdatesOnStart { get; private set; } + + /// + /// Checks for updates when Ryujinx starts when enabled, either prompting when an update is found or just showing a notification. + /// + public ReactiveObject UpdateCheckerType { get; private set; } /// /// Show "Confirm Exit" Dialog @@ -804,6 +810,7 @@ namespace Ryujinx.Ava.Utilities.Configuration Hacks = new HacksSection(); EnableDiscordIntegration = new ReactiveObject(); CheckUpdatesOnStart = new ReactiveObject(); + UpdateCheckerType = new ReactiveObject(); ShowConfirmExit = new ReactiveObject(); RememberWindowState = new ReactiveObject(); ShowTitleBar = new ReactiveObject(); diff --git a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs index f8fbc90d8..6374b4680 100644 --- a/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs +++ b/src/Ryujinx/Utilities/Configuration/ConfigurationState.cs @@ -175,7 +175,7 @@ namespace Ryujinx.Ava.Utilities.Configuration System.SystemTimeOffset.Value = 0; System.EnableDockedMode.Value = true; EnableDiscordIntegration.Value = true; - CheckUpdatesOnStart.Value = true; + UpdateCheckerType.Value = UpdaterType.PromptAtStartup; ShowConfirmExit.Value = true; RememberWindowState.Value = true; ShowTitleBar.Value = !OperatingSystem.IsWindows(); diff --git a/src/Ryujinx/Utilities/Configuration/UI/UpdaterType.cs b/src/Ryujinx/Utilities/Configuration/UI/UpdaterType.cs new file mode 100644 index 000000000..2ab17a497 --- /dev/null +++ b/src/Ryujinx/Utilities/Configuration/UI/UpdaterType.cs @@ -0,0 +1,13 @@ +using Ryujinx.Common.Utilities; +using System.Text.Json.Serialization; + +namespace Ryujinx.Ava.Utilities.Configuration.UI +{ + [JsonConverter(typeof(TypedStringEnumConverter))] + public enum UpdaterType + { + Off, + PromptAtStartup, + CheckInBackground + } +}