PowerStatus event
by
, 9-Nov-2013 at 10:58 AM (27011 Views)
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.
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.Code:External_Function WINAPI_RegisterForPowerSettings "RegisterPowerSettingNotification" User32.dll ; Handle hRecipient ; Pointer PowerSettingGuid ; UInteger Flags; Returns 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:
The cWinControl superclassCode:Define DEVICE_NOTIFY_WINDOW_HANDLE for 0 Define DEVICE_NOTIFY_SERVICE_HANDLE for 1
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:
Expansion of cPowerSettingsHandlerCode:Class cPowerSettingsHandler is a cWinControl End_Class
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.
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:Define WM_POWERBROADCAST for |CI$218
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 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 receiveCode: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
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.
It is now time to decide to what events the program should listen. The possible values are:Code:Procedure Page Integer iPageObject Forward Send Page iPageObject If (iPageObject <> 0) Begin Send RegisterWindow End End_Procedure Procedure RegisterWindow End_Procedure
These are documented in the MSDN article Power Setting GUIDs.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"
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.
This helper object make is possible to write a line of code as: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 GUID can now be used with the WINAPI_RegisterForPowerSettings function. For example:Code:Get UUIDFromString of oUUIDHandler C_GUID_BATTERY_PERCENTAGE_REMAINING to PowerSettingsGUID
Are we done now?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
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:
If the wParam parameter has this value the lParam is a pointer to a notification structure which needs to be defined as:Code:Define PBT_POWERSETTINGCHANGE for |CI$8013
The code will need to copy the memory from lParam to a variable of the tPOWERBROADCAST_SETTING struct.Code:Struct tPOWERBROADCAST_SETTING tUUID PowerSetting UInteger DataLength UChar[1] Data End_Struct
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:If (wParam = PBT_POWERSETTINGCHANGE) Begin Move (MemCopy (AddressOf (PowerBroadCastSettingData), lParam, SizeOfType (tPOWERBROADCAST_SETTING))) to iRetval End
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:Get UUIDToString of oUUIDHandler PowerBroadCastSettingData.PowerSetting to sGUID
The constant C_GUID_BATTERY_PERCENTAGE_REMAINING (and others) are listed above.Code:Showln (CurrentDateTime ()) ': C_GUID_BATTERY_PERCENTAGE_REMAINING:' PowerBroadCastSettingData.Data[0] '%'
The full testing code I wrote is:
An object of the class can be created in the main panel.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
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.