diff --git a/UniGetUI.iss b/UniGetUI.iss index 2d1300e02a..d2abe39a8b 100644 --- a/UniGetUI.iss +++ b/UniGetUI.iss @@ -1,7 +1,7 @@ ; Script generated by the Inno Setup Script Wizard. ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! -#define MyAppVersion "3.1.6-beta2" +#define MyAppVersion "3.1.6-beta3" #define MyAppName "UniGetUI" #define MyAppPublisher "Martí Climent" #define MyAppURL "https://github.com/marticliment/UniGetUI" diff --git a/scripts/BuildNumber b/scripts/BuildNumber index 780fea92d2..efee1f88bb 100644 --- a/scripts/BuildNumber +++ b/scripts/BuildNumber @@ -1 +1 @@ -77 \ No newline at end of file +78 \ No newline at end of file diff --git a/src/SharedAssemblyInfo.cs b/src/SharedAssemblyInfo.cs index 23015c44f2..bd8e2000bf 100644 --- a/src/SharedAssemblyInfo.cs +++ b/src/SharedAssemblyInfo.cs @@ -8,5 +8,5 @@ [assembly: AssemblyCopyright("2025, Martí Climent")] [assembly: AssemblyVersion("3.1.6.0")] [assembly: AssemblyFileVersion("3.1.6.0")] -[assembly: AssemblyInformationalVersion("3.1.6-beta2")] +[assembly: AssemblyInformationalVersion("3.1.6-beta3")] [assembly: SupportedOSPlatform("windows10.0.19041")] diff --git a/src/UniGetUI.Core.Data/CoreData.cs b/src/UniGetUI.Core.Data/CoreData.cs index d784221120..3f9c7239a8 100644 --- a/src/UniGetUI.Core.Data/CoreData.cs +++ b/src/UniGetUI.Core.Data/CoreData.cs @@ -10,8 +10,8 @@ public static class CoreData { private static int? __code_page; public static int CODE_PAGE { get => __code_page ??= GetCodePage(); } - public const string VersionName = "3.1.6-beta2"; // Do not modify this line, use file scripts/apply_versions.py - public const int BuildNumber = 77; // Do not modify this line, use file scripts/apply_versions.py + public const string VersionName = "3.1.6-beta3"; // Do not modify this line, use file scripts/apply_versions.py + public const int BuildNumber = 78; // Do not modify this line, use file scripts/apply_versions.py public const string UserAgentString = $"UniGetUI/{VersionName} (https://marticliment.com/unigetui/; contact@marticliment.com)"; @@ -120,6 +120,7 @@ public static string UniGetUI_DefaultBackupDirectory } public static bool IsDaemon; + public static bool WasDaemon; /// /// The ID of the notification that is used to inform the user that updates are available diff --git a/src/UniGetUI.Core.LanguageEngine/LanguageEngine.cs b/src/UniGetUI.Core.LanguageEngine/LanguageEngine.cs index f589ac15a2..f6e5b2e6cb 100644 --- a/src/UniGetUI.Core.LanguageEngine/LanguageEngine.cs +++ b/src/UniGetUI.Core.LanguageEngine/LanguageEngine.cs @@ -12,6 +12,7 @@ namespace UniGetUI.Core.Language public class LanguageEngine { private Dictionary MainLangDict = []; + public static string SelectedLocale = "??"; [NotNull] public string? Locale { get; private set; } @@ -48,6 +49,7 @@ public void LoadLanguage(string lang) Formatter = new() { Locale = Locale.Replace('_', '-') }; LoadStaticTranslation(); + SelectedLocale = Locale; Logger.Info("Loaded language locale: " + Locale); } diff --git a/src/UniGetUI.Interface.Enums/UniGetUI.Interface.Enums.csproj b/src/UniGetUI.Interface.Enums/UniGetUI.Interface.Enums.csproj index 41861c9c15..ed36b0d299 100644 --- a/src/UniGetUI.Interface.Enums/UniGetUI.Interface.Enums.csproj +++ b/src/UniGetUI.Interface.Enums/UniGetUI.Interface.Enums.csproj @@ -1,11 +1,20 @@ - net8.0 - enable - enable - x64 - false + net8.0-windows10.0.26100.0 + enable + win-x64;win-arm64 + win-$(Platform) + x64 + 10.0.19041.0 + 10.0.26100.53 + 8.0.404 + true + true + Martí Climent and the contributors + Martí Climent + enable + false diff --git a/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs new file mode 100644 index 0000000000..b59c8b0898 --- /dev/null +++ b/src/UniGetUI.Interface.Telemetry/TelemetryHandler.cs @@ -0,0 +1,146 @@ +using System.Net; +using UniGetUI.Core.Data; +using UniGetUI.Core.Language; +using UniGetUI.Core.Logging; +using UniGetUI.Core.SettingsEngine; +using UniGetUI.Core.Tools; +using UniGetUI.PackageEngine; +using UniGetUI.PackageEngine.Interfaces; + +namespace UniGetUI.Interface.Telemetry; + +public static class TelemetryHandler +{ + private const string HOST = "https://marticliment.com/unigetui/statistics"; + + private static string[] SettingsToSend = + { + "DisableAutoUpdateWingetUI", + "EnableUniGetUIBeta", + "DisableSystemTray", + "DisableNotifications", + "DisableAutoCheckforUpdates", + "AutomaticallyUpdatePackages", + "AskToDeleteNewDesktopShortcuts", + "EnablePackageBackup", + "DoCacheAdminRights", + "DoCacheAdminRightsForBatches", + "ForceLegacyBundledWinGet", + "UseSystemChocolatey", + "SP1", // UniGetUI is portable + "SP2" // UniGetUI was started as daemon + }; + + public static async void Initialize() + { + try + { + if (Settings.Get("DisableTelemetry")) return; + await CoreTools.WaitForInternetConnection(); + + if (Settings.GetValue("TelemetryClientToken").Length != 64) + { + Settings.SetValue("TelemetryClientToken", CoreTools.RandomString(64)); + } + + string ID = Settings.GetValue("TelemetryClientToken"); + + int mask = 0x1; + int ManagerMagicValue = 0; + + foreach (var manager in PEInterface.Managers) + { + if(manager.IsEnabled()) ManagerMagicValue |= mask; + mask = mask << 1; + if(manager.IsEnabled() && manager.Status.Found) ManagerMagicValue |= mask; + mask = mask << 1; + + if (mask == 0x1) + throw new OverflowException(); + } + + int SettingsMagicValue = 0; + mask = 0x1; + foreach (var setting in SettingsToSend) + { + bool enabled; + if (setting == "SP1") enabled = File.Exists("ForceUniGetUIPortable"); + else if (setting == "SP2") enabled = CoreData.WasDaemon; + else if (setting.StartsWith("Disable")) enabled = !Settings.Get(setting); + else enabled = Settings.Get(setting); + + if(enabled) SettingsMagicValue |= mask; + mask = mask << 1; + + if (mask == 0x1) + throw new OverflowException(); + } + + var request = new HttpRequestMessage(HttpMethod.Post, $"{HOST}/activity"); + + request.Headers.Add("clientId", ID); + request.Headers.Add("clientVersion", CoreData.VersionName); + request.Headers.Add("activeManagers", ManagerMagicValue.ToString()); + request.Headers.Add("activeSettings", SettingsMagicValue.ToString()); + request.Headers.Add("language", LanguageEngine.SelectedLocale); + + HttpClient _httpClient = new(CoreData.GenericHttpClientParameters); + HttpResponseMessage response = await _httpClient.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + Logger.Debug("[Telemetry] Call to /activity succeeded"); + } + else + { + Logger.Warn($"[Telemetry] Call to /activity failed with error code {response.StatusCode}"); + } + } + catch (Exception ex) + { + Logger.Error("[Telemetry] Hard crash when calling /activity"); + Logger.Error(ex); + } + } + + public static async void PackageInstalled(IPackage package) + { + try + { + if (Settings.Get("DisableTelemetry")) return; + await CoreTools.WaitForInternetConnection(); + + if (Settings.GetValue("TelemetryClientToken").Length != 64) + { + Settings.SetValue("TelemetryClientToken", CoreTools.RandomString(64)); + } + + string ID = Settings.GetValue("TelemetryClientToken"); + + + var request = new HttpRequestMessage(HttpMethod.Post, $"{HOST}/install"); + + request.Headers.Add("clientId", ID); + request.Headers.Add("packageId", package.Id); + request.Headers.Add("managerName", package.Manager.Name); + request.Headers.Add("sourceName", package.Source.Name); + + HttpClient _httpClient = new(CoreData.GenericHttpClientParameters); + HttpResponseMessage response = await _httpClient.SendAsync(request); + + if (response.IsSuccessStatusCode) + { + Logger.Debug("[Telemetry] Call to /install succeeded"); + } + else + { + Logger.Warn($"[Telemetry] Call to /install failed with error code {response.StatusCode}"); + } + } + catch (Exception ex) + { + Logger.Error("[Telemetry] Hard crash when calling /install"); + Logger.Error(ex); + } + } +} diff --git a/src/UniGetUI.Interface.Telemetry/UniGetUI.Interface.Telemetry.csproj b/src/UniGetUI.Interface.Telemetry/UniGetUI.Interface.Telemetry.csproj new file mode 100644 index 0000000000..2aed3bc30e --- /dev/null +++ b/src/UniGetUI.Interface.Telemetry/UniGetUI.Interface.Telemetry.csproj @@ -0,0 +1,36 @@ + + + + net8.0-windows10.0.26100.0 + enable + win-x64;win-arm64 + win-$(Platform) + x64 + 10.0.19041.0 + 10.0.26100.53 + 8.0.404 + true + true + Martí Climent and the contributors + Martí Climent + enable + false + false + UniGetUI.Interface.Telemetry + + + + + + + + + + + + + + + + + diff --git a/src/UniGetUI.sln b/src/UniGetUI.sln index 313b3cffd2..9dd8138557 100644 --- a/src/UniGetUI.sln +++ b/src/UniGetUI.sln @@ -98,6 +98,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniGetUI.PackageEngine.Mana EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniGetUI.PackageEngine.Operations", "UniGetUI.PackageEngine.Operations\UniGetUI.PackageEngine.Operations.csproj", "{727866B8-BBD5-43B9-933A-78199F65429C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniGetUI.Interface.Telemetry", "UniGetUI.Interface.Telemetry\UniGetUI.Interface.Telemetry.csproj", "{3C8BF564-B4B5-44A7-9D8C-102C2F820EAF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -256,6 +258,10 @@ Global {727866B8-BBD5-43B9-933A-78199F65429C}.Debug|x64.Build.0 = Debug|x64 {727866B8-BBD5-43B9-933A-78199F65429C}.Release|x64.ActiveCfg = Release|x64 {727866B8-BBD5-43B9-933A-78199F65429C}.Release|x64.Build.0 = Release|x64 + {3C8BF564-B4B5-44A7-9D8C-102C2F820EAF}.Debug|x64.ActiveCfg = Debug|x64 + {3C8BF564-B4B5-44A7-9D8C-102C2F820EAF}.Debug|x64.Build.0 = Debug|x64 + {3C8BF564-B4B5-44A7-9D8C-102C2F820EAF}.Release|x64.ActiveCfg = Release|x64 + {3C8BF564-B4B5-44A7-9D8C-102C2F820EAF}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -301,6 +307,7 @@ Global {54DA0549-366F-4E70-B5D1-0B8891D0A2A5} = {95168D0B-1B2C-4295-B6D4-D5BAF781B9FA} {E337A71E-3C30-4315-B8F1-57CBC5CF50A6} = {95168D0B-1B2C-4295-B6D4-D5BAF781B9FA} {727866B8-BBD5-43B9-933A-78199F65429C} = {7940E867-EEBA-4AFD-9904-1536F003239C} + {3C8BF564-B4B5-44A7-9D8C-102C2F820EAF} = {8CF74C87-534F-4017-A4ED-F2918025E31A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D044BB14-0B37-47E5-A579-8B30FCBA1F9F} diff --git a/src/UniGetUI/App.xaml.cs b/src/UniGetUI/App.xaml.cs index 0dfb68e30c..e99293e820 100644 --- a/src/UniGetUI/App.xaml.cs +++ b/src/UniGetUI/App.xaml.cs @@ -20,6 +20,7 @@ using Microsoft.Windows.AppLifecycle; using Microsoft.Windows.AppNotifications; using UniGetUI.Controls.OperationWidgets; +using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Interfaces; using UniGetUI.PackageEngine.Managers.PowerShellManager; using AbstractOperation = UniGetUI.PackageOperations.AbstractOperation; @@ -291,6 +292,7 @@ private async Task LoadComponentsAsync() // Load package managers await Task.Run(() => PEInterface.Initialize()); + TelemetryHandler.Initialize(); Logger.Info("LoadComponentsAsync finished executing. All managers loaded. Proceeding to interface."); MainWindow.SwitchToInterface(); diff --git a/src/UniGetUI/AppOperationHelper.cs b/src/UniGetUI/AppOperationHelper.cs index fa49245779..840530f100 100644 --- a/src/UniGetUI/AppOperationHelper.cs +++ b/src/UniGetUI/AppOperationHelper.cs @@ -6,6 +6,7 @@ using UniGetUI.Core.Logging; using UniGetUI.Core.Tools; using UniGetUI.Interface; +using UniGetUI.Interface.Telemetry; using UniGetUI.PackageEngine.Interfaces; using UniGetUI.PackageEngine.Managers.CargoManager; using UniGetUI.PackageEngine.Managers.PowerShellManager; @@ -112,6 +113,7 @@ public static void Remove(AbstractOperation op) var options = await InstallationOptions.FromPackageAsync(package, elevated, interactive, no_integrity); var op = new InstallPackageOperation(package, options, ignoreParallel, req); Add(op); + op.OperationSucceeded += (_, _) => TelemetryHandler.PackageInstalled(package); return op; } diff --git a/src/UniGetUI/EntryPoint.cs b/src/UniGetUI/EntryPoint.cs index 52d390b8bb..d3b236fef0 100644 --- a/src/UniGetUI/EntryPoint.cs +++ b/src/UniGetUI/EntryPoint.cs @@ -15,6 +15,7 @@ private static void Main(string[] args) try { CoreData.IsDaemon = args.Contains("--daemon"); + CoreData.WasDaemon = CoreData.IsDaemon; if (args.Contains("--uninstall-unigetui") || args.Contains("--uninstall-wingetui")) { diff --git a/src/UniGetUI/MainWindow.xaml b/src/UniGetUI/MainWindow.xaml index 29713d3b56..9065d9a0b9 100644 --- a/src/UniGetUI/MainWindow.xaml +++ b/src/UniGetUI/MainWindow.xaml @@ -44,9 +44,23 @@ - - - + + + + + + diff --git a/src/UniGetUI/Pages/DialogPages/DialogHelper_Generic.cs b/src/UniGetUI/Pages/DialogPages/DialogHelper_Generic.cs index e788db88e4..c937c804ac 100644 --- a/src/UniGetUI/Pages/DialogPages/DialogHelper_Generic.cs +++ b/src/UniGetUI/Pages/DialogPages/DialogHelper_Generic.cs @@ -1,6 +1,11 @@ using System.Diagnostics; +using Windows.UI; +using Microsoft.UI; +using Microsoft.UI.Text; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Documents; using Microsoft.UI.Xaml.Media; using Microsoft.Windows.AppNotifications; using Microsoft.Windows.AppNotifications.Builder; @@ -425,7 +430,8 @@ public static async void HandleBrokenWinGet() PrimaryButtonText = CoreTools.Translate("Close"), SecondaryButtonText = CoreTools.Translate("Restart"), DefaultButton = ContentDialogButton.Secondary, - XamlRoot = Window.XamlRoot + XamlRoot = Window.XamlRoot, + Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style, }; // Restart UniGetUI or reload packages depending on the user's choice @@ -463,5 +469,98 @@ public static async void HandleBrokenWinGet() } } + + public static async void ShowTelemetryDialog() + { + var dialog = new ContentDialog() + { + Title = CoreTools.Translate("Share anonymous usage data"), + XamlRoot = Window.XamlRoot, + Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style, + }; + + var MessageBlock = new RichTextBlock(); + dialog.Content = MessageBlock; + + var p = new Paragraph(); + MessageBlock.Blocks.Add(p); + + p.Inlines.Add(new Run() + { + Text = CoreTools.Translate("UniGetUI collects anonymous usage data with the sole purpose of understanding and improving the user experience.") + }); + p.Inlines.Add(new LineBreak()); + p.Inlines.Add(new Run() + { + Text = CoreTools.Translate("No personal information is collected nor sent, and the collected data is anonimized, so it can't be back-tracked to you.") + }); + p.Inlines.Add(new LineBreak()); + p.Inlines.Add(new LineBreak()); + var link = new Hyperlink() { NavigateUri = new Uri("https://www.marticliment.com/unigetui/privacy/"), }; + link.Inlines.Add(new Run() + { + Text = CoreTools.Translate("More details about the shared data and how it will be processed"), + }); + + p.Inlines.Add(link); + p.Inlines.Add(new LineBreak()); + p.Inlines.Add(new LineBreak()); + p.Inlines.Add(new Run() + { + Text = CoreTools.Translate("Do you accept that UniGetUI collects and sends anonymous usage statistics, with the sole purpose of understanding and improving the user experience?"), + FontWeight = FontWeights.SemiBold + }); + + + dialog.PrimaryButtonText = CoreTools.Translate("Decline"); + dialog.SecondaryButtonText = CoreTools.Translate("Accept"); + dialog.DefaultButton = ContentDialogButton.Secondary; + dialog.Closing += (s, e) => + { + if (e.Result == ContentDialogResult.None) e.Cancel = true; + }; + + var res = await Window.ShowDialogAsync(dialog); + + if (res is ContentDialogResult.Secondary) + { + Settings.Set("DisableTelemetry", false); + } + else + { + Settings.Set("DisableTelemetry", true); + } + } + + public static void ShowTelemetryBanner() + { + Window.TelemetryWarner.Title = CoreTools.Translate("Share anonymous usage data"); + Window.TelemetryWarner.Message = CoreTools.Translate("UniGetUI collects anonymous usage data in order to improve the user experience."); + Window.TelemetryWarner.IsOpen = true; + + Window.TelemetryWarner.Background = new SolidColorBrush(Color.FromArgb(62, 66, 135, 245)); + Window.TelemetryWarner.IconSource = new FontIconSource() + { + Glyph = "\uF167", + FontSize = 14, + Foreground = new SolidColorBrush(Color.FromArgb(255, 99, 154, 242)), + }; + + Window.TelemetryWarner.IsClosable = true; + Window.TelemetryWarner.Visibility = Visibility.Visible; + Window.TelemetryWarner.ActionButton = new Button() + { + Content = CoreTools.Translate("Settings"), + }; + + Window.TelemetryWarner.CloseButtonClick += (_, _) => Settings.Set("ShownTelemetryBanner", true); + Window.TelemetryWarner.ActionButton.Click += (_, _) => + { + Window.TelemetryWarner.Visibility = Visibility.Collapsed; + Window.TelemetryWarner.IsOpen = false; + ShowTelemetryDialog(); + Settings.Set("ShownTelemetryBanner", true); + }; + } } diff --git a/src/UniGetUI/Pages/MainView.xaml.cs b/src/UniGetUI/Pages/MainView.xaml.cs index 75c4550f79..4ee036793d 100644 --- a/src/UniGetUI/Pages/MainView.xaml.cs +++ b/src/UniGetUI/Pages/MainView.xaml.cs @@ -118,6 +118,11 @@ public MainView() UpdateOperationsLayout(); MainApp.Operations._operationList.CollectionChanged += (_, _) => UpdateOperationsLayout(); + + if (!Settings.Get("ShownTelemetryBanner")) + { + DialogHelper.ShowTelemetryBanner(); + } } public void LoadDefaultPage() diff --git a/src/UniGetUI/Pages/SettingsPage.xaml b/src/UniGetUI/Pages/SettingsPage.xaml index 4319e87d45..b8538b8ae9 100644 --- a/src/UniGetUI/Pages/SettingsPage.xaml +++ b/src/UniGetUI/Pages/SettingsPage.xaml @@ -85,6 +85,11 @@ Text="Install prerelease versions of UniGetUI" SettingName="EnableUniGetUIBeta" /> + +