View RSS Feed

Development Team Blog

PowerStatus event

Rating: 10 votes, 4.90 average.
At the Data Access forums I found a question if it was possible to respond when the computer enters the sleep modus. The topic draw my attention and I decided to play around a bit. In this blog you learn how to make respond code.

Notification event
Windows can send a notification message to your application when changes occur in the power status. To be able to receive the notification message the application needs to register itself for receiving these messages. This is done with the Windows API RegisterPowerSettingNotification function.
Code:
External_Function WINAPI_RegisterForPowerSettings "RegisterPowerSettingNotification" User32.dll ;
    Handle hRecipient ;
    Pointer PowerSettingGuid ;
    UInteger Flags;
    Returns Handle
The first argument can be a window handle which means a DataFlex object that creates a window handle needs to be created. This can be done in a button, a form or panel or so as they have after paging a window handle.

The second argument is a pointer to a GUID. A GUID is the same as an UUID and you should read the
Create a New Universally Unique IDentifier (UUID) blog I wrote in 2011 to see how they can be created and parsed as the power management functions make use of GUIDs, a lot. The GUID used here tells Windows what power notification messages needs to be send.

The third argument tells the Windows API if hReceipient is a window handle or that the process is a service. The value needs to be DEVICE_NOTIFY_WINDOW_HANDLE or DEVICE_NOTIFY_SERVICE_HANDLE. Define the constants via:
Code:
Define DEVICE_NOTIFY_WINDOW_HANDLE for 0
Define DEVICE_NOTIFY_SERVICE_HANDLE for 1
The cWinControl superclass
Mentioned above is that a control is needed that creates a window handle that can be send to Windows for receiving the notification message. While for a window handle the control could be based on an existing class in DataFlex it really needs to be a different one as the code needs to respond on an external message and adding this instruction to an existing class is not a good idea. A cWinControl sub-class is a good superclass as it is quite basic class and it is designed to register a Windows message and class. The base class would be:
Code:
Class cPowerSettingsHandler is a cWinControl
End_Class
Expansion of cPowerSettingsHandler
Creating an object of this class is OK but it won't receive notification messages as the window handle of the object is not passed to Windows. The class needs further expansion.

The MSDN article tells that Windows will send the message WM_POWERBROADCAST to the registered window handle. This message does not exist yet within the DataFlex product which means it needs to be defined in the code.
Code:
Define WM_POWERBROADCAST for |CI$218
The Windows notification message needs to be used in the object constructor event of the new cPowerSettingsHandler class. The following code does this in the end constructor.
Code:
Class cPowerSettingsHandler is a cWinControl
    Procedure OnWmPowerBroadCast Integer wParam Integer lParam
    End_Procedure

    Procedure End_Construct_Object
        Forward Send End_Construct_Object
        
        Set External_Message WM_POWERBROADCAST to (RefProc (OnWmPowerBroadCast))
    End_Procedure
End_Class
If above code was used in a subclass of a button or a form every object could receive the notification message and this is undesired, It is better to create a new class and register this class as a new Windows class using an external class message. The DataFlex side of the external class name is free as long as it does not exist yet. To make sure it is a DataFlex class I used the letters "DF" in the name. The Windows class name that can be used is called "static".
Code:
Class cPowerSettingsHandler is a cWinControl
    Procedure Construct_Object
        Set External_class_Name "cDFPowerSettingsHandler" to "static"
        
        Forward Send Construct_Object
    End_Procedure
    
    Procedure OnWmPowerBroadCast Integer wParam Integer lParam
    End_Procedure

    Procedure End_Construct_Object
        Forward Send End_Construct_Object
        
        Set External_Message WM_POWERBROADCAST to (RefProc (OnWmPowerBroadCast))
    End_Procedure
