Link to home
Start Free TrialLog in
Avatar of mdlines
mdlinesFlag for United Kingdom of Great Britain and Northern Ireland

asked on

VB code to detect monitor in standby (non wddm)

Hi
I need to programmatically (VB) detect when a non-wddm monitor has gone into standby (because of power save scheme). Is this even possible ?  Will this code also work in WIn XP ?  
Avatar of nffvrxqgrcfqvvc
nffvrxqgrcfqvvc

Hi,
You'll need to to subclass for WM_POWERBROADCAST. The callback used is your standard WindowProc. When the system is about to enter the specific power state windows notifies applications by broadcasting WM_POWERBROADCAST message. When you recieve this message check the wParam of the WindowProc for the PBT_APMSUSPEND. Detect when the device resumes check wParam for PBT_APMRESUMESUSPEND.
For detailed information see the following. WM_POWERBROADCAST Message
http://msdn.microsoft.com/en-us/library/aa373247(VS.85).aspx 
My bad... I mistakenly was thinking you wanted to detect "suspend" ... for standby the wParam you should be looking for is in the code section below.
PBT_APMSTANDBY = 0x0005
PBT_APMQUERYSTANDBY = 0x0001
PBT_APMRESUMESTANDBY = 0x0008

Open in new window

Avatar of mdlines

ASKER

Thanks for your help - I have created a hidden test window that is waiting for a message number 536 but it does not catch it when the monitor goes into standby.  If I send a message with number 536 using the following code - it does catch it. The reason why is probably obvious to you but I rarely program at this lower level! I am hoping you can see why my test works but not when message 536 is broadcast by windows.
;-------------------------------------------------------------------------------
;  This program sends a message to the test window in winbatch
;--------------------------------------------------------------------------------
user32 = StrCat(dirwindows(1),"user32.dll")
hWnd = dllHwnd("test hidden Window waiting for message number 536")
x = DllCall(user32,long:"PostMessageA",long:hWnd,long:536,long:123,long:456);
Exit
Okay. I was correct the first time.. When  I read the documentation the first time it said PBT_APMSTANDBY is not supported.. What you do is use the messages I first posted because that was correct. I have written an example that will help you see code section below.
Fire up a new Standard Project.
Add 1 standard module named: PowerManagmentCallback
Add 1 class module named: PowerManagment
Note: You must use the same class name for this example to work. In the example section you need only to create the instance once to start monitoring messages.
Good luck.

'
' PowerManagmentCallback.bas
'

Option Explicit

Public Const WM_POWERBROADCAST As Long = &H218&

Public Const GWLP_WNDPROC As Long = (-4)
Public Const GWLP_USERDATA As Long = (-21)

Public Declare Function CreateWindowExW Lib "User32.dll" (ByVal dwExStyle As Long, ByVal lpClassName As Long, ByVal lpWindowName As Long, ByVal dwStyle As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hWndParent As Long, ByVal hMenu As Long, ByVal hInstance As Long, ByVal lpParam As Long) As Long
Public Declare Function DestroyWindow Lib "User32.dll" (ByVal hWnd As Long) As Long
Public Declare Function CallWindowProcW Lib "User32.dll" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Public Declare Function SetWindowLongW Lib "User32.dll" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Declare Function GetWindowLongW Lib "User32.dll" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Public Declare Function GetModuleHandleW Lib "Kernel32.dll" (ByVal lpModuleName As Long) As Long
Public Declare Sub RtlMoveMemory Lib "Kernel32.dll" (pDest As Any, pSrc As Any, ByVal ByteLen As Long)

'STATIC
Dim PowerManagmentShadow    As PowerManagment      ' // PowerManagment Shadow Copy
Dim PowerManagmentPointer   As Long                ' // PowerManagment Pointer

Public Function WndProc( _
  ByVal hWnd As Long, _
  ByVal uMsg As Long, _
  ByVal wParam As Long, _
  ByVal lParam As Long) As Long
  '   Forwards all callback messages to PowerManagment class.
  PowerManagmentPointer = GetWindowLongW(hWnd, GWLP_USERDATA)
  RtlMoveMemory PowerManagmentShadow, PowerManagmentPointer, 4
  WndProc = PowerManagmentShadow.PowerManagmentWndProc(hWnd, uMsg, wParam, lParam)
  RtlMoveMemory PowerManagmentShadow, 0&, 4
  Set PowerManagmentShadow = Nothing
End Function





'
' PowerManagment.cls
'

Option Explicit

