add power management

Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
This commit is contained in:
Shawn Yuan
2025-09-17 17:09:01 +08:00
parent c818859103
commit baf42abba4
2 changed files with 237 additions and 6 deletions

View File

@@ -63,9 +63,16 @@ namespace Awake.Core
// Foreground usage tracker instance (lifecycle managed by Program)
internal static ForegroundUsageTracker? UsageTracker { get; set; }
private static PowerSchemeManager powerSchemeManager;
// Power scheme auto-switch (activity mode)
private static string? _originalPowerSchemeGuid;
private static bool _powerSchemeSwitched;
static Manager()
{
_tokenSource = new CancellationTokenSource();
powerSchemeManager = new PowerSchemeManager();
_stateQueue = [];
ModuleSettings = new SettingsUtils();
}
@@ -207,6 +214,94 @@ namespace Awake.Core
forceAdd ? TrayIconAction.Add : TrayIconAction.Update);
}
private static void CaptureOriginalPowerScheme()
{
try
{
powerSchemeManager.RefreshSchemes();
_originalPowerSchemeGuid = powerSchemeManager
.GetAllSchemes()
.FirstOrDefault(s => s.IsActive)?.PSGuid;
Logger.LogInfo($"Captured original power scheme: {_originalPowerSchemeGuid ?? "UNKNOWN"}");
}
catch (Exception ex)
{
Logger.LogWarning($"Failed to capture original power scheme: {ex.Message}");
}
}
private static void SwitchToHighestPerformanceIfNeeded()
{
if (_powerSchemeSwitched)
{
return;
}
try
{
powerSchemeManager.RefreshSchemes();
var highest = powerSchemeManager.GetHighestPerformanceScheme();
if (highest == null)
{
Logger.LogInfo("No power schemes found when attempting high performance switch.");
return;
}
if (highest.IsActive)
{
Logger.LogInfo("Already on highest performance scheme no switch needed.");
return;
}
if (_originalPowerSchemeGuid == null)
{
CaptureOriginalPowerScheme();
}
if (powerSchemeManager.SwitchScheme(highest.PSGuid))
{
_powerSchemeSwitched = true;
Logger.LogInfo($"Switched to highest performance scheme: {highest.Name} ({highest.PSGuid})");
}
else
{
Logger.LogWarning($"Failed to switch to highest performance scheme: {highest.Name} ({highest.PSGuid})");
}
}
catch (Exception ex)
{
Logger.LogWarning($"Exception while attempting to switch power scheme: {ex.Message}");
}
}
private static void RestoreOriginalPowerSchemeIfNeeded()
{
if (!_powerSchemeSwitched || string.IsNullOrWhiteSpace(_originalPowerSchemeGuid))
{
return;
}
try
{
if (powerSchemeManager.SwitchScheme(_originalPowerSchemeGuid))
{
Logger.LogInfo($"Restored original power scheme: {_originalPowerSchemeGuid}");
}
else
{
Logger.LogWarning($"Failed to restore original power scheme: {_originalPowerSchemeGuid}");
}
}
catch (Exception ex)
{
Logger.LogWarning($"Exception restoring original power scheme: {ex.Message}");
}
finally
{
_powerSchemeSwitched = false;
}
}
internal static void SetIndefiniteKeepAwake(bool keepDisplayOn = false, int processId = 0, [CallerMemberName] string callerName = "")
{
PowerToysTelemetry.Log.WriteEvent(new Telemetry.AwakeIndefinitelyKeepAwakeEvent());
@@ -424,15 +519,11 @@ namespace Awake.Core
internal static void CompleteExit(int exitCode)
{
SetPassiveKeepAwake(updateSettings: false);
RestoreOriginalPowerSchemeIfNeeded();
if (TrayHelper.WindowHandle != IntPtr.Zero)
{
// Delete the icon.
TrayHelper.SetShellIcon(TrayHelper.WindowHandle, string.Empty, null, TrayIconAction.Delete);
// Close the message window that we used for the tray.
Bridge.SendMessage(TrayHelper.WindowHandle, Native.Constants.WM_CLOSE, 0, 0);
Bridge.DestroyWindow(TrayHelper.WindowHandle);
}
@@ -611,6 +702,9 @@ namespace Awake.Core
IsDisplayOn = keepDisplayOn;
SetModeShellIcon();
// Capture original scheme before any switch
CaptureOriginalPowerScheme();
TimeSpan sampleInterval = TimeSpan.FromSeconds(_activitySample);
Observable.Interval(sampleInterval).Subscribe(
@@ -648,6 +742,7 @@ namespace Awake.Core
{
_activityLastHigh = DateTimeOffset.Now;
_stateQueue.Add(ComputeAwakeState(_activityKeepDisplay));
SwitchToHighestPerformanceIfNeeded();
}
TrayHelper.SetShellIcon(
@@ -661,6 +756,7 @@ namespace Awake.Core
Logger.LogInfo("Activity thresholds not met within timeout window. Ending activity mode.");
_activityActive = false;
CancelExistingThread();
RestoreOriginalPowerSchemeIfNeeded();
if (IsUsingPowerToysConfig)
{
@@ -668,7 +764,6 @@ namespace Awake.Core
}
else
{
Logger.LogInfo("Exiting after activity mode idle.");
CompleteExit(Environment.ExitCode);
}
}

View File

@@ -0,0 +1,136 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.RegularExpressions;
namespace Awake.Core
{
public class PowerSchemeManager
{
private readonly List<PowerScheme> _schemes = new();
public PowerSchemeManager()
{
RefreshSchemes();
}
public void RefreshSchemes()
{
_schemes.Clear();
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "powercfg",
Arguments = "/L",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true,
},
};
process.Start();
string output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
var matches = Regex.Matches(output, @"Power Scheme GUID: ([a-fA-F0-9\-]+)\s+\((.+?)\)(\s+\*)?");
foreach (Match match in matches)
{
_schemes.Add(new PowerScheme
{
PSGuid = match.Groups[1].Value,
Name = match.Groups[2].Value,
IsActive = match.Groups[3].Value.Contains('*'),
});
}
// Rank schemes by performance (descending)
_schemes.Sort((a, b) => GetScore(b).CompareTo(GetScore(a)));
}
/// <summary>
/// Returns all power schemes sorted by performance (highest first).
/// </summary>
public IReadOnlyList<PowerScheme> GetAllSchemes() => _schemes;
/// <summary>
/// Returns the highest performance scheme currently available (may already be active).
/// </summary>
public PowerScheme? GetHighestPerformanceScheme()
{
if (_schemes.Count == 0)
{
RefreshSchemes();
}
return _schemes.Count == 0 ? null : _schemes[0];
}
public bool SwitchScheme(string psGuid)
{
try
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "powercfg",
Arguments = $"/setactive {psGuid}",
UseShellExecute = false,
CreateNoWindow = true,
},
};
process.Start();
process.WaitForExit();
RefreshSchemes();
return true;
}
catch
{
return false;
}
}
private static int GetScore(PowerScheme scheme)
{
// Heuristic based on name (multi-language basic keywords).
string name = scheme.Name.ToLowerInvariant();
// High performance indicators
if (name.Contains("ultimate") || name.Contains("ultra"))
{
return 380;
}
if (name.Contains("high"))
{
return 310;
}
if (name.Contains("balanced"))
{
return 200;
}
if (name.Contains("saver"))
{
return 120;
}
// Default for unknown custom plans.
return 180;
}
public class PowerScheme
{
public string PSGuid { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public bool IsActive { get; set; }
}
}
}