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] 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 - } -}