' egl1044
Public Enum PowerBroadcastParams
  PBT_APMQUERYSUSPEND = &H0
  PBT_APMQUERYSTANDBY = &H1
  PBT_APMQUERYSUSPENDFAILED = &H2
  PBT_APMQUERYSTANDBYFAILED = &H3
  PBT_APMSUSPEND = &H4
  PBT_APMSTANDBY = &H5
  PBT_APMRESUMECRITICAL = &H6
  PBT_APMRESUMESUSPEND = &H7
  PBT_APMRESUMESTANDBY = &H8
  PBTF_APMRESUMEFROMFAILURE = &H1
  PBT_APMBATTERYLOW = &H9
  PBT_APMPOWERSTATUSCHANGE = &HA
  PBT_APMOEMEVENT = &HB
  PBT_APMRESUMEAUTOMATIC = &H12
End Enum

Private m_WndFunc        As Long   '//   The callback function address
Private m_hWindow        As Long   '//   The callback window handle

Event OnPowerBroadcast(ByVal PBT As Long)

Private Sub Class_Initialize()

  '   Create the window.
  CreateCallbackWindow
  
  '   Start processing messages
  StartPowerCallback
  
End Sub

Private Sub Class_Terminate()
  
  '   Stop processing messages
  StopPowerCallback
  
  '   Destroy the window.
  DestroyCallbackWindow
  
End Sub

Private Sub StartPowerCallback()
  
  '   Enable WndProc messages
  If m_hWindow <> 0 Then

    m_WndFunc = SetWindowLongW(m_hWindow, GWLP_WNDPROC, AddressOf WndProc)

    SetWindowLongW m_hWindow, GWLP_USERDATA, ObjPtr(Me)
    
  End If

End Sub

Private Sub StopPowerCallback()

  '   Disable WndProc messages
  If m_hWindow <> 0 Then
    m_WndFunc = SetWindowLongW(m_hWindow, GWLP_WNDPROC, m_WndFunc)
    m_WndFunc = 0
  End If
  
End Sub

Private Sub CreateCallbackWindow()

  '   Create callback window
  If m_hWindow = 0 Then
 
    m_hWindow = CreateWindowExW(0, StrPtr("STATIC"), StrPtr("PowerMgmtCallback"), _
                                0, 0, 0, 0, 0, 0, 0, GetModuleHandleW(0), 0)
  End If
  
End Sub

Private Sub DestroyCallbackWindow()

  '   Destroy callback window
  If m_hWindow <> 0 Then
  
    DestroyWindow m_hWindow
    m_hWindow = 0
    
  End If
  
End Sub

Friend Function PowerManagmentWndProc( _
  ByVal hWnd As Long, _
  ByVal uMsg As Long, _
  ByVal wParam As Long, _
  ByVal lParam As Long) As Long
  
  '   Callback
  
  If uMsg = WM_POWERBROADCAST Then
  
    RaiseEvent OnPowerBroadcast(wParam)

  End If
  
  PowerManagmentWndProc = CallWindowProcW(m_WndFunc, hWnd, uMsg, wParam, lParam)
  
End Function




'
' Form1.frm (Example usage)
'

Option Explicit

Dim WithEvents pMgmt As PowerManagment

Private Sub Form_Load()

  ' This call starts subclassing for power broadcast events
  Set pMgmt = New PowerManagment
  
End Sub

Private Sub Form_QueryUnload(Cancel As Integer, UnloadMode As Integer)

  ' This call stops subclassing for power broadcast events
  Set pMgmt = Nothing
  
End Sub

Private Sub pMgmt_OnPowerBroadcast(ByVal PBT As Long)
  
  Select Case PBT ' PowerBroadcastParams
  
    Case PowerBroadcastParams.PBT_APMQUERYSUSPEND
      Debug.Print "PBT_APMQUERYSUSPEND"
      
    Case PowerBroadcastParams.PBT_APMRESUMESUSPEND
      Debug.Print "PBT_APMRESUMESUSPEND"
      
    Case PowerBroadcastParams.PBT_APMSUSPEND
      Debug.Print "PBT_APMSUSPEND"
      
  End Select
  
End Sub

Open in new window

Apparently I can't spell Managment the correct way which is Management :(
Avatar of mdlines

ASKER

Hi eql1044
I really appreciate the incredible effort you have made to  help me with this.  I program in a high level language called winbatch in which you can achieve the same as in VB.  Returning to my last comment - is it possible for me to have a hidden window that 'hears' the power management broadcast message when the monitor slips into standby ? I can't work this out from all that amazing VB code you have created.
I never heard of winbatch language only batch files so I have no idea. I thought this was a VB question. If it's like VB then use a form or create your own STATIC window using CreateWindowEx API.
Avatar of mdlines