End_Class
Register which notifications we want to receive
The class is still not ready as it does not yet tell Windows that the Window handle should be used. To do that the RegisterPowerSettingNotification needs to be used. Because a Window handle is needed I decided to do start from the Page message.
Code:
Procedure Page Integer iPageObject
    Forward Send Page iPageObject
    
    If (iPageObject <> 0) Begin
        Send RegisterWindow
    End
End_Procedure

Procedure RegisterWindow
End_Procedure
It is now time to decide to what events the program should listen. The possible values are:
Code:
Define C_GUID_ACDC_POWER_SOURCE             for "5D3E9A59-E9D5-4B00-A6BD-FF34FF516548"
Define C_GUID_BATTERY_PERCENTAGE_REMAINING  for "A7AD8041-B45A-4CAE-87A3-EECBB468A9E1"
Define C_GUID_CONSOLE_DISPLAY_STATE         for "6FE69556-704A-47A0-8F24-C28D936FDA47"
Define C_GUID_GLOBAL_USER_PRESENCE          for "786E8A1D-B427-4344-9207-09E70BDCBEA9"
Define C_GUID_IDLE_BACKGROUND_TASK          for "515C31D8-F734-163D-A0FD-11A0-8C91E8F1"
Define C_GUID_MONITOR_POWER_ON              for "02731015-4510-4526-99E6-E5A17EBD1AEA" // should not be used anymore
Define C_GUID_POWERSCHEME_PERSONALITY       for "245D8541-3943-4422-B025-13A7-84F679B7"
Define C_GUID_MIN_POWER_SAVINGS             for "8C5E7FDA-E8BF-4A96-9A85-A6E23A8C635C"
Define C_GUID_MAX_POWER_SAVINGS             for "A1841308-3541-4FAB-BC81-F71556F20B4A"
Define C_GUID_TYPICAL_POWER_SAVINGS         for "381B4222-F694-41F0-9685-FF5BB260DF2E"
Define C_GUID_SESSION_DISPLAY_STATUS        for "2B84C20E-AD23-4ddf-93DB-05FFBD7EFCA5"
Define C_GUID_SESSION_USER_PRESENCE         for "3C0F4548-C03F-4c4d-B9F2-237EDE686376"
Define C_GUID_SYSTEM_AWAYMODE               for "98A7F580-01F7-48AA-9C0F-44352C29E5C0"
These are documented in the MSDN article Power Setting GUIDs.

If we want to get a notification message when the battery power (laptops) changes we should use the C_GUID_BATTERY_PERCENTAGE_REMAINING GUID. The above constants are all strings while the information needs to be passed as a GUID. The
Create a New Universally Unique IDentifier (UUID) blog contains a message to convert the string to a GUID. For that I decided to put a cUUIDHandler object inside the cPowerSettingsHandler class in the construct object event.
Code:
Class cPowerSettingsHandler is a cWinControl
    Procedure Construct_Object
        Set External_class_Name "cDFPowerSettingsHandler" to "static"
        
        Forward Send Construct_Object
        
        Object oUUIDHandler is a cUUIDHandler
        End_Object
    End_Procedure
End_Class
This helper object make is possible to write a line of code as:
Code:
Get UUIDFromString of oUUIDHandler C_GUID_BATTERY_PERCENTAGE_REMAINING to PowerSettingsGUID
This GUID can now be used with the WINAPI_RegisterForPowerSettings function. For example:
Code:
Procedure RegisterWindow
    Handle hWnd
    tUUIDEx PowerSettingsGUID
    Integer iRetval
    
    Get Window_Handle to hWnd
    Get UUIDFromString of oUUIDHandler C_GUID_BATTERY_PERCENTAGE_REMAINING to PowerSettingsGUID
    Move (WINAPI_RegisterForPowerSettings (hWnd, AddressOf (PowerSettingsGUID.UUID), DEVICE_NOTIFY_WINDOW_HANDLE)) to iRetval
