From 68bca9c0f75d8887e0b2089958fbeda40428c09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sat, 8 Feb 2025 00:05:05 +0100 Subject: [PATCH 01/13] Add basic thread sleeper --- src/UniGetUI/MainWindow.xaml.cs | 7 +++ src/UniGetUI/ThreadHelper.cs | 107 ++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/UniGetUI/ThreadHelper.cs diff --git a/src/UniGetUI/MainWindow.xaml.cs b/src/UniGetUI/MainWindow.xaml.cs index ea5bd098b..7842d0d07 100644 --- a/src/UniGetUI/MainWindow.xaml.cs +++ b/src/UniGetUI/MainWindow.xaml.cs @@ -115,6 +115,12 @@ public MainWindow() if (!Settings.Get("TransferredOldSettings")) TransferOldSettingsFormats(); + + Activated += (_, e) => + { + if(e.WindowActivationState is WindowActivationState.CodeActivated or WindowActivationState.PointerActivated) + ThreadHelper.HandleDWMThread(true); + }; } private static void TransferOldSettingsFormats() @@ -194,6 +200,7 @@ public async void HandleClosingEvent(AppWindow sender, AppWindowClosingEventArgs if (!Settings.Get("DisableSystemTray") || AutoUpdater.UpdateReadyToBeInstalled) { args.Cancel = true; + ThreadHelper.HandleDWMThread(false); try { this.Hide(enableEfficiencyMode: true); diff --git a/src/UniGetUI/ThreadHelper.cs b/src/UniGetUI/ThreadHelper.cs new file mode 100644 index 000000000..e2ca8e848 --- /dev/null +++ b/src/UniGetUI/ThreadHelper.cs @@ -0,0 +1,107 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using UniGetUI.Core.Logging; + +namespace UniGetUI; + +class ThreadHelper +{ + [DllImport("ntdll.dll")] + private static extern int NtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass, + ref IntPtr ThreadInformation, int ThreadInformationLength, out int ReturnLength); + + [DllImport("kernel32.dll")] + private static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); + + [DllImport("kernel32.dll")] + private static extern uint SuspendThread(IntPtr hThread); + + [DllImport("kernel32.dll")] + private static extern uint ResumeThread(IntPtr hThread); + + [DllImport("kernel32.dll")] + private static extern bool CloseHandle(IntPtr hObject); + + [StructLayout(LayoutKind.Sequential)] + private struct THREAD_BASIC_INFORMATION + { + public IntPtr ExitStatus; + public IntPtr TebBaseAddress; + public uint ProcessId; // Change to uint + public uint ThreadId; // Change to uint + public IntPtr AffinityMask; + public int Priority; // Change to int + public int BasePriority; // Change to int + public IntPtr StartAddress; + } + + + [Flags] + private enum ThreadAccess : uint + { + QUERY_INFORMATION = 0x0040, + SUSPEND_RESUME = 0x0002 + } + + public static void HandleDWMThread(bool enable) + { + IntPtr DWMtargetStartAddress = GetTargetFunctionAddress("dwmcorei.dll", 0x54F70); + IntPtr UGUItargetStartAddress = GetTargetFunctionAddress("UniGetUI.exe", 0x11240); + + if (DWMtargetStartAddress == IntPtr.Zero) + { + Logger.Error("Failed to resolve target function address."); + return; + } + if (UGUItargetStartAddress == IntPtr.Zero) + { + Logger.Error("Failed to resolve target function address."); + return; + } + + foreach (ProcessThread thread in Process.GetCurrentProcess().Threads) + { + IntPtr hThread = OpenThread(ThreadAccess.QUERY_INFORMATION | ThreadAccess.SUSPEND_RESUME, false, + (uint)thread.Id); + if (hThread == IntPtr.Zero) + continue; + + IntPtr adress = 0x00; + int status = NtQueryInformationThread(hThread, 9, ref adress, Marshal.SizeOf(typeof(IntPtr)), out _); + if (status == 0) + { + if (adress == DWMtargetStartAddress) { + if (enable) { + Logger.Warn("Resuming DWM thread!"); + ResumeThread(hThread); + } else { + Logger.Warn("Suspending DWM thread!"); + SuspendThread(hThread); + } + } /*else if (adress == UGUItargetStartAddress) { + if (enable) { + Logger.Warn("Resuming UniGetUI thread!"); + ResumeThread(hThread); + } else { + Logger.Warn("Suspending UniGetUI thread!"); + SuspendThread(hThread); + } + }*/ + } + + CloseHandle(hThread); + } + } + + static IntPtr GetTargetFunctionAddress(string moduleName, int offset) + { + foreach (ProcessModule module in Process.GetCurrentProcess().Modules) + { + if (module.ModuleName.Equals(moduleName, StringComparison.OrdinalIgnoreCase)) + { + return module.BaseAddress + offset; + } + } + return IntPtr.Zero; // Module not found + } +} From 5fcde1ace32f01a5f29b5bf9d64d19cfc15d22a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sat, 8 Feb 2025 12:59:17 +0100 Subject: [PATCH 02/13] For log lines displayed on the debugger, print the calling method name --- src/UniGetUI.Core.Logger/Logger.cs | 40 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/UniGetUI.Core.Logger/Logger.cs b/src/UniGetUI.Core.Logger/Logger.cs index 14789f024..a6a40a34e 100644 --- a/src/UniGetUI.Core.Logger/Logger.cs +++ b/src/UniGetUI.Core.Logger/Logger.cs @@ -7,64 +7,64 @@ public static class Logger private static readonly List LogContents = []; // String parameter log functions - public static void ImportantInfo(string s) + public static void ImportantInfo(string s, [System.Runtime.CompilerServices.CallerMemberName] string caller = "") { - Diagnostics.Debug.WriteLine(s); + Diagnostics.Debug.WriteLine($"[{caller}] " + s); LogContents.Add(new LogEntry(s, LogEntry.SeverityLevel.Success)); } - public static void Debug(string s) + public static void Debug(string s, [System.Runtime.CompilerServices.CallerMemberName] string caller = "") { - Diagnostics.Debug.WriteLine(s); + Diagnostics.Debug.WriteLine($"[{caller}] " + s); LogContents.Add(new LogEntry(s, LogEntry.SeverityLevel.Debug)); } - public static void Info(string s) + public static void Info(string s, [System.Runtime.CompilerServices.CallerMemberName] string caller = "") { - Diagnostics.Debug.WriteLine(s); + Diagnostics.Debug.WriteLine($"[{caller}] " + s); LogContents.Add(new LogEntry(s, LogEntry.SeverityLevel.Info)); } - public static void Warn(string s) + public static void Warn(string s, [System.Runtime.CompilerServices.CallerMemberName] string caller = "") { - Diagnostics.Debug.WriteLine(s); + Diagnostics.Debug.WriteLine($"[{caller}] " + s); LogContents.Add(new LogEntry(s, LogEntry.SeverityLevel.Warning)); } - public static void Error(string s) + public static void Error(string s, [System.Runtime.CompilerServices.CallerMemberName] string caller = "") { - Diagnostics.Debug.WriteLine(s); + Diagnostics.Debug.WriteLine($"[{caller}] " + s); LogContents.Add(new LogEntry(s, LogEntry.SeverityLevel.Error)); } // Exception parameter log functions - public static void ImportantInfo(Exception e) + public static void ImportantInfo(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string caller = "") { - Diagnostics.Debug.WriteLine(e.ToString()); + Diagnostics.Debug.WriteLine($"[{caller}] " + e.ToString()); LogContents.Add(new LogEntry(e.ToString(), LogEntry.SeverityLevel.Success)); } - public static void Debug(Exception e) + public static void Debug(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string caller = "") { - Diagnostics.Debug.WriteLine(e.ToString()); + Diagnostics.Debug.WriteLine($"[{caller}] " + e.ToString()); LogContents.Add(new LogEntry(e.ToString(), LogEntry.SeverityLevel.Debug)); } - public static void Info(Exception e) + public static void Info(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string caller = "") { - Diagnostics.Debug.WriteLine(e.ToString()); + Diagnostics.Debug.WriteLine($"[{caller}] " + e.ToString()); LogContents.Add(new LogEntry(e.ToString(), LogEntry.SeverityLevel.Info)); } - public static void Warn(Exception e) + public static void Warn(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string caller = "") { - Diagnostics.Debug.WriteLine(e.ToString()); + Diagnostics.Debug.WriteLine($"[{caller}] " + e.ToString()); LogContents.Add(new LogEntry(e.ToString(), LogEntry.SeverityLevel.Warning)); } - public static void Error(Exception e) + public static void Error(Exception e, [System.Runtime.CompilerServices.CallerMemberName] string caller = "") { - Diagnostics.Debug.WriteLine(e.ToString()); + Diagnostics.Debug.WriteLine($"[{caller}] " + e.ToString()); LogContents.Add(new LogEntry(e.ToString(), LogEntry.SeverityLevel.Error)); } From 5b7ac7166452b35a2f2c9654aa05c94d6ec358be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sat, 8 Feb 2025 12:59:52 +0100 Subject: [PATCH 03/13] Sleep DWM and XAML process when app is in background, and resume them once the app window is shown again --- src/UniGetUI.Core.Tools/DWMThreadHelper.cs | 140 +++++++++++++++++++++ src/UniGetUI/MainWindow.xaml.cs | 24 ++-- src/UniGetUI/ThreadHelper.cs | 107 ---------------- 3 files changed, 156 insertions(+), 115 deletions(-) create mode 100644 src/UniGetUI.Core.Tools/DWMThreadHelper.cs delete mode 100644 src/UniGetUI/ThreadHelper.cs diff --git a/src/UniGetUI.Core.Tools/DWMThreadHelper.cs b/src/UniGetUI.Core.Tools/DWMThreadHelper.cs new file mode 100644 index 000000000..63a593edd --- /dev/null +++ b/src/UniGetUI.Core.Tools/DWMThreadHelper.cs @@ -0,0 +1,140 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; +using UniGetUI.Core.Logging; + +namespace UniGetUI; + +public class DWMThreadHelper +{ + [DllImport("ntdll.dll")] + private static extern int NtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass, + ref IntPtr ThreadInformation, int ThreadInformationLength, out int ReturnLength); + + [DllImport("kernel32.dll")] + private static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); + + [DllImport("kernel32.dll")] + private static extern uint SuspendThread(IntPtr hThread); + + [DllImport("kernel32.dll")] + private static extern uint ResumeThread(IntPtr hThread); + + [DllImport("kernel32.dll")] + private static extern bool CloseHandle(IntPtr hObject); + + [Flags] + private enum ThreadAccess : uint + { + QUERY_INFORMATION = 0x0040, + SUSPEND_RESUME = 0x0002 + } + + private const int ThreadQuerySetWin32StartAddress = 9; + private static bool DWM_IsSuspended; + private static bool XAML_IsSuspended; + + public static void ChangeState_DWM(bool suspend) + { + if (DWM_IsSuspended && suspend) + { + Logger.Warn("DWM Thread was already suspended"); return; + } + else if (!DWM_IsSuspended && !suspend) + { + Logger.Warn("DWM Thread was already running"); return; + } + + IntPtr adress = GetTargetFunctionAddress("dwmcorei.dll", 0x54F70); + if (adress == IntPtr.Zero) + { + Logger.Error("Failed to resolve thread start adress."); return; + } + + ChangeState(suspend, adress, ref DWM_IsSuspended, "DWM"); + } + + public static void ChangeState_XAML(bool suspend) + { + if (XAML_IsSuspended && suspend) + { + Logger.Warn("XAML Thread was already suspended"); return; + } + else if (!XAML_IsSuspended && !suspend) + { + Logger.Warn("XAML Thread was already running"); return; + } + + // The reported offset on ProcessExplorer seems to be missing 0x6280 somehow + // 0x54F70 + 0x6280 = 0x5B1F0 + IntPtr adress = GetTargetFunctionAddress("Microsoft.UI.Xaml.dll", 0x5B1F0); + if (adress == IntPtr.Zero) + { + Logger.Error("Failed to resolve thread start adress."); return; + } + + ChangeState(suspend, adress, ref XAML_IsSuspended, "XAML"); + } + + private static void ChangeState(bool suspend, IntPtr threadAdress, ref bool IsSuspended, string loggerName) + { + foreach (ProcessThread thread in Process.GetCurrentProcess().Threads) + { + IntPtr hThread = OpenThread(ThreadAccess.QUERY_INFORMATION | ThreadAccess.SUSPEND_RESUME, false, + (uint)thread.Id); + if (hThread == IntPtr.Zero) + continue; + + IntPtr adress = 0x00; + int status = NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, ref adress, Marshal.SizeOf(typeof(IntPtr)), out _); + + if (status == 0 && adress == threadAdress) + { + if (suspend) + { + uint res = SuspendThread(hThread); + if (res == 0) + { + IsSuspended = true; + Logger.Warn($"{loggerName} Thread was suspended successfully"); + CloseHandle(hThread); + return; + } + else + { + Logger.Error($"Could not suspend {loggerName} Thread with NTSTATUS = 0x{res:X}"); + } + } + else + { + int res = (int)ResumeThread(hThread); + if (res >= 0) + { + IsSuspended = false; + Logger.Warn($"{loggerName} Thread was resumed successfully"); + CloseHandle(hThread); + return; + } + else + { + Logger.Error($"Could not resume {loggerName} Thread with NTSTATUS = 0x{res:X}"); + } + } + } + + CloseHandle(hThread); + } + Logger.Error($"No thread matching {loggerName} was found"); + } + + private static IntPtr GetTargetFunctionAddress(string moduleName, int offset) + { + foreach (ProcessModule module in Process.GetCurrentProcess().Modules) + { + if (module.ModuleName.Equals(moduleName, StringComparison.OrdinalIgnoreCase)) + { + return module.BaseAddress + offset; + } + } + return IntPtr.Zero; + } +} diff --git a/src/UniGetUI/MainWindow.xaml.cs b/src/UniGetUI/MainWindow.xaml.cs index 7842d0d07..73160cfbb 100644 --- a/src/UniGetUI/MainWindow.xaml.cs +++ b/src/UniGetUI/MainWindow.xaml.cs @@ -118,8 +118,12 @@ public MainWindow() Activated += (_, e) => { - if(e.WindowActivationState is WindowActivationState.CodeActivated or WindowActivationState.PointerActivated) - ThreadHelper.HandleDWMThread(true); + if (e.WindowActivationState is WindowActivationState.CodeActivated + or WindowActivationState.PointerActivated) + { + DWMThreadHelper.ChangeState_DWM(false); + DWMThreadHelper.ChangeState_XAML(false); + } }; } @@ -200,21 +204,22 @@ public async void HandleClosingEvent(AppWindow sender, AppWindowClosingEventArgs if (!Settings.Get("DisableSystemTray") || AutoUpdater.UpdateReadyToBeInstalled) { args.Cancel = true; - ThreadHelper.HandleDWMThread(false); + DWMThreadHelper.ChangeState_DWM(true); + DWMThreadHelper.ChangeState_XAML(true); try { - this.Hide(enableEfficiencyMode: true); - MainContentFrame.Content = null; + // this.Hide(enableEfficiencyMode: true); AppWindow.Hide(); + MainContentFrame.Content = null; } catch (Exception ex) { // Somewhere, Sometimes, MS Window Efficiency mode just crashes - Logger.Debug("Windows efficiency mode API crashed, but this was expected"); + Logger.Debug("Windows efficiency mode API crashed, but this was [kinda] expected"); Logger.Debug(ex); - this.Hide(enableEfficiencyMode: false); - MainContentFrame.Content = null; + // this.Hide(enableEfficiencyMode: false); AppWindow.Hide(); + MainContentFrame.Content = null; } } else @@ -364,6 +369,9 @@ public void ProcessCommandLineParameters() public new void Activate() { + DWMThreadHelper.ChangeState_DWM(false); + DWMThreadHelper.ChangeState_XAML(false); + if (!HasLoadedLastGeometry) { RestoreGeometry(); diff --git a/src/UniGetUI/ThreadHelper.cs b/src/UniGetUI/ThreadHelper.cs deleted file mode 100644 index e2ca8e848..000000000 --- a/src/UniGetUI/ThreadHelper.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System.Diagnostics; -using System.Runtime.InteropServices; -using UniGetUI.Core.Logging; - -namespace UniGetUI; - -class ThreadHelper -{ - [DllImport("ntdll.dll")] - private static extern int NtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass, - ref IntPtr ThreadInformation, int ThreadInformationLength, out int ReturnLength); - - [DllImport("kernel32.dll")] - private static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); - - [DllImport("kernel32.dll")] - private static extern uint SuspendThread(IntPtr hThread); - - [DllImport("kernel32.dll")] - private static extern uint ResumeThread(IntPtr hThread); - - [DllImport("kernel32.dll")] - private static extern bool CloseHandle(IntPtr hObject); - - [StructLayout(LayoutKind.Sequential)] - private struct THREAD_BASIC_INFORMATION - { - public IntPtr ExitStatus; - public IntPtr TebBaseAddress; - public uint ProcessId; // Change to uint - public uint ThreadId; // Change to uint - public IntPtr AffinityMask; - public int Priority; // Change to int - public int BasePriority; // Change to int - public IntPtr StartAddress; - } - - - [Flags] - private enum ThreadAccess : uint - { - QUERY_INFORMATION = 0x0040, - SUSPEND_RESUME = 0x0002 - } - - public static void HandleDWMThread(bool enable) - { - IntPtr DWMtargetStartAddress = GetTargetFunctionAddress("dwmcorei.dll", 0x54F70); - IntPtr UGUItargetStartAddress = GetTargetFunctionAddress("UniGetUI.exe", 0x11240); - - if (DWMtargetStartAddress == IntPtr.Zero) - { - Logger.Error("Failed to resolve target function address."); - return; - } - if (UGUItargetStartAddress == IntPtr.Zero) - { - Logger.Error("Failed to resolve target function address."); - return; - } - - foreach (ProcessThread thread in Process.GetCurrentProcess().Threads) - { - IntPtr hThread = OpenThread(ThreadAccess.QUERY_INFORMATION | ThreadAccess.SUSPEND_RESUME, false, - (uint)thread.Id); - if (hThread == IntPtr.Zero) - continue; - - IntPtr adress = 0x00; - int status = NtQueryInformationThread(hThread, 9, ref adress, Marshal.SizeOf(typeof(IntPtr)), out _); - if (status == 0) - { - if (adress == DWMtargetStartAddress) { - if (enable) { - Logger.Warn("Resuming DWM thread!"); - ResumeThread(hThread); - } else { - Logger.Warn("Suspending DWM thread!"); - SuspendThread(hThread); - } - } /*else if (adress == UGUItargetStartAddress) { - if (enable) { - Logger.Warn("Resuming UniGetUI thread!"); - ResumeThread(hThread); - } else { - Logger.Warn("Suspending UniGetUI thread!"); - SuspendThread(hThread); - } - }*/ - } - - CloseHandle(hThread); - } - } - - static IntPtr GetTargetFunctionAddress(string moduleName, int offset) - { - foreach (ProcessModule module in Process.GetCurrentProcess().Modules) - { - if (module.ModuleName.Equals(moduleName, StringComparison.OrdinalIgnoreCase)) - { - return module.BaseAddress + offset; - } - } - return IntPtr.Zero; // Module not found - } -} From 5056bc7b4dab3d370ea9773b9323e5f3f7a77a71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sat, 8 Feb 2025 13:05:03 +0100 Subject: [PATCH 04/13] Fix packages showing greyed out until mouseover event --- src/UniGetUI/Pages/MainView.xaml.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/UniGetUI/Pages/MainView.xaml.cs b/src/UniGetUI/Pages/MainView.xaml.cs index 9a31f545c..93a6ac112 100644 --- a/src/UniGetUI/Pages/MainView.xaml.cs +++ b/src/UniGetUI/Pages/MainView.xaml.cs @@ -260,9 +260,11 @@ public void NavigateTo(PageType NewPage_t) OldPage_t = CurrentPage_t; CurrentPage_t = NewPage_t; + (oldPage as IEnterLeaveListener)?.OnLeave(); + (NewPage as AbstractPackagesPage)?.FocusPackageList(); + (NewPage as AbstractPackagesPage)?.FilterPackages(); (NewPage as IEnterLeaveListener)?.OnEnter(); - (oldPage as IEnterLeaveListener)?.OnLeave(); } private void ReleaseNotesMenu_Click(object sender, RoutedEventArgs e) From a56af411f6a25d2934cb44a455e86bcaefa21ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sat, 8 Feb 2025 13:21:19 +0100 Subject: [PATCH 05/13] Fix quitting UniGetUI creating a nonexistent process --- src/UniGetUI/App.xaml.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/UniGetUI/App.xaml.cs b/src/UniGetUI/App.xaml.cs index a8bd907be..8dd47ad16 100644 --- a/src/UniGetUI/App.xaml.cs +++ b/src/UniGetUI/App.xaml.cs @@ -400,12 +400,14 @@ public async Task ShowMainWindowFromLaunchAsync() public async void DisposeAndQuit(int outputCode = 0) { - Logger.Warn("Quitting..."); + Logger.Warn("Quitting UniGetUI"); + DWMThreadHelper.ChangeState_DWM(false); + DWMThreadHelper.ChangeState_XAML(false); MainWindow?.Close(); BackgroundApi?.Stop(); Exit(); - await Task.Delay(100); - Environment.Exit(outputCode); + // await Task.Delay(100); + // Environment.Exit(outputCode); } public void KillAndRestart() From 18188e75eb7d4ff49d30519d27a004f5394d7fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sat, 8 Feb 2025 13:43:53 +0100 Subject: [PATCH 06/13] Fix efficiency mode being always enabled, fix icon tray not showing when launched as daemon --- src/UniGetUI/App.xaml.cs | 18 +++---------- src/UniGetUI/MainWindow.xaml.cs | 48 ++++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/src/UniGetUI/App.xaml.cs b/src/UniGetUI/App.xaml.cs index 8dd47ad16..cd1cd9e12 100644 --- a/src/UniGetUI/App.xaml.cs +++ b/src/UniGetUI/App.xaml.cs @@ -2,6 +2,7 @@ using System.Text.RegularExpressions; using Windows.ApplicationModel.Activation; using CommunityToolkit.WinUI.Helpers; +using H.NotifyIcon; using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; @@ -349,14 +350,9 @@ private async Task CheckForMissingDependencies() await MainWindow.HandleMissingDependencies(missing_deps); } - protected override async void OnLaunched(LaunchActivatedEventArgs args) + protected override void OnLaunched(LaunchActivatedEventArgs args) { - if (!CoreData.IsDaemon) - { - await ShowMainWindowFromLaunchAsync(); - } - - CoreData.IsDaemon = false; + MainWindow?.Activate(); } public async Task ShowMainWindowFromRedirectAsync(AppActivationArguments rawArgs) @@ -390,14 +386,6 @@ public async Task ShowMainWindowFromRedirectAsync(AppActivationArguments rawArgs MainWindow.DispatcherQueue.TryEnqueue(MainWindow.Activate); } - public async Task ShowMainWindowFromLaunchAsync() - { - while (MainWindow is null) - await Task.Delay(100); - - MainWindow.DispatcherQueue.TryEnqueue(MainWindow.Activate); - } - public async void DisposeAndQuit(int outputCode = 0) { Logger.Warn("Quitting UniGetUI"); diff --git a/src/UniGetUI/MainWindow.xaml.cs b/src/UniGetUI/MainWindow.xaml.cs index 73160cfbb..7b6b383e5 100644 --- a/src/UniGetUI/MainWindow.xaml.cs +++ b/src/UniGetUI/MainWindow.xaml.cs @@ -19,6 +19,7 @@ using UniGetUI.PackageEngine.Classes.Manager.Classes; using UniGetUI.PackageEngine.Interfaces; using Windows.ApplicationModel.DataTransfer; +using H.NotifyIcon.EfficiencyMode; using Microsoft.Windows.AppNotifications; using UniGetUI.Core.Classes; using UniGetUI.Interface.Enums; @@ -125,6 +126,27 @@ public MainWindow() DWMThreadHelper.ChangeState_XAML(false); } }; + + if (CoreData.IsDaemon) + { + try + { + TrayIcon?.ForceCreate(true); + } + catch (Exception ex) + { + TrayIcon?.ForceCreate(false); + Logger.Error("Could not create taskbar tray with efficiency mode enabled"); + Logger.Error(ex); + } + DWMThreadHelper.ChangeState_DWM(true); + DWMThreadHelper.ChangeState_XAML(true); + CoreData.IsDaemon = false; + } + else + { + Activate(); + } } private static void TransferOldSettingsFormats() @@ -206,21 +228,19 @@ public async void HandleClosingEvent(AppWindow sender, AppWindowClosingEventArgs args.Cancel = true; DWMThreadHelper.ChangeState_DWM(true); DWMThreadHelper.ChangeState_XAML(true); + try { - // this.Hide(enableEfficiencyMode: true); - AppWindow.Hide(); - MainContentFrame.Content = null; + EfficiencyModeUtilities.SetEfficiencyMode(true); } catch (Exception ex) { - // Somewhere, Sometimes, MS Window Efficiency mode just crashes - Logger.Debug("Windows efficiency mode API crashed, but this was [kinda] expected"); - Logger.Debug(ex); - // this.Hide(enableEfficiencyMode: false); - AppWindow.Hide(); - MainContentFrame.Content = null; + Logger.Error("Could not disable efficiency mode"); + Logger.Error(ex); } + + MainContentFrame.Content = null; + AppWindow.Hide(); } else { @@ -369,6 +389,16 @@ public void ProcessCommandLineParameters() public new void Activate() { + try + { + EfficiencyModeUtilities.SetEfficiencyMode(false); + } + catch (Exception ex) + { + Logger.Error("Could not disable efficiency mode"); + Logger.Error(ex); + } + DWMThreadHelper.ChangeState_DWM(false); DWMThreadHelper.ChangeState_XAML(false); From 5686e7cc030b195c925a24700dd699a5b6bad30a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sat, 8 Feb 2025 16:35:24 +0100 Subject: [PATCH 07/13] Cache thread Id and startAdress so they don't have to be loaded each time --- src/UniGetUI.Core.Tools/DWMThreadHelper.cs | 142 +++++++++++++-------- 1 file changed, 89 insertions(+), 53 deletions(-) diff --git a/src/UniGetUI.Core.Tools/DWMThreadHelper.cs b/src/UniGetUI.Core.Tools/DWMThreadHelper.cs index 63a593edd..9c88557fd 100644 --- a/src/UniGetUI.Core.Tools/DWMThreadHelper.cs +++ b/src/UniGetUI.Core.Tools/DWMThreadHelper.cs @@ -33,100 +33,136 @@ private enum ThreadAccess : uint private static bool DWM_IsSuspended; private static bool XAML_IsSuspended; + private static int? DWMThreadId; + private static IntPtr? DWMThreadAdress; + private static int? XAMLThreadId; + private static IntPtr? XAMLThreadAdress; + public static void ChangeState_DWM(bool suspend) { if (DWM_IsSuspended && suspend) { - Logger.Warn("DWM Thread was already suspended"); return; + Logger.Debug("DWM Thread was already suspended"); return; } else if (!DWM_IsSuspended && !suspend) { - Logger.Warn("DWM Thread was already running"); return; + Logger.Debug("DWM Thread was already running"); return; } - IntPtr adress = GetTargetFunctionAddress("dwmcorei.dll", 0x54F70); - if (adress == IntPtr.Zero) + DWMThreadAdress ??= GetTargetFunctionAddress("dwmcorei.dll", 0x54F70); + if (DWMThreadAdress is null) { Logger.Error("Failed to resolve thread start adress."); return; } - ChangeState(suspend, adress, ref DWM_IsSuspended, "DWM"); + ChangeState(suspend, (IntPtr)DWMThreadAdress, ref DWM_IsSuspended, ref DWMThreadId, "DWM"); } public static void ChangeState_XAML(bool suspend) { if (XAML_IsSuspended && suspend) { - Logger.Warn("XAML Thread was already suspended"); return; + Logger.Debug("XAML Thread was already suspended"); return; } else if (!XAML_IsSuspended && !suspend) { - Logger.Warn("XAML Thread was already running"); return; + Logger.Debug("XAML Thread was already running"); return; } // The reported offset on ProcessExplorer seems to be missing 0x6280 somehow // 0x54F70 + 0x6280 = 0x5B1F0 - IntPtr adress = GetTargetFunctionAddress("Microsoft.UI.Xaml.dll", 0x5B1F0); - if (adress == IntPtr.Zero) + XAMLThreadAdress ??= GetTargetFunctionAddress("Microsoft.UI.Xaml.dll", 0x5B1F0); + if (XAMLThreadAdress is null) { Logger.Error("Failed to resolve thread start adress."); return; } - ChangeState(suspend, adress, ref XAML_IsSuspended, "XAML"); + ChangeState(suspend, (IntPtr)XAMLThreadAdress, ref XAML_IsSuspended, ref XAMLThreadId, "XAML"); } - private static void ChangeState(bool suspend, IntPtr threadAdress, ref bool IsSuspended, string loggerName) + private static IntPtr GetThreadStartAdress(int threadId) { - foreach (ProcessThread thread in Process.GetCurrentProcess().Threads) - { - IntPtr hThread = OpenThread(ThreadAccess.QUERY_INFORMATION | ThreadAccess.SUSPEND_RESUME, false, - (uint)thread.Id); - if (hThread == IntPtr.Zero) - continue; - - IntPtr adress = 0x00; - int status = NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, ref adress, Marshal.SizeOf(typeof(IntPtr)), out _); + IntPtr hThread = OpenThread(ThreadAccess.QUERY_INFORMATION | ThreadAccess.SUSPEND_RESUME, false, (uint)threadId); + if (hThread == IntPtr.Zero) return IntPtr.Zero; + + IntPtr adress = 0x00; + int status = NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, ref adress, Marshal.SizeOf(typeof(IntPtr)), out _); + if(status != 0) Logger.Warn($"NtQueryInformationThread returned non-zero status code 0x{(uint)status:X}"); + CloseHandle(hThread); + return adress; + } - if (status == 0 && adress == threadAdress) + private static void ChangeState(bool suspend, IntPtr threadAdress, ref bool IsSuspended, ref int? threadId, + string loggerName, bool canRetry = true) + { + if (threadId is null) + { + foreach (ProcessThread thread in Process.GetCurrentProcess().Threads) { - if (suspend) - { - uint res = SuspendThread(hThread); - if (res == 0) - { - IsSuspended = true; - Logger.Warn($"{loggerName} Thread was suspended successfully"); - CloseHandle(hThread); - return; - } - else - { - Logger.Error($"Could not suspend {loggerName} Thread with NTSTATUS = 0x{res:X}"); - } - } - else + if (GetThreadStartAdress(thread.Id) == threadAdress) { - int res = (int)ResumeThread(hThread); - if (res >= 0) - { - IsSuspended = false; - Logger.Warn($"{loggerName} Thread was resumed successfully"); - CloseHandle(hThread); - return; - } - else - { - Logger.Error($"Could not resume {loggerName} Thread with NTSTATUS = 0x{res:X}"); - } + threadId = thread.Id; + Logger.Info($"Thread with Id {threadId} was assigned as {loggerName} thread"); } } + } - CloseHandle(hThread); + if (threadId is null) + { + Logger.Error($"No thread matching {loggerName} with start adress {threadAdress:X} was found"); + return; } - Logger.Error($"No thread matching {loggerName} was found"); + + IntPtr hThread = OpenThread(ThreadAccess.QUERY_INFORMATION | ThreadAccess.SUSPEND_RESUME, false, (uint)threadId); + if (hThread == IntPtr.Zero) + { // When the thread cannot be opened + if (canRetry) + { + threadId = null; // On first try, reset argument threadId so it does get loaded again. + ChangeState(suspend, threadAdress, ref IsSuspended, ref threadId, loggerName, false); + return; + } + // The threadId was already reloaded + Logger.Warn($"Thread with id={threadId} and assigned as {loggerName} exists but could not be opened!"); + return; + } + + + if (suspend) + { + uint res = SuspendThread(hThread); + if (res == 0) + { + IsSuspended = true; + Logger.Info($"{loggerName} Thread was suspended successfully"); + CloseHandle(hThread); + return; + } + else + { + Logger.Warn($"Could not suspend {loggerName} Thread with NTSTATUS = 0x{res:X}"); + } + } + else + { + int res = (int)ResumeThread(hThread); + if (res >= 0) + { + IsSuspended = false; + Logger.Info($"{loggerName} Thread was resumed successfully"); + CloseHandle(hThread); + return; + } + else + { + Logger.Error($"Could not resume {loggerName} Thread with NTSTATUS = 0x{res:X}"); + } + } + + CloseHandle(hThread); } - private static IntPtr GetTargetFunctionAddress(string moduleName, int offset) + private static IntPtr? GetTargetFunctionAddress(string moduleName, int offset) { foreach (ProcessModule module in Process.GetCurrentProcess().Modules) { @@ -135,6 +171,6 @@ private static IntPtr GetTargetFunctionAddress(string moduleName, int offset) return module.BaseAddress + offset; } } - return IntPtr.Zero; + return null; } } From b7eea32ddce544bf966d7d7d9bc6ca18480b864b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sat, 8 Feb 2025 17:17:20 +0100 Subject: [PATCH 08/13] Add a kill switch to the Background CPU optimizer --- src/UniGetUI.Core.Tools/DWMThreadHelper.cs | 5 +++++ src/UniGetUI/Pages/SettingsPage.xaml | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/UniGetUI.Core.Tools/DWMThreadHelper.cs b/src/UniGetUI.Core.Tools/DWMThreadHelper.cs index 9c88557fd..57f54f3d6 100644 --- a/src/UniGetUI.Core.Tools/DWMThreadHelper.cs +++ b/src/UniGetUI.Core.Tools/DWMThreadHelper.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using System.Runtime.InteropServices; using UniGetUI.Core.Logging; +using UniGetUI.Core.SettingsEngine; namespace UniGetUI; @@ -40,6 +41,8 @@ private enum ThreadAccess : uint public static void ChangeState_DWM(bool suspend) { + if (Settings.Get("DisableDMWThreadOptimizations")) return; + if (DWM_IsSuspended && suspend) { Logger.Debug("DWM Thread was already suspended"); return; @@ -60,6 +63,8 @@ public static void ChangeState_DWM(bool suspend) public static void ChangeState_XAML(bool suspend) { + if (Settings.Get("DisableDMWThreadOptimizations")) return; + if (XAML_IsSuspended && suspend) { Logger.Debug("XAML Thread was already suspended"); return; diff --git a/src/UniGetUI/Pages/SettingsPage.xaml b/src/UniGetUI/Pages/SettingsPage.xaml index ef3b322c1..0cdaa64b3 100644 --- a/src/UniGetUI/Pages/SettingsPage.xaml +++ b/src/UniGetUI/Pages/SettingsPage.xaml @@ -332,6 +332,11 @@ UnderText="Beta features and other options that shouldn't be touched" Icon="experimental"> + Date: Sat, 8 Feb 2025 17:21:44 +0100 Subject: [PATCH 09/13] .NET Tools that can only be installed globally should be installed globally - this issue was spotted thanks to telemetry :) --- .../Helpers/DotNetPkgOperationHelper.cs | 12 +++++++++--- .../AbstractOperation.cs | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs b/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs index 1ee2a810c..12c2bd628 100644 --- a/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs +++ b/src/UniGetUI.PackageEngine.Managers.Dotnet/Helpers/DotNetPkgOperationHelper.cs @@ -33,8 +33,8 @@ protected override IEnumerable _getOperationParameters( if (options.CustomInstallLocation != "") parameters.AddRange(["--tool-path", "\"" + options.CustomInstallLocation + "\""]); - if (package.OverridenOptions.Scope == PackageScope.Global || - (package.OverridenOptions.Scope is null && options.InstallationScope == PackageScope.Global)) + if (package.OverridenOptions.Scope is PackageScope.Global || + (package.OverridenOptions.Scope is null && options.InstallationScope is PackageScope.Global)) parameters.Add("--global"); if (operation is OperationType.Install or OperationType.Update) @@ -66,6 +66,12 @@ protected override OperationVeredict _getOperationResult( IEnumerable processOutput, int returnCode) { - return returnCode == 0 ? OperationVeredict.Success : OperationVeredict.Failure; + if (returnCode is not 0 && package.OverridenOptions.Scope is not PackageScope.Global) + { + package.OverridenOptions.Scope = PackageScope.Global; + return OperationVeredict.AutoRetry; + } + + return returnCode is 0 ? OperationVeredict.Success : OperationVeredict.Failure; } } diff --git a/src/UniGetUI.PackageEngine.Operations/AbstractOperation.cs b/src/UniGetUI.PackageEngine.Operations/AbstractOperation.cs index a21986c7c..dbc29b0ad 100644 --- a/src/UniGetUI.PackageEngine.Operations/AbstractOperation.cs +++ b/src/UniGetUI.PackageEngine.Operations/AbstractOperation.cs @@ -242,10 +242,11 @@ public async Task MainThread() OperationVeredict result; Line(CoreTools.Translate("Starting operation..."), LineType.ProgressIndicator); if (Status is OperationStatus.InQueue) Status = OperationStatus.Running; - OperationStarting?.Invoke(this, EventArgs.Empty); do { + OperationStarting?.Invoke(this, EventArgs.Empty); + try { // Check if the operation was canceled From 104de8d25d4e51a3a77f86d5ab32c658cd28387c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sat, 8 Feb 2025 23:25:16 +0100 Subject: [PATCH 10/13] Fix a hard crash happening when UniGetUI in portable mode does not have write permissions on the settings directory --- src/UniGetUI.Core.Data/CoreData.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/UniGetUI.Core.Data/CoreData.cs b/src/UniGetUI.Core.Data/CoreData.cs index 21a7c6b54..98527c65f 100644 --- a/src/UniGetUI.Core.Data/CoreData.cs +++ b/src/UniGetUI.Core.Data/CoreData.cs @@ -39,11 +39,22 @@ public static string UniGetUIDataDirectory if (IS_PORTABLE is null) IS_PORTABLE = File.Exists(Path.Join(UniGetUIExecutableDirectory, "ForceUniGetUIPortable")); - if (IS_PORTABLE == true) + if (IS_PORTABLE is true) { string path = Path.Join(UniGetUIExecutableDirectory, "Settings"); - if (!Directory.Exists(path)) Directory.CreateDirectory(path); - return path; + try + { + if (!Directory.Exists(path)) Directory.CreateDirectory(path); + var testfilepath = Path.Join(path, "PermissionTestFile"); + File.WriteAllText(testfilepath, "https://www.youtube.com/watch?v=dQw4w9WgXcQ"); + return path; + } + catch (Exception ex) + { + IS_PORTABLE = false; + Logger.Error($"Could not acces/write path {path}. UniGetUI will NOT be run in portable mode, and User settings will be used instead"); + Logger.Error(ex); + } } string old_path = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".wingetui"); From 1d5007b1291dd93e69fb3437869e2252227aca52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sat, 8 Feb 2025 23:54:11 +0100 Subject: [PATCH 11/13] Add a subtitle to the titlebar to show various info, such as "run as admin", "debug build", etc --- src/UniGetUI.Core.Data/CoreData.cs | 1 + src/UniGetUI/MainWindow.xaml | 3 ++- src/UniGetUI/MainWindow.xaml.cs | 26 ++++++++++++++++++++++---- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/UniGetUI.Core.Data/CoreData.cs b/src/UniGetUI.Core.Data/CoreData.cs index 98527c65f..125cc19a7 100644 --- a/src/UniGetUI.Core.Data/CoreData.cs +++ b/src/UniGetUI.Core.Data/CoreData.cs @@ -28,6 +28,7 @@ public static HttpClientHandler GenericHttpClientParameters } private static bool? IS_PORTABLE; + public static bool IsPortable { get => IS_PORTABLE ?? false; } /// /// The directory where all the user data is stored. The directory is automatically created if it does not exist. diff --git a/src/UniGetUI/MainWindow.xaml b/src/UniGetUI/MainWindow.xaml index d6b3e4cf0..f1bc60d83 100644 --- a/src/UniGetUI/MainWindow.xaml +++ b/src/UniGetUI/MainWindow.xaml @@ -30,7 +30,8 @@ - + + diff --git a/src/UniGetUI/MainWindow.xaml.cs b/src/UniGetUI/MainWindow.xaml.cs index 7b6b383e5..7ff5c8c2e 100644 --- a/src/UniGetUI/MainWindow.xaml.cs +++ b/src/UniGetUI/MainWindow.xaml.cs @@ -75,14 +75,18 @@ public MainWindow() if (CoreTools.IsAdministrator()) { - Title = "UniGetUI " + CoreTools.Translate("[RAN AS ADMINISTRATOR]"); - AppTitle.Text = Title; + AddToSubtitle(CoreTools.Translate("[RAN AS ADMINISTRATOR]")); + } + + if (CoreData.IsPortable) + { + AddToSubtitle(CoreTools.Translate("Portable mode")); } #if DEBUG - Title = Title + " - DEBUG BUILD"; - AppTitle.Text = Title; + AddToSubtitle(CoreTools.Translate("DEBUG BUILD")); #endif + var panel = new StackPanel { Width = 400, @@ -149,6 +153,20 @@ public MainWindow() } } + private void AddToSubtitle(string line) + { + if (AppSubTitle.Text.Length > 0) + AppSubTitle.Text += " - "; + AppSubTitle.Text += line; + Title = "UniGetUI - " + AppSubTitle.Text; + } + + private void ClearSubtitle() + { + AppSubTitle.Text = ""; + Title = "UniGetUI"; + } + private static void TransferOldSettingsFormats() { foreach (IPackageManager Manager in PEInterface.Managers) From 9e3482c307cb549692cf3938af7adf9f70c341db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 9 Feb 2025 00:13:05 +0100 Subject: [PATCH 12/13] Bootstrap vcpkg the first time it gets updated. --- .../Vcpkg.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/UniGetUI.PackageEngine.Managers.Vcpkg/Vcpkg.cs b/src/UniGetUI.PackageEngine.Managers.Vcpkg/Vcpkg.cs index c973d25cb..af474f00c 100644 --- a/src/UniGetUI.PackageEngine.Managers.Vcpkg/Vcpkg.cs +++ b/src/UniGetUI.PackageEngine.Managers.Vcpkg/Vcpkg.cs @@ -21,6 +21,8 @@ public class Vcpkg : PackageManager public Dictionary TripletSourceMap; public static Uri URI_VCPKG_IO = new Uri("https://vcpkg.io/"); + private bool hasBeenBootstrapped; + public Vcpkg() { Dependencies = [ @@ -373,6 +375,30 @@ public override void RefreshPackageIndexes() processLogger.AddToStdOut(p.StandardOutput.ReadToEnd()); processLogger.AddToStdErr(p.StandardError.ReadToEnd()); processLogger.Close(p.ExitCode); + + if (!hasBeenBootstrapped) + { + p = new() + { + StartInfo = new ProcessStartInfo + { + FileName = "cmd.exe", + WorkingDirectory = vcpkgRoot, + Arguments = "/C .\\bootstrap-vcpkg.bat", + UseShellExecute = false, + // RedirectStandardOutput = true, + // RedirectStandardError = true, + CreateNoWindow = true + } + }; + IProcessTaskLogger processLogger2 = TaskLogger.CreateNew(LoggableTaskType.RefreshIndexes, p); + p.Start(); + p.WaitForExit(); + // processLogger2.AddToStdOut(p.StandardOutput.ReadToEnd()); + // processLogger2.AddToStdErr(p.StandardError.ReadToEnd()); + processLogger2.Close(p.ExitCode); + hasBeenBootstrapped = true; + } } public static Tuple GetVcpkgPath() From e826281dfd26c7f1b9e4be241bbf61b0ee506ad7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=AD=20Climent?= Date: Sun, 9 Feb 2025 22:31:53 +0100 Subject: [PATCH 13/13] Remove somehow duplicated function --- src/UniGetUI/MainWindow.xaml.cs | 14 -------------- .../Pages/DialogPages/DesktopShortcuts.xaml.cs | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/UniGetUI/MainWindow.xaml.cs b/src/UniGetUI/MainWindow.xaml.cs index 0d46932a0..7ff5c8c2e 100644 --- a/src/UniGetUI/MainWindow.xaml.cs +++ b/src/UniGetUI/MainWindow.xaml.cs @@ -167,20 +167,6 @@ private void ClearSubtitle() Title = "UniGetUI"; } - private void AddToSubtitle(string line) - { - if (AppSubTitle.Text.Length > 0) - AppSubTitle.Text += " - "; - AppSubTitle.Text += line; - Title = "UniGetUI - " + AppSubTitle.Text; - } - - private void ClearSubtitle() - { - AppSubTitle.Text = ""; - Title = "UniGetUI"; - } - private static void TransferOldSettingsFormats() { foreach (IPackageManager Manager in PEInterface.Managers) diff --git a/src/UniGetUI/Pages/DialogPages/DesktopShortcuts.xaml.cs b/src/UniGetUI/Pages/DialogPages/DesktopShortcuts.xaml.cs index 2c9d955c5..0f9b1cfdd 100644 --- a/src/UniGetUI/Pages/DialogPages/DesktopShortcuts.xaml.cs +++ b/src/UniGetUI/Pages/DialogPages/DesktopShortcuts.xaml.cs @@ -94,7 +94,7 @@ private void YesResetButton_Click(object sender, RoutedEventArgs e) ConfirmResetFlyout.Hide(); } - private void NoResetButton_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + private void NoResetButton_Click(object sender, RoutedEventArgs e) { ConfirmResetFlyout.Hide(); }