3

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?

GSerg
  • 76,472
  • 17
  • 159
  • 346
tmighty
  • 10,734
  • 21
  • 104
  • 218
  • It looks like Data depends upon the PowerSetting. Check out [this page](https://learn.microsoft.com/en-us/windows/win32/power/power-setting-guids). – Brian M Stafford Oct 01 '21 at 13:50
  • Try using `Long` for DWORD. Here is a [handy list](https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-basic-6/aa261773(v=vs.60)?redirectedfrom=MSDN) for your reference. – Brian M Stafford Oct 01 '21 at 14:06
  • @BrianMStafford Thank you . And how would UCHAR Data[1] be translated to VB6? – tmighty Oct 01 '21 at 19:25
  • @tmighty If you read the page on PowerSettings, you see statements like `The Data member is a DWORD that indicates the current monitor state` and then it lists the values to expect. I think this is what you are after. I've never played with this so I can't say for sure. – Brian M Stafford Oct 01 '21 at 20:31

1 Answers1

3

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.

Steeeve
  • 824
  • 1
  • 6
  • 14
  • Thank you very much. I have a slight problem: I can't test your code. For me, the WM_POWERBROADCAST event is not fired when my monitor goes to sleep. Does it work for you? – tmighty Oct 03 '21 at 17:55
  • @tmighty try using GUID_CONSOLE_DISPLAY_STATE instead of GUID_MONITOR_POWER_ON, if you are on Win8+, as stated in the [docs](https://learn.microsoft.com/en-us/windows/win32/power/power-setting-guids). I hope it helps. – Steeeve Oct 03 '21 at 19:02
  • @tmighty I can confirm, that under Windows 10 both GUID_MONITOR_POWER_ON and GUID_CONSOLE_DISPLAY_STATE work, if the displays got shut down by the power management of windows. If I shut them off manually (by pressing the power button on the displays), none of the events will be raised. But this would be another question... – Steeeve Oct 04 '21 at 16:41
  • Not for me. Could you post your entire code including the WindowProc function so that I can try it out? I just don't receive this event. I would love to test out your version. – tmighty Oct 04 '21 at 18:29
  • @tmighty The WindowProc function is already there. I've just added GUID_CONSOLE_DISPLAY_STATE for testing, but it doesn't matter. Both events were raised if the display is switched of by windows, none of them if I switch of the display manually. – Steeeve Oct 04 '21 at 19:32
  • @tmighty I have added the new code and sample output. My test project is very simple exe, there is only a Form with the code in the last block, everything else is in a module. Let me know, if you still have problems. – Steeeve Oct 05 '21 at 16:11
  • Thank you, but I can't get your code to compile. It says "Invalid use a AdressOf". The way that you have posted doesn't reflect how it looks on your side. Can you update your post? – tmighty Oct 05 '21 at 21:32
  • Invalid use of AdressOf means, your `WindowProc` procedure is not in a module. As I wrote, put only the codeblock titled 'Test Form in VB6' in a form, the rest in a single module. – Steeeve Oct 06 '21 at 04:42
  • Thank you very much! I have added Public OldProc& to me module, and it worked. Thank you again!!! – tmighty Oct 06 '21 at 16:18
  • Oh, i forgot a variable? I'm glad it finally works :) – Steeeve Oct 06 '21 at 16:20
  • I wanted to say Thank you again. Amazing code, works great!! – tmighty Nov 02 '21 at 12:59