End_Procedure
Are we done now?
No. The code inside the OnWmPowerBroadCast needs to be written. The documentation says that the wParam can have multiple values. For our class we are interested in PBT_POWERSETTINGCHANGE. Defined as:
Code:
Define PBT_POWERSETTINGCHANGE   for |CI$8013
If the wParam parameter has this value the lParam is a pointer to a notification structure which needs to be defined as:
Code:
Struct tPOWERBROADCAST_SETTING
    tUUID PowerSetting
    UInteger DataLength
    UChar[1] Data
End_Struct
The code will need to copy the memory from lParam to a variable of the tPOWERBROADCAST_SETTING struct.
Code:
If (wParam = PBT_POWERSETTINGCHANGE) Begin
    Move (MemCopy (AddressOf (PowerBroadCastSettingData), lParam, SizeOfType (tPOWERBROADCAST_SETTING))) to iRetval 
End
After the copy the PowerSetting member of the tPOWERBROADCAST_SETTING variable needs to be converted to a string again to compare. Conversion to string makes it possible to define GUID constants in the source code.
Code:
Get UUIDToString of oUUIDHandler PowerBroadCastSettingData.PowerSetting to sGUID
If the sGUID equals to C_GUID_BATTERY_PERCENTAGE_REMAINING the Data member is a dWord value containing the remaining percentage of batttery power. For testing I wrote:
Code:
Showln (CurrentDateTime ()) ': C_GUID_BATTERY_PERCENTAGE_REMAINING:' PowerBroadCastSettingData.Data[0] '%'
The constant C_GUID_BATTERY_PERCENTAGE_REMAINING (and others) are listed above.

The full testing code I wrote is:
Code:
Use Windows.pkg
Use cWinControl.pkg
Use cUUIDHandler.pkg

External_Function WINAPI_RegisterForPowerSettings "RegisterPowerSettingNotification" User32.dll ;
    Handle hRecipient ;
    Pointer PowerSettingGuid ;
    UInteger Flags;
    Returns Handle 
    
Define DEVICE_NOTIFY_WINDOW_HANDLE for 0
Define DEVICE_NOTIFY_SERVICE_HANDLE for 1

Define WM_POWERBROADCAST for |CI$218

Define PBT_POWERSETTINGCHANGE   for |CI$8013

// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373216(v=vs.85).aspx
Enum_List // SYSTEM_POWER_CONDITION 
    Define PoAc
    Define PoDc
    Define PoHot
    Define PoConditionMaximum
End_Enum_List

Define PowerUserPresent  for 0
Define PowerUserInactive for 2

// http://msdn.microsoft.com/en-us/library/windows/desktop/hh448380(v=vs.85).aspx
Define C_GUID_ACDC_POWER_SOURCE             for "5D3E9A59-E9D5-4B00-A6BD-FF34FF516548"
Define C_GUID_BATTERY_PERCENTAGE_REMAINING  for "A7AD8041-B45A-4CAE-87A3-EECBB468A9E1"
Define C_GUID_CONSOLE_DISPLAY_STATE         for "6FE69556-704A-47A0-8F24-C28D936FDA47"
Define C_GUID_GLOBAL_USER_PRESENCE          for "786E8A1D-B427-4344-9207-09E70BDCBEA9"
Define C_GUID_IDLE_BACKGROUND_TASK          for "515C31D8-F734-163D-A0FD-11A0-8C91E8F1"
Define C_GUID_MONITOR_POWER_ON              for "02731015-4510-4526-99E6-E5A17EBD1AEA" // should not be used anymore
Define C_GUID_POWERSCHEME_PERSONALITY       for "245D8541-3943-4422-B025-13A7-84F679B7"
Define C_GUID_MIN_POWER_SAVINGS             for "8C5E7FDA-E8BF-4A96-9A85-A6E23A8C635C"
Define C_GUID_MAX_POWER_SAVINGS             for "A1841308-3541-4FAB-BC81-F71556F20B4A"
Define C_GUID_TYPICAL_POWER_SAVINGS         for "381B4222-F694-41F0-9685-FF5BB260DF2E"
Define C_GUID_SESSION_DISPLAY_STATUS        for "2B84C20E-AD23-4ddf-93DB-05FFBD7EFCA5"
Define C_GUID_SESSION_USER_PRESENCE         for "3C0F4548-C03F-4c4d-B9F2-237EDE686376"
Define C_GUID_SYSTEM_AWAYMODE               for "98A7F580-01F7-48AA-9C0F-44352C29E5C0"