ASKER

VB is closest to winbatch which is why I placed this question in the VB category.

As you advised, I have created a window which sees the message 536 I send to it using

x = DllCall(user32,long:"PostMessageA",long:hWnd,long:536,long:123,long:456);

I was hoping you might be able to suggest  a reason why my window gets these messages addressed to it but not the messages 'broadcast' to all windows. perhaps its because my window only sees messages sent using user32.dll ?
ASKER CERTIFIED SOLUTION
Avatar of nffvrxqgrcfqvvc
nffvrxqgrcfqvvc

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of mdlines

ASKER

Thank you eql1044 for all your help
No problem, Did you find out what you need to do in (WinBatch) to use WindowProc?
Avatar of mdlines

ASKER

not yet - I have posted your code into the winbatch forum to see if anyone there can help with how to register a callback to my hidden window which needs to get the power management messages.
Well worst case scenerio I can compile the code for you into ActiveX.DLL if (WinBatch) allow you to use ActiveX DLL and subscribe to events? You could also use VB.NET Express edition which might have built in class for handling these messages. Just let me know. Wouldn't mind to see the (WinBatch) solution either if you find one could you share it with us?
Avatar of mdlines

ASKER

thanks for your activeX.Dll offer - Winbatch can call DLLs  although i am not sure about the 'subscribe to events'  part.   I will be happy to share the solution if the winbatch forum can help me achieve the objective.  Could your DLL set a registry value when the monitor-gone-into-standby message is received ?  Are you a C programmer too?
Well if it's possible you can always use a regular script file(.vbs) with the ActiveX DLL. You can subscribe to the events using WScript.CreateObject() but I don't do much scripting and also I wouldn't be sure how you would keep the script running besides a loop and Wscript.Sleep to keep the CPU usage down. I could write the value to the registry but I wouldn't recommend that because you really shouldn't be polling for values in the registry it's more for storing settings. I don't program in C but I can read it. :(
I don't know anything about WinBatch, I could alter the code so you could pass in an hWnd and I could make a call to SendMessage() with wParam of the same PBT value. That would work better then the registry. If it lets you use ActiceX DLL it should let you subscribe to events one way or another maybe ask how to use ActiveX DLL with Events if you can't find a solution.
Avatar of mdlines

ASKER

I did some more testing and things are not as clear cut as I thought.  I ran my test hidden window listening for 536 messages and I reconfigured the PC's power button to put PC to sleep when pressed. MY window does pick up that WM_POWERBROADCAST message and shows wparam=4 lparam=0. SO the it looks like the winbatch code is working fine and either
1. there is no WM_POWERBROADCAST message sent out for when monitor goes into standby due to powersave scheme
OR
2. you only get WM_POWERBROADCAST messages if  you have a wddm compliant monitor driver.
I'm not really sure to be honest , don't know how Winbatch works, but if you feel the urge to confirm your findings you can use the DLL and script code to see if it throws a message box on screen when entering or resuming standy mode.
You need to register the DLL.. If you use the script example you will have to Terminate the wscript.exe process when you don't want notifications.
https://filedb.experts-exchange.com/incoming/ee-stuff/7955-PowerMgmt.zip 
Avatar of mdlines

ASKER

Thanks - I downloaded your dll and vbs and they work in exactly the same way as winbatch as described in my last post.  your script window detects the 'whole pc' going into standby but it does NOT  detect just the monitor entering standby which is the functionality I need.
Ohhh.. Well that is something completley different. That is just turning the monitor power off to save juice. You could probrably detect this by creating a WH_CBT hook with CBTProc callback. You can check nCode for WM_SYSCOMMAND (HCBT_SYSCOMMAND) then check wParam for SC_MONITORPOWER.
The lParam gives you the status
-1 (the display is powering on)
1 (the display is going to low power)
2 (the display is being shut off)
Avatar of mdlines

ASKER

I am sorry I didnt make my requirement clear enough.  Is the method you just described the same as creating a window and registering for callback messages as we did before but with a different message number ?
No exactly it's very different. You need to call SetWindowsHookEx() with WH_CBT. I have tested this theory for you above and it does "work" but with a limitation. The window needs to be in the foreground to recieve the message. To make it work at all times you will have to create a global hook which means you have to create your own standard DLL with the hook code so that it will get the message regardless of which application is in the foreground.
I don't know another way to get this information and especially on XP.
There seems to be alternative on Vista and later that might. See.. GUID_MONITOR_POWER_ON
RegisterPowerSettingNotification
http://msdn.microsoft.com/en-us/library/aa373196(VS.85).aspx

