Search code examples
winapivb6

How do I define POWERBROADCAST_SETTING in VB6?


I would like to detect monitor states.

To do that, I register the WM_POWERBROADCAST message.

The lParam of this message contains PBT_POWERSETTINGCHANGE.

typedef struct {
  GUID  PowerSetting;
  DWORD DataLength;
  UCHAR Data[1];
} POWERBROADCAST_SETTING, *PPOWERBROADCAST_SETTING;

GUID is defined like this in VB6:

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

How are

  DWORD DataLength;
  UCHAR Data[1];

to be translated to VB6?


Solution

  • The UCHAR Data[1] member of the POWERBROADCAST_SETTING structure indicates an array of bytes which depends on the PowerSetting and DataLength member. According to the docs, the Data member can be a GUID or a DWORD. So the simplest way in VB6 would be to declare a structure for the fixed members and get the remaining data in a second step according to the PowerSetting member.

    Public Type Guid
        Data1 As Long
        Data2 As Integer
        Data3 As Integer
        Data4(0 To 7) As Byte
    End Type
    
    Private Type PowerBroadcastSetting
        PowerSetting As Guid
        DataLength As Long
    End Type
    

    The window procedure should look like this:

    Public Function WindowProc(ByVal hWnd As Long, ByVal iMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
        Dim g As Guid
        Dim L As Long
        Dim pbs As PowerBroadcastSetting
        
        Select Case iMsg
            Case WM_POWERBROADCAST
                Select Case wParam
                    Case PBT_APMPOWERSTATUSCHANGE
                        DebugPrint "PBT_APMPOWERSTATUSCHANGE"
                    Case PBT_APMRESUMEAUTOMATIC
                        DebugPrint "PBT_APMRESUMEAUTOMATIC"
                    Case PBT_APMRESUMESUSPEND
                        DebugPrint "PBT_APMRESUMESUSPEND"
                    Case PBT_APMSUSPEND
                        DebugPrint "PBT_APMSUSPEND"
                    Case PBT_POWERSETTINGCHANGE
                        CopyMemory pbs, ByVal lParam, Len(pbs)
                        DebugPrint "PBT_POWERSETTINGCHANGE " & GuidToString(pbs.PowerSetting)
                        Select Case GuidToString(pbs.PowerSetting)
                            Case GUID_POWERSCHEME_PERSONALITY
                                CopyMemory g, ByVal lParam + Len(pbs), 16
                                DebugPrint "New power scheme: " & GuidToString(g)
                            Case GUID_SESSION_DISPLAY_STATUS
                                CopyMemory L, ByVal lParam + Len(pbs), 4
                                DebugPrint "Display status: " & L
                            Case GUID_MONITOR_POWER_ON
                                CopyMemory L, ByVal lParam + Len(pbs), 4
                                DebugPrint "Primary Monitor state: " & L
                            Case GUID_CONSOLE_DISPLAY_STATE
                                CopyMemory L, ByVal lParam + Len(pbs), 4
                                DebugPrint "Console Display state: " & L
                        End Select
                End Select
                'An application should return TRUE if it processes this message.
                WindowProc = 1
                Exit Function
        End Select
        'Pass message to original window proc
        WindowProc = CallWindowProc(ProcOld, hWnd, iMsg, wParam, lParam)
    End Function
    

    Following API declarations are used:

    Public Const GWL_WNDPROC As Long = (-4)
    Private Const WM_POWERBROADCAST As Long = 536
    
    Public Type Guid
        Data1 As Long
        Data2 As Integer
        Data3 As Integer
        Data4(0 To 7) As Byte
    End Type
    
    Private Type PowerBroadcastSetting
        PowerSetting As Guid
        DataLength As Long
    End Type
    
    
    'Power status has changed.
    Private Const PBT_APMPOWERSTATUSCHANGE = 10
    
    'Operation is resuming automatically from a low-power state. This message is sent every time the system resumes.
    Private Const PBT_APMRESUMEAUTOMATIC As Long = 18
    
    'Operation is resuming from a low-power state. This message is sent after PBT_APMRESUMEAUTOMATIC if the resume is triggered by user input, such as pressing a key.
    Private Const PBT_APMRESUMESUSPEND As Long = 7
    
    'System is suspending operation.
    Private Const PBT_APMSUSPEND As Long = 4
    
    'A power setting change event has been received.
    Private Const PBT_POWERSETTINGCHANGE As Long = 32787
    
    'Power Setting GUIDs
    
    '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.
    Public Const GUID_POWERSCHEME_PERSONALITY As String = "{245D8541-3943-4422-B025-13A784F679B7}"
    
    'The display associated with the application's session has been powered on or off.
    '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 Const GUID_SESSION_DISPLAY_STATUS As String = "{2B84C20E-AD23-4DDF-93DB-05FFBD7EFCA5}"
    
    Public Const GUID_MONITOR_POWER_ON As String = "{02731015-4510-4526-99E6-E5A17EBD1AEA}"
    ' Windows 8 +
    Public Const GUID_CONSOLE_DISPLAY_STATE As String = "{6FE69556-704A-47A0-8F24-C28D936FDA47}"
    
    'Notifications are sent using WM_POWERBROADCAST messages with a wParam parameter of PBT_POWERSETTINGCHANGE.
    Public Const DEVICE_NOTIFY_WINDOW_HANDLE As Long = 0
    Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cbCopy As Long)
    Public Declare Function RegisterPowerSettingNotification Lib "user32.dll" (ByVal hRecipient As Long, PowerSettingGuid As Guid, ByVal Flags As Long) As Long
    Public Declare Function UnregisterPowerSettingNotification Lib "user32.dll" (ByVal Handle As Long) As Long
    Private Declare Function StringFromGUID2 Lib "ole32.dll" (rguid As Guid, ByVal lpsz As Long, ByVal cchMax As Long) As Long
    Private Declare Function CLSIDFromString Lib "ole32.dll" (ByVal lpsz As Long, pclsid As Guid) As Long
    Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
    Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    Private Declare Sub OutputDebugString Lib "kernel32" Alias "OutputDebugStringA" (ByVal lpOutputString As String)
    

    And the helper functions:

    Public Function GuidToString(g As Guid) As String
        Dim L As Long
        Dim b(0 To 77) As Byte
        
        'we have space for 38 unicode chars (guid incl. brackets) + terminating zero (78 bytes)
        L = StringFromGUID2(g, VarPtr(b(0)), 39)
        'strip terminating 0, convert to string
        GuidToString = Left(b, L - 1)
    End Function
    
    Public Function GuidFromString(ByVal gs As String) As Guid
        CLSIDFromString StrPtr(gs), GuidFromString
    End Function
    
    Public Sub DebugPrint(ByVal s As String)
        OutputDebugString s & vbCrLf
    End Sub
    

    Test Form in VB6:

    Option Explicit
    
    Private isSubclassed As Boolean
    Private hScheme As Long
    Private hDisplay As Long
    Private hMonitor As Long
    Private hConsole As Long
    
    Private Sub cmdRegister_Click()
        Unregister
        Register
    End Sub
    
    Private Sub cmdUnregister_Click()
        Unregister
    End Sub
    
    Private Sub Register()
        ProcOld = SetWindowLong(hWnd, GWL_WNDPROC, AddressOf WindowProc)
        isSubclassed = True
        MsgBox "Subclassed"
        'Register Power Events
        hScheme = RegisterPowerSettingNotification(hWnd, GuidFromString(GUID_POWERSCHEME_PERSONALITY), DEVICE_NOTIFY_WINDOW_HANDLE)
        hDisplay = RegisterPowerSettingNotification(hWnd, GuidFromString(GUID_SESSION_DISPLAY_STATUS), DEVICE_NOTIFY_WINDOW_HANDLE)
        hConsole = RegisterPowerSettingNotification(hWnd, GuidFromString(GUID_CONSOLE_DISPLAY_STATE), DEVICE_NOTIFY_WINDOW_HANDLE)
        MsgBox "Registered " & hScheme & " " & hDisplay & " " & hMonitor & " " & hConsole
    End Sub
    
    Private Sub Unregister()
        'Unregister Power Events
        If hScheme Then
            UnregisterPowerSettingNotification hScheme
            hScheme = 0
        End If
        
        If hDisplay Then
            UnregisterPowerSettingNotification hDisplay
            hDisplay = 0
        End If
        
        If hMonitor Then
            UnregisterPowerSettingNotification hMonitor
            hMonitor = 0
        End If
        If hConsole Then
            UnregisterPowerSettingNotification hConsole
            hConsole = 0
        End If
        'Unsubclass
        If isSubclassed Then
            SetWindowLong hWnd, GWL_WNDPROC, ProcOld
            isSubclassed = False
            MsgBox "Unsubclassed"
        End If
    End Sub
    
    Private Sub Form_Unload(Cancel As Integer)
        Unregister
    End Sub
    

    Edit: Added GUID_CONSOLE_DISPLAY_STATE. Here are the outputs caught with DebugView on Windows 10:

    Displays put in standby by the power management of Windows after inactivity:

    [7752] PBT_POWERSETTINGCHANGE {6FE69556-704A-47A0-8F24-C28D936FDA47}
    [7752] Console Display state: 2
    [7752] PBT_POWERSETTINGCHANGE {2B84C20E-AD23-4DDF-93DB-05FFBD7EFCA5}
    [7752] Display status: 2

    After 15 Seconds:

    [7752] PBT_POWERSETTINGCHANGE {6FE69556-704A-47A0-8F24-C28D936FDA47}
    [7752] Console Display state: 0
    [7752] PBT_POWERSETTINGCHANGE {02731015-4510-4526-99E6-E5A17EBD1AEA}
    [7752] Primary Monitor state: 0
    [7752] PBT_POWERSETTINGCHANGE {2B84C20E-AD23-4DDF-93DB-05FFBD7EFCA5}
    [7752] Display status: 0

    WakeUp:

    [7752] PBT_POWERSETTINGCHANGE {6FE69556-704A-47A0-8F24-C28D936FDA47}
    [7752] Console Display state: 1
    [7752] PBT_POWERSETTINGCHANGE {02731015-4510-4526-99E6-E5A17EBD1AEA}
    [7752] Primary Monitor state: 1
    [7752] PBT_POWERSETTINGCHANGE {2B84C20E-AD23-4DDF-93DB-05FFBD7EFCA5}
    [7752] Display status: 1

    If you switch off the displays manually, there will be no notifications, at least with my hardware. Not sure, if on other systems the events will be raised.