Struct tPOWERBROADCAST_SETTING
    tUUID PowerSetting
    UInteger DataLength
    UChar[1] Data
End_Struct

Class cPowerSettingsHandler is a cWinControl
    Procedure Construct_Object
        Set External_class_Name "cDFPowerSettingsHandler" to "static"
        
        Forward Send Construct_Object
        
        Object oUUIDHandler is a cUUIDHandler
        End_Object
    End_Procedure
    
    Procedure Page Integer iPageObject
        Forward Send Page iPageObject
        
        If (iPageObject <> 0) Begin
            Send RegisterWindow
        End
    End_Procedure
    
    Procedure RegisterWindow
        Handle hWnd
        tUUIDEx PowerSettingsGUID
        Integer iRetval
        
        Get Window_Handle to hWnd
        Get UUIDFromString of oUUIDHandler C_GUID_ACDC_POWER_SOURCE to PowerSettingsGUID
        Move (WINAPI_RegisterForPowerSettings (hWnd, AddressOf (PowerSettingsGUID.UUID), DEVICE_NOTIFY_WINDOW_HANDLE)) to iRetval
        Get UUIDFromString of oUUIDHandler C_GUID_BATTERY_PERCENTAGE_REMAINING to PowerSettingsGUID
        Move (WINAPI_RegisterForPowerSettings (hWnd, AddressOf (PowerSettingsGUID.UUID), DEVICE_NOTIFY_WINDOW_HANDLE)) to iRetval
        Get UUIDFromString of oUUIDHandler C_GUID_CONSOLE_DISPLAY_STATE to PowerSettingsGUID
        Move (WINAPI_RegisterForPowerSettings (hWnd, AddressOf (PowerSettingsGUID.UUID), DEVICE_NOTIFY_WINDOW_HANDLE)) to iRetval
        Get UUIDFromString of oUUIDHandler C_GUID_GLOBAL_USER_PRESENCE to PowerSettingsGUID
        Move (WINAPI_RegisterForPowerSettings (hWnd, AddressOf (PowerSettingsGUID.UUID), DEVICE_NOTIFY_WINDOW_HANDLE)) to iRetval
        Get UUIDFromString of oUUIDHandler C_GUID_IDLE_BACKGROUND_TASK to PowerSettingsGUID
        Move (WINAPI_RegisterForPowerSettings (hWnd, AddressOf (PowerSettingsGUID.UUID), DEVICE_NOTIFY_WINDOW_HANDLE)) to iRetval
        Get UUIDFromString of oUUIDHandler C_GUID_POWERSCHEME_PERSONALITY to PowerSettingsGUID
        Move (WINAPI_RegisterForPowerSettings (hWnd, AddressOf (PowerSettingsGUID.UUID), DEVICE_NOTIFY_WINDOW_HANDLE)) to iRetval
        Get UUIDFromString of oUUIDHandler C_GUID_SESSION_DISPLAY_STATUS to PowerSettingsGUID
        Move (WINAPI_RegisterForPowerSettings (hWnd, AddressOf (PowerSettingsGUID.UUID), DEVICE_NOTIFY_WINDOW_HANDLE)) to iRetval
        Get UUIDFromString of oUUIDHandler C_GUID_SESSION_USER_PRESENCE to PowerSettingsGUID
        Move (WINAPI_RegisterForPowerSettings (hWnd, AddressOf (PowerSettingsGUID.UUID), DEVICE_NOTIFY_WINDOW_HANDLE)) to iRetval
        Get UUIDFromString of oUUIDHandler C_GUID_SYSTEM_AWAYMODE to PowerSettingsGUID
        Move (WINAPI_RegisterForPowerSettings (hWnd, AddressOf (PowerSettingsGUID.UUID), DEVICE_NOTIFY_WINDOW_HANDLE)) to iRetval
    End_Procedure
    
    Procedure OnWmPowerBroadCast Integer wParam Integer lParam
        tPOWERBROADCAST_SETTING PowerBroadCastSettingData
        Integer iRetval
        String sGUID sPowerSchemePersonalityGUID
        tUUID PowerSchemePersonality
        
        Case Begin
            Case (wParam = PBT_POWERSETTINGCHANGE)
                Move (MemCopy (AddressOf (PowerBroadCastSettingData), lParam, SizeOfType (tPOWERBROADCAST_SETTING))) to iRetval
                Get UUIDToString of oUUIDHandler PowerBroadCastSettingData.PowerSetting to sGUID
                Case Begin
                    Case (sGUID = C_GUID_ACDC_POWER_SOURCE)
                        Show (CurrentDateTime ()) ': C_GUID_ACDC_POWER_SOURCE:' 
                        Case Begin
                            Case (PowerBroadCastSettingData.Data[0] = PoAc)
                                Show 'The computer is powered by an AC power source (or similar, such as a laptop powered by a 12V automotive adapter).'
                                Case Break
                            Case (PowerBroadCastSettingData.Data[0] = PoDc)
                                Show 'The computer is powered by an onboard battery power source.'
                                Case Break
                            Case (PowerBroadCastSettingData.Data[0] = PoHot)
                                Show 'The computer is powered by a short-term power source such as a UPS device.'
                                Case Break
                        Case End
                        Showln
                        Case Break
                    Case (sGUID = C_GUID_BATTERY_PERCENTAGE_REMAINING)
                        Showln (CurrentDateTime ()) ': C_GUID_BATTERY_PERCENTAGE_REMAINING:' PowerBroadCastSettingData.Data[0] '%'
                        Case Break
                    Case (sGUID = C_GUID_CONSOLE_DISPLAY_STATE)
                        Show (CurrentDateTime ()) ': C_GUID_CONSOLE_DISPLAY_STATE:'
                        Case Begin
                            Case (PowerBroadCastSettingData.Data[0] = 0)
                                Show 'Display is off'
                                Case Break
                            Case (PowerBroadCastSettingData.Data[0] = 1)
                                Show 'Display is on'
                                Case Break
                            Case (PowerBroadCastSettingData.Data[0] = 2)
                                Show 'Display is dimmed'
                                Case Break
                        Case End
                        Showln
                        Case Break
                    Case (sGUID = C_GUID_GLOBAL_USER_PRESENCE)
                        Show (CurrentDateTime ()) ': C_GUID_GLOBAL_USER_PRESENCE:'
                        Case Begin
                            Case (PowerBroadCastSettingData.Data[0] = PowerUserPresent)
                                Show 'The user is present in any local or remote session on the system.'
                                Case Break
                            Case (PowerBroadCastSettingData.Data[0] = PowerUserInactive)
                                Show 'The user is not present in any local or remote session on the system.'
                                Case Break
                        Case End
                        Showln
                        Case Break
                    Case (sGUID = C_GUID_IDLE_BACKGROUND_TASK)
                        Showln (CurrentDateTime ()) ': C_GUID_IDLE_BACKGROUND_TASK:'
                        Case Break
                    Case (sGUID = C_GUID_MONITOR_POWER_ON)
                        Show (CurrentDateTime ()) ': C_GUID_MONITOR_POWER_ON:' 
                        Case Begin
                            Case (PowerBroadCastSettingData.Data[0] = 0)
                                Show 'The monitor is off'
                                Case Break
                            Case (PowerBroadCastSettingData.Data[0] = 1)
                                Show 'The monitor is on'
                                Case Break
                        Case End
                        Showln
                        Case Break
                    Case (sGUID = C_GUID_POWERSCHEME_PERSONALITY)
                        Show (CurrentDateTime ()) ': C_GUID_POWERSCHEME_PERSONALITY:'
                        Move (MemCopy (AddressOf (PowerSchemePersonality), AddressOf (PowerBroadCastSettingData.Data[0]), SizeOfType (tUUID))) to iRetval
                        Get UUIDToString of oUUIDHandler PowerSchemePersonality to sPowerSchemePersonalityGUID
                        Case Begin
                            Case (sPowerSchemePersonalityGUID = C_GUID_MIN_POWER_SAVINGS)
                                Show 'High Performance - The scheme is designed to deliver maximum performance at the expense of power consumption savings.'
                                Case Break
                            Case (sPowerSchemePersonalityGUID = C_GUID_MAX_POWER_SAVINGS)
                                Show 'Power Saver - The scheme is designed to deliver maximum power consumption savings at the expense of system performance and responsiveness.'
                                Case Break
                            Case (sPowerSchemePersonalityGUID = C_GUID_TYPICAL_POWER_SAVINGS)
                                Show 'Automatic - The scheme is designed to automatically balance performance and power consumption savings.'
                                Case Break
                        Case End
                        Showln
                        Case Break
                    Case (sGUID = C_GUID_SESSION_DISPLAY_STATUS)
                        Show (CurrentDateTime ()) ': C_GUID_SESSION_DISPLAY_STATUS:'
                        Case Begin
                            Case (PowerBroadCastSettingData.Data[0] = 0)
                                Show 'Display is off'
                                Case Break
                            Case (PowerBroadCastSettingData.Data[0] = 1)
                                Show 'Display is on'
                                Case Break
                            Case (PowerBroadCastSettingData.Data[0] = 2)
                                Show 'Display is dimmed'
                                Case Break
                        Case End
                        Showln
                        Case Break
                    Case (sGUID = C_GUID_SESSION_USER_PRESENCE)
                        Show (CurrentDateTime ()) ': C_GUID_SESSION_USER_PRESENCE:'
                        Case Begin
                            Case (PowerBroadCastSettingData.Data[0] = PowerUserPresent)
                                Show 'The user is providing input to the session.'
                                Case Break
                            Case (PowerBroadCastSettingData.Data[0] = PowerUserInactive)
                                Show 'The user activity timeout has elapsed with no interaction from the user.'
                                Case Break
                        Case End
                        Showln
                        Case Break
                    Case (sGUID = C_GUID_SYSTEM_AWAYMODE)
                        Show (CurrentDateTime ()) ': C_GUID_SYSTEM_AWAYMODE:'
                        Case Begin
                            Case (PowerBroadCastSettingData.Data[0] = 0)
                                Show 'The computer is exiting away mode.'
                                Case Break
                            Case (PowerBroadCastSettingData.Data[0] = 1)
                                Show 'The computer is entering away mode.'
                                Case Break
                        Case End
                        Showln
                        Case Break
                Case End
            Case Break
        Case End
    End_Procedure
    
    Procedure End_Construct_Object
        Forward Send End_Construct_Object
        
        Set External_Message WM_POWERBROADCAST to (RefProc (OnWmPowerBroadCast))
    End_Procedure
End_Class
An object of the class can be created in the main panel.

Compiler errors with v17.1
To use the cUUIDHandler class and code posted in the blog requires a little adjustment when using it with revision 17.1 as a part of the code is present in the product. I would wish it was all present but it is not. The posted External_Function statements need to change their name to avoid compiler errors.

Comments

  1. Todd Forsberg's Avatar
    I have no need (at this time) for a PowerStatus class, but I really liked this blog. It cleanly demonstrates how to register a VDF object to listen to, and respond to windows events. Thanks Vincent.
  2. Gregg Finney's Avatar
    Nice work Vincent.
  3. Samuel Pizarro's Avatar
    Wonderful ! Realy nice work Vincent. Thanks very much!