mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-02-24 04:00:02 +01:00
add power management
Signed-off-by: Shawn Yuan <shuaiyuan@microsoft.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
136
src/modules/awake/Awake/Core/PowerSchemeManager.cs
Normal file
136
src/modules/awake/Awake/Core/PowerSchemeManager.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user