Detect when montior is turned off or idle?

Get help with using AutoHotkey and its commands and hotkeys
Scottland
Posts: 5
Joined: 04 May 2016, 09:18

Detect when montior is turned off or idle?

04 May 2016, 09:22

Hi,

I'd like to use AHK to control my Philips Hue Lights and have it execute a script which turns off (or dims) the lights if the monitor is turned off, or if the display is sent to standby (the computer does not enter standby).

From the event manager I cannot see any actions that I could trigger using Task Scheduler - so I'm looking to AHK instead.


Does anyone know how I can see if the display is on? The monitor is connected via displayport - so windows is aware when the monitor is turned off as it's effectively disconnected as far as I can tell.


Thanks in advance...
User avatar
Masonjar13
Posts: 1517
Joined: 20 Jul 2014, 10:16
GitHub: Masonjar13
Location: Не Россия

Re: Detect when montior is turned off or idle?

04 May 2016, 09:33

I haven't really tried, but you could try using SysGet,monitorCount,80 or SysGet,monitorCount,MonitorCount (there is a difference, so try both) and check if monitorCount = 0. It may not be set to 0 by it turning off though, you'll have to test it.
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
Scottland
Posts: 5
Joined: 04 May 2016, 09:18

Re: Detect when montior is turned off or idle?

04 May 2016, 09:53

Thanks for that - doesn't quite seem to work though. It will return 2 with both displays powered on, and 1 with only 1 display powered on. But with both displays powered off it still returns 1.
User avatar
Masonjar13
Posts: 1517
Joined: 20 Jul 2014, 10:16
GitHub: Masonjar13
Location: Не Россия

Re: Detect when montior is turned off or idle?

04 May 2016, 10:58

This should work if the monitor is physically turned off, but not sure if it triggers with idle-off. To accommodate for idle-off, you can use A_TimeIdle.