Registering for Power Events
http://msdn.microsoft.com/en-us/library/aa373195(VS.85).aspx
Avatar of mdlines

ASKER

Thanks again for the further information - I really appreciate the time and effort you have spent helping me.

I am not so concerned about XP because my application monitors PC usage 'idle' time and in XP windows does not turn off the screensaver when the monitor goes into standby. Vista and Windows 7 both do terminate the screensaver and hence I need to know when the monitor enters standby state.  Therefore the 2nd method you mention might be more appropriate.  Would you be able to help me with the code if I asked another question which you could answer for 500 more points?  Does this method still work if the monitor driver is not wddm compliant ?  
I could but I won't be able to test the code. It looks very simpe actually to implement. Do the same thing as you did before by creating a window in your winbatch.
Make a call to RegisterPowerSettingNotification specify the hWnd of the window you created, and the GUID_MONITOR_POWER_ON
hNotify = RegisterPowerSettingNotification(hWnd, uuid, DEVICE_NOTIFY_WINDOW_HANDLE)
This should register the extra event. You just need to go back and see if the WM_POWERBROADCAST is being broadcasted after you register for the GUID event.
Check for WM_POWERBROADCAST message. Then check wParam for PBT_POWERSETTINGCHANGE if your inside this area then copy the (lParam) to POWERBROADCAST_SETTING structure. The Data member is a DWORD that indicates the current monitor state.
If uMsg = WM_POWERBROADCAST Then
  If wParam = PBT_POWERSETTINGCHANGE Then
  '  lParam
  '  Pointer to a POWERBROADCAST_SETTING structure.
  '  Copy (lParam) to the structure here and use Data value.
    End If
End If
Avatar of mdlines

ASKER

AM I on the right lines with my code below ?

I create a window that listens for power management messages (is it still 536?)
then I make the call to registerpowersettingnotification ( i am not sure about this bit)
then the code waits until the listening window receives a message
;create a window that listens for power management messages
hwnd = zwinCreate("My listening window",536)
Message("OK","The window is ready.")

;get the window handle
user32 = StrCat(dirwindows(1),"user32.dll")
hWnd = dllHwnd("My listening window")

;register my listening window for 'monitor off' messages 
x = DllCall(user32,long:"RegisterPowerSettingNotification",long:hWnd,long:0,long:0)

;wait for the listening window to receive a message
s = zWinGetMessage()
if (S == "") 
    Message("On Return","Window is closed")
    return
endif
W = int(ItemExtract(1,S,":"))
L = int(ItemExtract(2,S,":"))
Message("On Return",StrCat("Message Received:   wParam=",W,"   lParam=",L))

hwnd = zwinDestroy()
Exit

Open in new window

x = DllCall(user32,long:"RegisterPowerSettingNotification",long:hWnd,LPCGUID:uuid,long:0)
The second parameter  wants a pointer to a a GUID value. I use the GUID structure when working with guids in VB not sure about WinBatch. I would in VB convert the string guid value {02731015-4510-4526-99e6-e5a17ebd1aea} into a GUID structure and pass that as the second parameter to RegisterPowerSettingNotification. See example code for vb in codeblock

Private Const GUID_MONITOR_POWER_ON As String = "{02731015-4510-4526-99e6-e5a17ebd1aea}"

Private Declare Function IIDFromString Lib "ole32" ( _
  ByVal lpsz As Long, _
  ByRef lpiid As UUID) As Long

Private Type UUID 'GUID
  Data1 As Long
  Data2 As Integer
  Data3 As Integer
  Data4(7) As Byte
End Type

Dim gEvent As UUID
Call IIDFromString(StrPtr(GUID_MONITOR_POWER_ON), gEvent)
RegisterPowerSettingNotification hWnd, VarPtr(gEvent), 0)

Open in new window

Avatar of mdlines

ASKER

Thanks again eql1044 for all your help.  My quest is now complete as someone from the Winbatch forum has suggested a very simple solution to my business problem of monitoring idle time.  I will make a call to user32.dll to obtain the total tick count and tick count of last user input from which I can derive 'idle time'.
Ahh.. You never mentioned you wanted to detect Idle time only things for standby and monitor power.. You should have asked or mentioned that instead! It's good to see you found exactly what your looking for :)
I wouldn't mind messing with WinBatch just to see what it can do, what is the winbatch forum you go to for help? I can't find anything on this language.
Avatar of mdlines

ASKER

main website is www.windowware.com - front page describes its features and abilities well

forum is http://webboard.winbatch.com/wbisadll/wbpx.isa/~winware - you can log on guest

Thanks again for all your help
Regards

Martin Lines