mdlines
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 ?
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 ?
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
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),"user 32.dll")
hWnd = dllHwnd("test hidden Window waiting for message number 536")
x = DllCall(user32,long:"PostM essageA",l ong:hWnd,l ong:536,lo ng:123,lon g:456);
Exit
;-------------------------
; This program sends a message to the test window in winbatch
;-------------------------
user32 = StrCat(dirwindows(1),"user
hWnd = dllHwnd("test hidden Window waiting for message number 536")
x = DllCall(user32,long:"PostM
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.
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
Apparently I can't spell Managment the correct way which is Management :(
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 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.
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:"PostM essageA",l ong:hWnd,l ong:536,lo ng:123,lon g: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 ?
As you advised, I have created a window which sees the message 536 I send to it using
x = DllCall(user32,long:"PostM
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Thank you eql1044 for all your help
No problem, Did you find out what you need to do in (WinBatch) to use WindowProc?
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?
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.
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.
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.
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
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
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)
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)
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
RegisterPowerSettingNotifi cation
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
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
RegisterPowerSettingNotifi
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
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 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 RegisterPowerSettingNotifi cation specify the hWnd of the window you created, and the GUID_MONITOR_POWER_ON
hNotify = RegisterPowerSettingNotifi cation(hWn d, uuid, DEVICE_NOTIFY_WINDOW_HANDL E)
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
Make a call to RegisterPowerSettingNotifi
hNotify = RegisterPowerSettingNotifi
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
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 registerpowersettingnotifi cation ( i am not sure about this bit)
then the code waits until the listening window receives a message
I create a window that listens for power management messages (is it still 536?)
then I make the call to registerpowersettingnotifi
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
x = DllCall(user32,long:"Regis terPowerSe ttingNotif ication",l ong:hWnd,L PCGUID:uui d,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-e 5a17ebd1ae a} into a GUID structure and pass that as the second parameter to RegisterPowerSettingNotifi cation. See example code for vb in codeblock
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-e
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)
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.
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.
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
forum is http://webboard.winbatch.com/wbisadll/wbpx.isa/~winware - you can log on guest
Thanks again for all your help
Regards
Martin Lines
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