Untested. It should beep when the monitor is off (probably won't work if the audio is integrated into the monitor).

EDIT: Added support for W8+

Code: Select all

#persistent

onMessage(0x218,"WM_POWERBROADCAST")
global monitorStatus:=1
loop {
    while(!monitorStatus){
        if(a_index=1)
            soundbeep
        sleep 100
    }
    sleep 100
}
return

WM_POWERBROADCAST(wParam,lParam){
    static PBT_POWERSETTINGCHANGE:=0x8013
    static GUID_MONITOR_POWER_ON:="02731015-4510-4526-99e6-e5a17ebd1aea"
    static GUID_CONSOLE_DISPLAY_STATE:="6fe69556-704a-47a0-8f24-c28d936fda47"
    
    if(wParam=PBT_POWERSETTINGCHANGE){
            if(numGet(lParam,0)=GUID_MONITOR_POWER_ON || numGet(lParam,0)=GUID_CONSOLE_DISPLAY_STATE)
                monitorStatus:=numGet(lParam,20)?1:0
    }
}
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
Scottland
Posts: 5
Joined: 04 May 2016, 09:18

Re: Detect when montior is turned off or idle?

04 May 2016, 11:21

Brilliant, thanks I'll have a play with it later.
User avatar
Masonjar13
Posts: 1517
Joined: 20 Jul 2014, 10:16
GitHub: Masonjar13
Location: Не Россия

Re: Detect when montior is turned off or idle?

04 May 2016, 18:56

I was noticing that messages wouldn't be sent to the script, when I stumbled upon this. After implementing, I'm still not having the script receive any messages (function is never triggered), though. Maybe someone else could help..

Code: Select all

#persistent

GUID_MONITOR_POWER_ON:="02731015-4510-4526-99e6-e5a17ebd1aea"
GUID_CONSOLE_DISPLAY_STATE:="6fe69556-704a-47a0-8f24-c28d936fda47"
global monitorStatus:=1
global cnt:=0
onMessage(0x218,"WM_POWERBROADCAST")

if a_OSVersion in WIN_8,WIN_8.1,WIN_10
    rhandle:=dllCall("RegisterPowerSettingNotification","UInt",a_scriptHwnd,"Str",GUID_CONSOLE_DISPLAY_STATE,"Int",0)
else
    rhandle:=dllCall("RegisterPowerSettingNotification","UInt",a_scriptHwnd,"Str",GUID_MONITOR_POWER_ON,"Int",0)

setTimer,checkMonitor,1000
return

checkMonitor:
while(!monitorStatus){
    if(a_index=1)
        msgbox ok
    sleep 1000
}
return

WM_POWERBROADCAST(wParam,lParam){
    cnt++
    monitorStatus:=numGet(lParam,20)?1:0
    return
}
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
User avatar
Masonjar13
Posts: 1517
Joined: 20 Jul 2014, 10:16
GitHub: Masonjar13
Location: Не Россия

Re: Detect when montior is turned off or idle?

04 May 2016, 20:36

It's now receiving the messages, but I can NOT find the Data member. Documentation suggests that the first parameter (GUID) is 16-bytes, the second (DWord) is 4-bytes, and the 3rd (DWord) is 4-bytes. The third is suppose to have the answer, so the correct offset should be 20, but that appears to be out of range.

Code: Select all

#persistent
#singleInstance force

GUID_MONITOR_POWER_ON:="02731015-4510-4526-99e6-e5a17ebd1aea"
GUID_CONSOLE_DISPLAY_STATE:="6fe69556-704a-47a0-8f24-c28d936fda47"
global monitorStatus:=1
global newGUID:=""

varSetCapacity(newGUID,16,0)
if a_OSVersion in WIN_8,WIN_8.1,WIN_10
    dllCall("Rpcrt4\UuidFromString","Str",GUID_CONSOLE_DISPLAY_STATE,"UInt",&newGUID)
else
    dllCall("Rpcrt4\UuidFromString","Str",GUID_MONITOR_POWER_ON,"UInt",&newGUID)
rhandle:=dllCall("RegisterPowerSettingNotification","UInt",a_scriptHwnd,"Str",strGet(&newGUID),"Int",0)
onMessage(0x218,"WM_POWERBROADCAST")

setTimer,checkMonitor,1000
return

checkMonitor:
while(!monitorStatus){
    if(a_index=1)
        msgbox ok
    sleep 1000
}
return

WM_POWERBROADCAST(wParam,lParam){
    static PBT_POWERSETTINGCHANGE:=0x8013
    if(wParam=PBT_POWERSETTINGCHANGE){
        if(subStr(strGet(lParam),1,strLen(strGet(lParam))-1)=strGet(&newGUID)){
            fileAppend,% "lParam Data: " numGet(lParam,20,"UInt") "`n`n",file.txt ; Problem testing line
            ;monitorStatus:=numGet(lParam,20,"UInt")?1:0 ; Problem line
        }
    }
    return
}
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
lexikos
Posts: 7086
Joined: 30 Sep 2013, 04:07
GitHub: Lexikos

Re: Detect when montior is turned off or idle?

05 May 2016, 03:34

numGet(lParam,20,"UInt")
Even if the offset is correct, your usage is not. See the second paragraph for "VarOrAddress".
User avatar
Masonjar13
Posts: 1517
Joined: 20 Jul 2014, 10:16
GitHub: Masonjar13
Location: Не Россия

Re: Detect when montior is turned off or idle?

05 May 2016, 14:23

lexikos wrote:Even if the offset is correct, your usage is not. See the second paragraph for "VarOrAddress".
I knew I was doing something wrong, I just didn't know WHAT. Thank you, lexikos. The following now works (tested on Windows 8.1)(the offset was correct).

Code: Select all

#persistent
#singleInstance force

GUID_MONITOR_POWER_ON:="02731015-4510-4526-99e6-e5a17ebd1aea"
GUID_CONSOLE_DISPLAY_STATE:="6fe69556-704a-47a0-8f24-c28d936fda47"
global monitorStatus:=1
global newGUID:=""

varSetCapacity(newGUID,16,0)
if a_OSVersion in WIN_8,WIN_8.1,WIN_10
    dllCall("Rpcrt4\UuidFromString","Str",GUID_CONSOLE_DISPLAY_STATE,"UInt",&newGUID)
else
    dllCall("Rpcrt4\UuidFromString","Str",GUID_MONITOR_POWER_ON,"UInt",&newGUID)
rhandle:=dllCall("RegisterPowerSettingNotification","UInt",a_scriptHwnd,"Str",strGet(&newGUID),"Int",0)
onMessage(0x218,"WM_POWERBROADCAST")

setTimer,checkMonitor,500
return

checkMonitor:
while(!monitorStatus){
    if(a_index=1)
        msgbox ok
    sleep 500
}
return

WM_POWERBROADCAST(wParam,lParam){
    static PBT_POWERSETTINGCHANGE:=0x8013
    if(wParam=PBT_POWERSETTINGCHANGE){
        if(subStr(strGet(lParam),1,strLen(strGet(lParam))-1)=strGet(&newGUID)){
            ;fileAppend,% "lParam Data: " numGet(lParam+0,20,"UInt") "`n`n",file.txt
            monitorStatus:=numGet(lParam+0,20,"UInt")?1:0
        }
    }
    return
}
A few extra notes to whom it may concern (OP).

Windows 8+ uses GUID_CONSOLE_DISPLAY_STATE which also reports when the screen is dimmed. 2 = screen dimmed, 1 = screen on, 0 = screen off.
Windows 7 and before use GUID_CONSOLE_DISPLAY_STATE which only supports screen on and off.
Windows XP and below will not work with RegisterPowerNotification as it doesn't exist before Vista, but it should work with that part removed.
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
pneumatic
Posts: 299
Joined: 05 Dec 2016, 01:51

Re: Detect when montior is turned off or idle?

13 Jan 2020, 12:57

Masonjar13 wrote:
05 May 2016, 14:23
lexikos wrote:Even if the offset is correct, your usage is not. See the second paragraph for "VarOrAddress".
I knew I was doing something wrong, I just didn't know WHAT. Thank you, lexikos. The following now works (tested on Windows 8.1)(the offset was correct).

Code: Select all

#persistent
#singleInstance force

GUID_MONITOR_POWER_ON:="02731015-4510-4526-99e6-e5a17ebd1aea"
GUID_CONSOLE_DISPLAY_STATE:="6fe69556-704a-47a0-8f24-c28d936fda47"
global monitorStatus:=1
global newGUID:=""

varSetCapacity(newGUID,16,0)
if a_OSVersion in WIN_8,WIN_8.1,WIN_10
    dllCall("Rpcrt4\UuidFromString","Str",GUID_CONSOLE_DISPLAY_STATE,"UInt",&newGUID)
else
    dllCall("Rpcrt4\UuidFromString","Str",GUID_MONITOR_POWER_ON,"UInt",&newGUID)
rhandle:=dllCall("RegisterPowerSettingNotification","UInt",a_scriptHwnd,"Str",strGet(&newGUID),"Int",0)
onMessage(0x218,"WM_POWERBROADCAST")

setTimer,checkMonitor,500
return

checkMonitor:
while(!monitorStatus){
    if(a_index=1)
        msgbox ok
    sleep 500
}
return

WM_POWERBROADCAST(wParam,lParam){
    static PBT_POWERSETTINGCHANGE:=0x8013
    if(wParam=PBT_POWERSETTINGCHANGE){
        if(subStr(strGet(lParam),1,strLen(strGet(lParam))-1)=strGet(&newGUID)){
            ;fileAppend,% "lParam Data: " numGet(lParam+0,20,"UInt") "`n`n",file.txt
            monitorStatus:=numGet(lParam+0,20,"UInt")?1:0
        }
    }
    return
}
A few extra notes to whom it may concern (OP).

Windows 8+ uses GUID_CONSOLE_DISPLAY_STATE which also reports when the screen is dimmed. 2 = screen dimmed, 1 = screen on, 0 = screen off.
Windows 7 and before use GUID_CONSOLE_DISPLAY_STATE which only supports screen on and off.
Windows XP and below will not work with RegisterPowerNotification as it doesn't exist before Vista, but it should work with that part removed.

Just wanted to say thanks for this. I knew I had to use RegisterPowerSettingNotification and UuidFromString, but there's no way I could have figured out all the byte offsets :crazy: :lol:

Somewhat annoyingly, Windows sends a 0x1 (display on) immediately after RegisterPowerSettingNotification. My workaround is to ignore the event if A_TickCount is n milliseconds after RegisterPowerSettingNotification (since we can't just ignore the first instance since we don't know if this behaviour is the same on all systems).

Also there is a typo at the end of your post.
Last edited by pneumatic on 13 Jan 2020, 15:02, edited 2 times in total.
pneumatic
Posts: 299
Joined: 05 Dec 2016, 01:51

Re: Detect when montior is turned off or idle?

13 Jan 2020, 14:58

Masonjar13 wrote:
05 May 2016, 14:23

Code: Select all

#persistent
#singleInstance force

GUID_MONITOR_POWER_ON:="02731015-4510-4526-99e6-e5a17ebd1aea"
GUID_CONSOLE_DISPLAY_STATE:="6fe69556-704a-47a0-8f24-c28d936fda47"
global monitorStatus:=1
global newGUID:=""

varSetCapacity(newGUID,16,0)
if a_OSVersion in WIN_8,WIN_8.1,WIN_10
    dllCall("Rpcrt4\UuidFromString","Str",GUID_CONSOLE_DISPLAY_STATE,"UInt",&newGUID)
else
    dllCall("Rpcrt4\UuidFromString","Str",GUID_MONITOR_POWER_ON,"UInt",&newGUID)
rhandle:=dllCall("RegisterPowerSettingNotification","UInt",a_scriptHwnd,"Str",strGet(&newGUID),"Int",0)
onMessage(0x218,"WM_POWERBROADCAST")

setTimer,checkMonitor,500
return

checkMonitor:
while(!monitorStatus){
    if(a_index=1)
        msgbox ok
    sleep 500
}
return

WM_POWERBROADCAST(wParam,lParam){
    static PBT_POWERSETTINGCHANGE:=0x8013
    if(wParam=PBT_POWERSETTINGCHANGE){
        if(subStr(strGet(lParam),1,strLen(strGet(lParam))-1)=strGet(&newGUID)){
            ;fileAppend,% "lParam Data: " numGet(lParam+0,20,"UInt") "`n`n",file.txt
            monitorStatus:=numGet(lParam+0,20,"UInt")?1:0
        }
    }
    return
}

I just have a question about this:

Code: Select all

monitorStatus := numGet(lParam+0, 20, "UInt") ? 1 : 0

Are you deliberately using the ternary operator to also include the case where the NumGet returns ANY non-blank or non-zero value?

In other words, NumGet could return 1 or 12345 or "some string" and you still intend for monitorStatus to become 1?

Because on my test systems, monitorStatus is always 1 for display on. I'd like to know if I'm being too strict in requiring that monitorStatus must be exactly 1.

In other words, did you do some testing and found that NumGet sometimes returns some other non-blank, non-zero value when the monitor is turned on?

Thanks
User avatar
Masonjar13
Posts: 1517
Joined: 20 Jul 2014, 10:16
GitHub: Masonjar13
Location: Не Россия

Re: Detect when montior is turned off or idle?

13 Jan 2020, 15:34

Considering this is a 4 year old thread.. Idk. But taking a look at the structure, if you're using 7 or below, GUID_MONITOR_POWER_ON is needed, which reports either 0 or 1. But, for 8+, GUID_CONSOLE_DISPLAY_STATE is used, which has 0, 1, or 2, where 2 = dimmed. How I had written it, dimmed is synonymous with on.

I have no recollection of even writing this, so couldn't tell you why I chose to do so.
OS: Windows 10 Pro | Editor: Notepad++
My Personal Function Library | Old Build - New Build
pneumatic
Posts: 299
Joined: 05 Dec 2016, 01:51

Re: Detect when montior is turned off or idle?

13 Jan 2020, 15:50

Masonjar13 wrote:
13 Jan 2020, 15:34
Considering this is a 4 year old thread.. Idk. But taking a look at the structure, if you're using 7 or below, GUID_MONITOR_POWER_ON is needed, which reports either 0 or 1. But, for 8+, GUID_CONSOLE_DISPLAY_STATE is used, which has 0, 1, or 2, where 2 = dimmed. How I had written it, dimmed is synonymous with on.

I see, thanks. I will just compare it to their explicit values for now. I don't actually need to detect dimming (my screen doesn't even dim and Windows still sends 0x2 about 10 seconds before turning the display off)

Also just adding some random possibly useful info for anyone else who happens to be reading this thread in future

- At the start of msg monitor func, I'm using Critical , 1000 to ensure window messages are buffered for 1000ms instead of being discarded (because in some cases multiple messages arrive very quickly)

- At the start of msg monitor func, only process it if ( hwnd = A_ScriptHwnd ) otherwise it gets called for each GUI window that is open (however the next bit will solve that too)

- Do your payload in a separate thread eg. with SetTimer , MyThread, -1 and make MyThread subroutine Critical. This further guards against multiple messages clashing and code being executed out of order.

- At the end of msg monitor func, return true according to WinAPI "An application should return TRUE if it processes this message."

Return to “Ask For Help”

Who is online

Users browsing this forum: Clemens375, electrone77, euras, Google [Bot], kczx3, mikeyww and 55 guests