dahall / Vanara

A set of .NET libraries for Windows implementing PInvoke calls to many native Windows APIs with supporting wrappers.
MIT License
1.81k stars 195 forks source link

[Question] PowrProf.PowerSettingRegisterNotification #122

Closed Huntk23 closed 4 years ago

Huntk23 commented 4 years ago

First of all, excellent library/wrappers. Thank you for this!

I have been stuck trying to figure out why I cannot get PowrProf.PowerSettingRegisterNotification to work. I am trying to read/set a power setting in Win32.

Specifically: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfoa SPI_GETPOWEROFFTIMEOUT & SPI_SETPOWEROFFTIMEOUT

Both of these are not supported and references GUID_VIDEO_POWERDOWN_TIMEOUT that says to use RegisterPowerSettingsNotification: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerpowersettingnotification

I found these under your PowrProf wrapper and have been attempting to get it work. So far, I receive a good message back from PowrProf.PowerSettingRegisterNotification:

ERROR_SUCCESS: The operation completed successfully.

I may not be creating the IntPtr for the recipient correctly? I may not be fully understanding how to utilize the wrapper without reinventing the wheel. I haven't worked much with Win32/PInvoke, but not getting far from trial/error and example research. The simple console application example I have abruptly closes after a couple of seconds. Virtually not making it to the while loop to give me time to change the setting in Windows and print the changed value:

class Program
{
    public static PowrProf.DeviceNotifyCallbackRoutine DeviceNotifyCallbackRoutine { get; set; }

    private static uint HandlerCallback(IntPtr context, uint eventType, IntPtr setting)
    {
        Console.WriteLine($"Setting value changed: {setting}");

        return 0;
    }

    static void Main()
    {
        DeviceNotifyCallbackRoutine = HandlerCallback;

        var deviceNotifySubscribeParameters = new PowrProf.DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
        {
            Callback = Marshal.GetFunctionPointerForDelegate(DeviceNotifyCallbackRoutine),
            Context = IntPtr.Zero
        };

        var success = PowrProf.PowerSettingRegisterNotification(PowrProf.GUID_VIDEO_POWERDOWN_TIMEOUT, PowrProf.DEVICE_NOTIFY.DEVICE_NOTIFY_CALLBACK, deviceNotifySubscribeParameters.Callback, out var powerNotification);

        Console.WriteLine(success);
        Console.WriteLine($"IsInvalid: {powerNotification.IsInvalid}, IsClosed: {powerNotification.IsClosed}, IsNull: {powerNotification.IsNull}");

        int counter = 0;
        while (!powerNotification.IsInvalid)
        {
            Thread.Sleep(3000);
            if (++counter == 10) break;
        }

        if (!powerNotification.IsNull)
        {
            PowrProf.PowerSettingUnregisterNotification(powerNotification);
        }

        powerNotification.Close();
        powerNotification.Dispose();

        Console.ReadKey();
}
dahall commented 4 years ago

I've looked into your question and apologize. There is both a bug and a few omissions that are making this hard for you. I've corrected them and committed the updates. They will get released in the next version, due shortly. Here is the function to get the value you want.

public uint GetVideoPowerdownTimeout()
{
   uint timeOut = 0;
   var evt = new AutoResetEvent(false);
   PowerSettingRegisterNotification(GUID_VIDEO_POWERDOWN_TIMEOUT, DEVICE_NOTIFY.DEVICE_NOTIFY_CALLBACK, new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS { Callback = PowerSettingFunc }, out var powerNotification).ThrowIfFailed();
   evt.WaitOne(1000);
   PowerSettingUnregisterNotification(powerNotification).ThrowIfFailed();
   return timeOut;

   Win32Error PowerSettingFunc(IntPtr Context, uint Type, IntPtr Setting)
   {
      if (Type == (uint)User32.PowerBroadcastType.PBT_POWERSETTINGCHANGE && Setting != IntPtr.Zero)
      {
         var pbSetting = Setting.ToStructure<User32.POWERBROADCAST_SETTING>();
         if (pbSetting.DataLength == Marshal.SizeOf(typeof(uint)))
            timeOut = BitConverter.ToUInt32(pbSetting.Data, 0);
      }
      evt.Set();
      return Win32Error.ERROR_SUCCESS;
   }
}
Huntk23 commented 4 years ago

@dahall Thanks for your swift response and fix! I look forward to the next version release to continue tinkering.

dahall commented 4 years ago

You can pull the 3.2.8 build now as prerelease from AppVeyor's project NuGet source at https://ci.appveyor.com/nuget/vanara-prerelease.