using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
using Vanara.Collections;
using Vanara.Extensions;
using Vanara.InteropServices;
using Vanara.PInvoke;
using static Vanara.PInvoke.Kernel32;
using static Vanara.PInvoke.PowrProf;
using static Vanara.PInvoke.User32;
namespace Vanara.Diagnostics
{
/// Indicates the status of the battery.
public enum BatteryStatus
{
/// The battery or battery controller is not present.
NotPresent,
/// The battery is discharging.
Discharging,
/// The battery is idle.
Idle,
/// The battery is charging.
Charging
}
/// Specifies the status of battery saver.
public enum EnergySaverStatus
{
/// Battery saver is off permanently or the device is plugged in.
Disabled,
/// Battery saver is off now, but ready to turn on automatically.
Off,
/// Battery saver is on. Save energy where possible.
On
}
/// Specifies the power capabilities of a device.
[Flags]
public enum PowerCapabilities
{
/// There is a system power button.
PowerButtonPresent = 1 << 0,
/// There is a system sleep button.
SleepButtonPresent = 1 << 1,
/// There is a lid switch.
LidPresent = 1 << 2,
/// The operating system supports sleep state S1.
SystemS1 = 1 << 3,
/// The operating system supports sleep state S2.
SystemS2 = 1 << 4,
/// The operating system supports sleep state S3.
SystemS3 = 1 << 5,
/// The operating system supports sleep state S4 (hibernation).
SystemS4 = 1 << 6,
/// The operating system supports power off state S5 (soft off).
SystemS5 = 1 << 7,
/// The system hibernation file is present.
HiberFilePresent = 1 << 8,
/// The system supports wake capabilities.
FullWake = 1 << 9,
/// The system supports video display dimming capabilities.
VideoDimPresent = 1 << 10,
/// The system supports APM BIOS power management features.
ApmPresent = 1 << 11,
/// There is an uninterruptible power supply (UPS).
UpsPresent = 1 << 12,
/// The system supports thermal zones.
ThermalControl = 1 << 13,
/// The system supports processor throttling.
ProcessorThrottle = 1 << 14,
/// The system supports the hybrid sleep state.
FastSystemS4 = 1 << 15,
///
/// The system supports fast startup (aka: hiberboot, hybrid boot, or hybrid shutdown) which is a setting that helps your PC start
/// up faster after shutdown.
///
Hiberboot = 1 << 16,
///
/// The platform has support for ACPI wake alarm devices. For more details on wake alarm devices, please see the ACPI specification
/// section 9.18.
///
WakeAlarmPresent = 1 << 17,
/// The system supports the S0 low power idle model.
AoAc = 1 << 18,
/// The system supports allowing the removal of power to fixed disk devices.
DiskSpinDown = 1 << 19,
///
AoAcConnectivitySupported = 1 << 20,
/// There are one or more batteries in the system.
SystemBatteriesPresent = 1 << 21,
/// The system batteries are short-term. Short-term batteries are used in uninterruptible power supplies (UPS).
BatteriesAreShortTerm = 1 << 22
}
/// Represents the device's power supply status.
public enum PowerSupplyStatus
{
/// The device has no power supply.
NotPresent,
/// The device has an inadequate power supply.
Inadequate,
/// The device has an adequate power supply.
Adequate
}
///
/// Provides access to information about a device's battery and power supply status and configuration. This extends the capabilities
/// Windows.System.Power.PowerManager to include more detail, schemes and devices.
///
public static class PowerManager
{
private static readonly SystemPowerEventHandler Instance;
static PowerManager()
{
Instance = new SystemPowerEventHandler();
}
///
/// Occurs when the system is entering or exiting away mode. The Data property is a DWORD that indicates the current away mode state.
///
/// 0x0 - The computer is exiting away mode.
/// 0x1 - The computer is entering away mode.
///
///
public static event EventHandler> AwayModeChanged
{
add => Instance.AddEvent(GUID_SYSTEM_AWAYMODE, value);
remove => Instance.RemoveEvent(GUID_SYSTEM_AWAYMODE, value);
}
///
/// Occurs when the remaining battery capacity has changed. The granularity varies from system to system but the finest granularity
/// is 1 percent. The Data member is a DWORD that indicates the current battery capacity remaining as a percentage from 0 through 100.
///
public static event EventHandler> BatteryCapacityChanged
{
add => Instance.AddEvent(GUID_BATTERY_PERCENTAGE_REMAINING, value);
remove => Instance.RemoveEvent(GUID_BATTERY_PERCENTAGE_REMAINING, value);
}
///
///
/// Occurs when the battery saver has been turned off or on in response to changing power conditions. This notification is useful
/// for components that participate in energy conservation. These applications should register for this notification and save power
/// when battery saver is on.
///
/// The Data member is a DWORD that indicates battery saver state.
///
/// 0x0 - Battery saver is off.
/// 0x1 - Battery saver is on. Save energy where possible.
///
///
public static event EventHandler> BatterySaverStatusChanged
{
add => Instance.AddEvent(GUID_POWER_SAVING_STATUS, value);
remove => Instance.RemoveEvent(GUID_POWER_SAVING_STATUS, value);
}
///
/// Occurs when the current monitor's display state has changed.
///
/// Windows 7, Windows Server 2008 R2, Windows Vista and Windows Server 2008: This notification is available
/// starting with Windows 8 and Windows Server 2012.
///
/// The Data member is a DWORD with one of the following values.
///
/// 0x0 - The display is off.
/// 0x1 - The display is on.
/// 0x2 - The display is dimmed.
///
///
public static event EventHandler> CurrentMonitorDisplayStateChanged
{
add => Instance.AddEvent(GUID_CONSOLE_DISPLAY_STATE, value);
remove => Instance.RemoveEvent(GUID_CONSOLE_DISPLAY_STATE, value);
}
///
///
/// The user status associated with any session has changed. This represents the combined status of user presence across all local
/// and remote sessions on the system.
///
///
/// This notification is sent only services and other programs running in session 0. User-mode applications should register for
/// GUID_SESSION_USER_PRESENCE instead.
///
///
/// Windows 7, Windows Server 2008 R2, Windows Vista and Windows Server 2008: This notification is available
/// starting with Windows 8 and Windows Server 2012.
///
/// The Data member is a DWORD with one of the following values.
///
/// PowerUserPresent (0) - The user is present in any local or remote session on the system.
/// PowerUserInactive (2) - The user is not present in any local or remote session on the system.
///
///
public static event EventHandler> GlobalUserStatusChanged
{
add => Instance.AddEvent(GUID_GLOBAL_USER_PRESENCE, value);
remove => Instance.RemoveEvent(GUID_GLOBAL_USER_PRESENCE, value);
}
///
///
/// The active power scheme personality has changed. All power schemes map to one of these personalities. The Data member is a GUID
/// that indicates the new active power scheme personality.
///
///
/// GUID_MIN_POWER_SAVINGS (8C5E7FDA-E8BF-4A96-9A85-A6E23A8C635C)
/// High Performance - The scheme is designed to deliver maximum performance at the expense of power consumption savings.
///
/// GUID_MAX_POWER_SAVINGS (A1841308-3541-4FAB-BC81-F71556F20B4A)
///
/// Power Saver - The scheme is designed to deliver maximum power consumption savings at the expense of system performance and responsiveness.
///
///
/// GUID_TYPICAL_POWER_SAVINGS (381B4222-F694-41F0-9685-FF5BB260DF2E)
/// Automatic - The scheme is designed to automatically balance performance and power consumption savings.
///
///
///
public static event EventHandler> PowerSchemePersonalityChanged
{
add => Instance.AddEvent(GUID_POWERSCHEME_PERSONALITY, value);
remove => Instance.RemoveEvent(GUID_POWERSCHEME_PERSONALITY, value);
}
///
/// Occurs when there is a change in the power status of the computer, such as a switch from battery power to A/C. The system also
/// broadcasts this event when remaining battery power slips below the threshold specified by the user or if the battery power
/// changes by a specified percentage.
///
public static event EventHandler PowerStatusChanged;
///
///
/// The primary system monitor has been powered on or off. This notification is useful for components that actively render content
/// to the display device, such as media visualization. These applications should register for this notification and stop rendering
/// graphics content when the monitor is off to reduce system power consumption. The Data member is a DWORD that indicates the
/// current monitor state.
///
///
/// 0x0 - The monitor is off.
/// 0x1 - The monitor is on.
///
///
/// Windows 8 and Windows Server 2012: New applications should use GUID_CONSOLE_DISPLAY_STATE instead of this notification.
///
///
public static event EventHandler> PrimarySystemMonitorPowerChanged
{
add => Instance.AddEvent(GUID_MONITOR_POWER_ON, value);
remove => Instance.RemoveEvent(GUID_MONITOR_POWER_ON, value);
}
///
/// Occurs when the system is resuming from sleep or hibernation. This event is delivered every time the system resumes and does not
/// indicate whether a user is present.
///
public static event EventHandler ResumedAfterSleep;
/// Occurs when the system has resumed operation after being suspended.
public static event EventHandler ResumedAfterSuspend;
///
/// Occurs when the display associated with the application's session has been powered on or off.
///
/// Windows 7, Windows Server 2008 R2, Windows Vista and Windows Server 2008: This notification is available
/// starting with Windows 8 and Windows Server 2012.
///
///
/// This notification is sent only to user-mode applications.Services and other programs running in session 0 do not receive this notification.
///
/// The Data member is a DWORD with one of the following values.
///
/// 0x0 - The display is off.
/// 0x1 - The display is on.
/// 0x2 - The display is dimmed.
///
///
public static event EventHandler> SessionDisplayStatusChanged
{
add => Instance.AddEvent(GUID_SESSION_DISPLAY_STATUS, value);
remove => Instance.RemoveEvent(GUID_SESSION_DISPLAY_STATUS, value);
}
///
/// The user status associated with the application's session has changed.
///
/// Windows 7, Windows Server 2008 R2, Windows Vista and Windows Server 2008: This notification is available
/// starting with Windows 8 and Windows Server 2012.
///
///
/// This notification is sent only to user-mode applications running in an interactive session. Services and other programs running
/// in session 0 should register for GUID_GLOBAL_USER_PRESENCE. The Data member is a DWORD with one of the
/// following values.
///
///
/// PowerUserPresent(0) - The user is providing input to the session.
/// PowerUserInactive (2) - The user activity timeout has elapsed with no interaction from the user.
///
///
public static event EventHandler> SessionUserStatusChanged
{
add => Instance.AddEvent(GUID_SESSION_USER_PRESENCE, value);
remove => Instance.RemoveEvent(GUID_SESSION_USER_PRESENCE, value);
}
/// Occurs when the computer is about to enter a suspended state.
public static event EventHandler Suspending;
///
/// Occurs when the system is busy. This indicates that the system will not be moving into an idle state in the near future and that
/// the current time is a good time for components to perform background or idle tasks that would otherwise prevent the computer
/// from entering an idle state.
///
/// There is no notification when the system is able to move into an idle state.The idle background task notification does not
/// indicate whether a user is present at the computer.The Data member has no information and can be ignored.
///
///
public static event EventHandler> SystemIsBusy
{
add => Instance.AddEvent(GUID_IDLE_BACKGROUND_TASK, value);
remove => Instance.RemoveEvent(GUID_IDLE_BACKGROUND_TASK, value);
}
///
///
/// Occurs when the system power source has changed. The Data member is a DWORD with values from the SYSTEM_POWER_CONDITION
/// enumeration that indicates the current power source.
///
///
/// 0 - Indicates the system is being powered by an AC power source.
/// 1 - Indicates the system is being powered by a DC power source.
///
/// 2 - Indicates the system is being powered by a short-term DC power source. For example, this would be the case if the system is
/// being powered by a short-term battery supply in a backing UPS system. When this value is received, the consumer should make
/// preparations for either a system hibernate or system shutdown.
///
///
///
public static event EventHandler> SystemPowerSourceChanged
{
add => Instance.AddEvent(GUID_ACDC_POWER_SOURCE, value);
remove => Instance.RemoveEvent(GUID_ACDC_POWER_SOURCE, value);
}
internal static event EventHandler LowBattery;
internal static event CancelEventHandler QueryStandby;
internal static event CancelEventHandler QuerySuspend;
internal static event EventHandler ResumedAfterFailure;
internal static event EventHandler ResumedAfterStandby;
internal static event EventHandler StandbyFailed;
internal static event EventHandler StandingBy;
internal static event EventHandler SuspendFailed;
/// Gets the device's battery status.
/// Returns a value.
public static BatteryStatus BatteryStatus => GetStatus().BatteryFlag switch
{
BATTERY_STATUS.BATTERY_CHARGING => BatteryStatus.Charging,
BATTERY_STATUS.BATTERY_NONE => BatteryStatus.NotPresent,
_ => GetStatus().ACLineStatus == AC_STATUS.AC_OFFLINE ? BatteryStatus.Discharging : BatteryStatus.Idle,
};
/// Gets flags indicating the system's power capabilities.
/// Returns a value.
public static PowerCapabilities DeviceCapabilities
{
get
{
SYSTEM_POWER_CAPABILITIES c = GetCapabilities();
PowerCapabilities ret = 0;
if (c.PowerButtonPresent) ret |= PowerCapabilities.PowerButtonPresent;
if (c.SleepButtonPresent) ret |= PowerCapabilities.SleepButtonPresent;
if (c.LidPresent) ret |= PowerCapabilities.LidPresent;
if (c.SystemS1) ret |= PowerCapabilities.SystemS1;
if (c.SystemS2) ret |= PowerCapabilities.SystemS2;
if (c.SystemS3) ret |= PowerCapabilities.SystemS3;
if (c.SystemS4) ret |= PowerCapabilities.SystemS4;
if (c.SystemS5) ret |= PowerCapabilities.SystemS5;
if (c.HiberFilePresent) ret |= PowerCapabilities.HiberFilePresent;
if (c.FullWake) ret |= PowerCapabilities.FullWake;
if (c.VideoDimPresent) ret |= PowerCapabilities.VideoDimPresent;
if (c.ApmPresent) ret |= PowerCapabilities.ApmPresent;
if (c.UpsPresent) ret |= PowerCapabilities.UpsPresent;
if (c.ThermalControl) ret |= PowerCapabilities.ThermalControl;
if (c.ProcessorThrottle) ret |= PowerCapabilities.ProcessorThrottle;
if (c.FastSystemS4) ret |= PowerCapabilities.FastSystemS4;
if (c.Hiberboot) ret |= PowerCapabilities.Hiberboot;
if (c.WakeAlarmPresent) ret |= PowerCapabilities.WakeAlarmPresent;
if (c.AoAc) ret |= PowerCapabilities.AoAc;
if (c.DiskSpinDown) ret |= PowerCapabilities.DiskSpinDown;
if (c.AoAcConnectivitySupported) ret |= PowerCapabilities.AoAcConnectivitySupported;
if (c.SystemBatteriesPresent) ret |= PowerCapabilities.SystemBatteriesPresent;
if (c.BatteriesAreShortTerm) ret |= PowerCapabilities.BatteriesAreShortTerm;
return ret;
}
}
/// Gets the device's battery saver status, indicating when to save energy.
/// Returns a value.
public static EnergySaverStatus EnergySaverStatus
{
get
{
SYSTEM_POWER_STATUS s = GetStatus();
return s.ACLineStatus == AC_STATUS.AC_ONLINE || s.BatteryFlag == BATTERY_STATUS.BATTERY_NONE ? EnergySaverStatus.Disabled : (s.SystemStatusFlag == 0 ? EnergySaverStatus.Off : EnergySaverStatus.On);
}
}
/// Gets a value indicating whether the system is on AC power (plugged in).
/// if on AC power; otherwise, .
public static bool OnACPower => GetStatus().ACLineStatus == AC_STATUS.AC_ONLINE;
/// Indicates the OEM's preferred power management profile for this device.
public static POWER_PLATFORM_ROLE PlatformRole => PInvokeClient.Windows8.IsPlatformSupported() ? PowerDeterminePlatformRoleEx(PowerPlatformRoleVersion.POWER_PLATFORM_ROLE_V2) : PowerDeterminePlatformRole();
/// A collection of the powered devices on the system. Filters on this list can be set as properties.
/// Returns a value.
public static PoweredDeviceCollection PoweredDevices { get; } = new PoweredDeviceCollection();
/// Gets the device's power supply status.
/// Returns a value.
public static PowerSupplyStatus PowerSupplyStatus => GetStatus().ACLineStatus switch
{
AC_STATUS.AC_ONLINE => PowerSupplyStatus.Adequate,
AC_STATUS.AC_LINE_BACKUP_POWER => PowerSupplyStatus.Inadequate,
_ => PowerSupplyStatus.NotPresent,
};
/// Gets the total percentage of charge remaining from all batteries connected to the device.
/// Returns a int? value from 0 to 100, or if the status is unknown.
public static int? RemainingChargePercent { get { var p = GetStatus().BatteryLifePercent; return p == 255 ? (int?)null : p; } }
/// Gets the total runtime remaining from all batteries connected to the device.
///
/// The total runtime remaining from all batteries connected to the device, or if the status is unknown or
/// connected to AC power.
///
public static TimeSpan? RemainingDischargeTime { get { var s = GetStatus().BatteryLifeTime; return s == uint.MaxValue ? (TimeSpan?)null : TimeSpan.FromSeconds(s); } }
/// Gets the collection of all power schemes on this device.
/// Returns a value.
public static PowerSchemeCollection Schemes { get; } = new PowerSchemeCollection();
internal static string NameForGuid(Guid guid) => typeof(PowrProf).GetFields(BindingFlags.Public | BindingFlags.Static).FirstOrDefault(i => i.FieldType == typeof(Guid) && guid.Equals(i.GetValue(null)))?.Name;
private static SYSTEM_POWER_CAPABILITIES GetCapabilities() => GetPwrCapabilities(out SYSTEM_POWER_CAPABILITIES c) ? c : default;
private static SYSTEM_POWER_STATUS GetStatus() => GetSystemPowerStatus(out SYSTEM_POWER_STATUS s) ? s : default;
private class SystemPowerEventHandler : SystemEventHandler
{
private readonly Dictionary regs = new Dictionary();
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
lock (regs)
{
foreach (SafeHPOWERSETTINGNOTIFY reg in regs.Values)
reg.Dispose();
regs.Clear();
}
}
}
protected override bool MessageFilter(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam, out IntPtr lReturn)
{
System.Diagnostics.Debug.WriteLine($"Message={msg}");
lReturn = default;
switch (msg)
{
case (uint)WindowMessage.WM_POWERBROADCAST:
var pbt = (PowerBroadcastType)wParam.ToInt32();
System.Diagnostics.Debug.WriteLine($"WM_POWERBROADCAST : {pbt}");
switch (pbt)
{
case PowerBroadcastType.PBT_APMQUERYSUSPEND:
var qscancel = new CancelEventArgs();
QuerySuspend?.Invoke(null, qscancel);
lReturn = qscancel.Cancel ? BROADCAST_QUERY_DENY : new IntPtr(-1);
return true;
case PowerBroadcastType.PBT_APMQUERYSTANDBY:
var qsbcancel = new CancelEventArgs();
QueryStandby?.Invoke(null, qsbcancel);
lReturn = qsbcancel.Cancel ? BROADCAST_QUERY_DENY : new IntPtr(-1);
return true;
case PowerBroadcastType.PBT_APMQUERYSUSPENDFAILED:
SuspendFailed?.Invoke(null, EventArgs.Empty);
break;
case PowerBroadcastType.PBT_APMQUERYSTANDBYFAILED:
StandbyFailed?.Invoke(null, EventArgs.Empty);
break;
case PowerBroadcastType.PBT_APMSUSPEND:
Suspending?.Invoke(null, EventArgs.Empty);
break;
case PowerBroadcastType.PBT_APMSTANDBY:
StandingBy?.Invoke(null, EventArgs.Empty);
break;
case PowerBroadcastType.PBT_APMRESUMECRITICAL:
ResumedAfterFailure?.Invoke(null, EventArgs.Empty);
break;
case PowerBroadcastType.PBT_APMRESUMESUSPEND:
ResumedAfterSuspend?.Invoke(null, EventArgs.Empty);
break;
case PowerBroadcastType.PBT_APMRESUMESTANDBY:
ResumedAfterStandby?.Invoke(null, EventArgs.Empty);
break;
case PowerBroadcastType.PBT_APMBATTERYLOW:
LowBattery?.Invoke(null, EventArgs.Empty);
break;
case PowerBroadcastType.PBT_APMPOWERSTATUSCHANGE:
PowerStatusChanged?.Invoke(null, EventArgs.Empty);
break;
case PowerBroadcastType.PBT_APMOEMEVENT:
break;
case PowerBroadcastType.PBT_APMRESUMEAUTOMATIC:
ResumedAfterSleep?.Invoke(null, EventArgs.Empty);
break;
case PowerBroadcastType.PBT_POWERSETTINGCHANGE:
POWERBROADCAST_SETTING setting = lParam.ToStructure();
switch (setting.PowerSetting)
{
case var g when g == GUID_IDLE_BACKGROUND_TASK:
RaiseEvent(g);
break;
case var g when g == GUID_POWERSCHEME_PERSONALITY:
RaiseEvent(g, setting);
break;
case var g when g == GUID_ACDC_POWER_SOURCE:
RaiseEvent(g, setting);
break;
//case var g when g == GUID_CONSOLE_DISPLAY_STATE:
//case var g when g == GUID_BATTERY_PERCENTAGE_REMAINING:
//case var g when g == GUID_GLOBAL_USER_PRESENCE:
//case var g when g == GUID_MONITOR_POWER_ON:
//case var g when g == GUID_POWER_SAVING_STATUS:
//case var g when g == GUID_SESSION_DISPLAY_STATUS:
//case var g when g == GUID_SESSION_USER_PRESENCE:
//case var g when g == GUID_SYSTEM_AWAYMODE:
default:
RaiseEvent(setting.PowerSetting, setting);
break;
}
break;
default:
break;
}
break;
}
return false;
}
protected override void OnEventAdd(Guid key)
{
lock (regs)
regs.Add(key, RegisterNotification(key));
}
protected override void OnEventRemove(Guid key)
{
lock (regs)
{
if (regs.TryGetValue(key, out SafeHPOWERSETTINGNOTIFY reg))
{
reg.Dispose();
regs.Remove(key);
}
}
}
private void RaiseEvent(Guid key)
{
if (HasKey(key))
RaiseEvent(key, null, new PowerEventArgs