Skip to content

Commit

Permalink
Sleep DWM and XAML process when app is in background, and resume them…
Browse files Browse the repository at this point in the history
… once the app window is shown again
  • Loading branch information
marticliment committed Feb 8, 2025
1 parent 5fcde1a commit 5b7ac71
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 115 deletions.
140 changes: 140 additions & 0 deletions src/UniGetUI.Core.Tools/DWMThreadHelper.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
24 changes: 16 additions & 8 deletions src/UniGetUI/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -364,6 +369,9 @@ public void ProcessCommandLineParameters()

public new void Activate()
{
DWMThreadHelper.ChangeState_DWM(false);
DWMThreadHelper.ChangeState_XAML(false);

if (!HasLoadedLastGeometry)
{
RestoreGeometry();
Expand Down
107 changes: 0 additions & 107 deletions src/UniGetUI/ThreadHelper.cs

This file was deleted.

0 comments on commit 5b7ac71

Please sign in to comment.