1

I need my python script to get windows monitor power state.

According to this link in StackOverflow, I can use RegisterPowerSettingNotification to get GUID_MONITOR_POWER_ON but I don't know how to implement it. The objective is to trigger events when the screen goes off.

I can access the funcion at ctypes.windll.user32.RegisterPowerSettingNotification but I need help to call it and get the messsages

RegisterPowerSettingNotification
Power Setting GUIDs

I am running Windows 10 and Python 3


Update:

I was able to call and register the window to receive messages but I'm still unable to get the information I need from it... I know that lparam is a pointer to a structure but I need to get information from it like the GUID PowerSetting and Data fields

import win32con
import win32api
import win32gui
import sys
import time
from ctypes import POINTER, windll
from comtypes import GUID
from ctypes.wintypes import HANDLE, DWORD

PBT_POWERSETTINGCHANGE = 0x8013

def log_info(msg):
    """ Prints """
    print(msg)
    f = open("test.log", "a+")
    f.write(msg + "\n")
    f.close()

def wndproc(hwnd, msg, wparam, lparam):
    print('.')
    log_info("wndproc: %s\nw: %s\nl: %s" % (msg, wparam, lparam))
    if msg == win32con.WM_POWERBROADCAST:
        if wparam == win32con.PBT_APMPOWERSTATUSCHANGE:
            log_info('Power status has changed')
        if wparam == win32con.PBT_APMRESUMEAUTOMATIC:
            log_info('System resume')
        if wparam == win32con.PBT_APMRESUMESUSPEND:
            log_info('System resume by user input')
        if wparam == win32con.PBT_APMSUSPEND:
            log_info('System suspend')
        if wparam == PBT_POWERSETTINGCHANGE:
            log_info('Power setting changed...')
            #lparam is pointer to structure i need

if __name__ == "__main__":
    log_info("*** STARTING ***")
    hinst = win32api.GetModuleHandle(None)
    wndclass = win32gui.WNDCLASS()
    wndclass.hInstance = hinst
    wndclass.lpszClassName = "testWindowClass"
    messageMap = { win32con.WM_POWERBROADCAST : wndproc }

    wndclass.lpfnWndProc = messageMap

    try:
        myWindowClass = win32gui.RegisterClass(wndclass)
        hwnd = win32gui.CreateWindowEx(win32con.WS_EX_LEFT,
                                     myWindowClass, 
                                     "testMsgWindow", 
                                     0, 
                                     0, 
                                     0, 
                                     win32con.CW_USEDEFAULT, 
                                     win32con.CW_USEDEFAULT, 
                                     0, 
                                     0, 
                                     hinst, 
                                     None)
    except Exception as e:
        log_info("Exception: %s" % str(e))


    if hwnd is None:
        log_info("hwnd is none!")
    else:
        log_info("hwnd: %s" % hwnd)

    register_function = windll.user32.RegisterPowerSettingNotification

    guids_info = {
                    'GUID_MONITOR_POWER_ON' : '{02731015-4510-4526-99e6-e5a17ebd1aea}',
                    'GUID_SYSTEM_AWAYMODE' : '{98a7f580-01f7-48aa-9c0f-44352c29e5C0}',
                    'fake' : '{98a7f580-01f7-48aa-9c0f-44352c29e555}' # just to see if I get an error or a different return from function
                 }

    hwnd_pointer = HANDLE(hwnd)
    for name, guid_info in guids_info.items():
        result = register_function(hwnd_pointer, GUID(guid_info), DWORD(0))
        print('registering', name)
        print('result:', result) # result is pointer to unregister function if I'm not mistaken
        print()

    print('\nEntering loop')
    while True:
        win32gui.PumpWaitingMessages()
        time.sleep(1)
Victor Marcelino
  • 219
  • 3
  • 10
  • [This Q&A](https://stackoverflow.com/q/1411186/1889329) shows you how to register and create a window that can receive the `WM_POWERBROADCAST` messages. – IInspectable Feb 10 '18 at 13:55
  • @IInspectable With the code and the fix proposed on the Q&A I could get it working as the author wanted, but even after adding `win32con.WM_POWERBROADCAST : wndproc` to the dictionary, the script still does not report anything when screen goes off (timer in windows power options, not physical power button as that is not the question) – Victor Marcelino Feb 11 '18 at 05:59
  • The 'fix' is not required; the power setting notifications work with message-only windows just fine. Maybe your call to `RegisterPowerSettingNotification` is wrong, or you aren't running a message loop. – IInspectable Feb 11 '18 at 06:27
  • @IInspectable I did not call `RegisterPowerSettingNotification` because I don't know how.to. I'm quite new to windows api's and ctypes – Victor Marcelino Feb 11 '18 at 13:33
  • What I have tried but with no success is calling the function as `ctypes.windll.user32.RegisterPowerSettingNotification(ctypes.wintypes.HANDLE(hwnd), '{guid-of-GUID_MONITOR_POWER_ON}', ctypes.wintypes.DWORD(0))` but still no messages – Victor Marcelino Feb 11 '18 at 13:39
  • I was able to solve in my case by using the browser window that was displayed anyway, and looking for the time between consecutive requestAnimationFrame loops. When the monitor was off this extended from a consistent 33ms to over 100ms. – EdF Feb 27 '23 at 10:38

1 Answers1

7

To make the code work it is needed the powerbroadcast_setting structure, a function declaration in ctypes with CFUNCTYPE and a cast from lparam to powerbroadcast_setting

Fully working code:

import win32con
import win32api
import win32gui
import time
from ctypes import POINTER, windll, Structure, cast, CFUNCTYPE, c_int, c_uint, c_void_p, c_bool
from comtypes import GUID
from ctypes.wintypes import HANDLE, DWORD


PBT_POWERSETTINGCHANGE = 0x8013
GUID_CONSOLE_DISPLAY_STATE = '{6FE69556-704A-47A0-8F24-C28D936FDA47}'
GUID_ACDC_POWER_SOURCE = '{5D3E9A59-E9D5-4B00-A6BD-FF34FF516548}'
GUID_BATTERY_PERCENTAGE_REMAINING = '{A7AD8041-B45A-4CAE-87A3-EECBB468A9E1}'
GUID_MONITOR_POWER_ON = '{02731015-4510-4526-99E6-E5A17EBD1AEA}'
GUID_SYSTEM_AWAYMODE = '{98A7F580-01F7-48AA-9C0F-44352C29E5C0}'


class POWERBROADCAST_SETTING(Structure):
    _fields_ = [("PowerSetting", GUID),
                ("DataLength", DWORD),
                ("Data", DWORD)]


def wndproc(hwnd, msg, wparam, lparam):
    if msg == win32con.WM_POWERBROADCAST:
        if wparam == win32con.PBT_APMPOWERSTATUSCHANGE:
            print('Power status has changed')
        if wparam == win32con.PBT_APMRESUMEAUTOMATIC:
            print('System resume')
        if wparam == win32con.PBT_APMRESUMESUSPEND:
            print('System resume by user input')
        if wparam == win32con.PBT_APMSUSPEND:
            print('System suspend')
        if wparam == PBT_POWERSETTINGCHANGE:
            print('Power setting changed...')
            settings = cast(lparam, POINTER(POWERBROADCAST_SETTING)).contents
            power_setting = str(settings.PowerSetting)
            data_length = settings.DataLength
            data = settings.Data
            if power_setting == GUID_CONSOLE_DISPLAY_STATE:
                if data == 0: print('Display off')
                if data == 1: print('Display on')
                if data == 2: print('Display dimmed')
            elif power_setting == GUID_ACDC_POWER_SOURCE:
                if data == 0: print('AC power')
                if data == 1: print('Battery power')
                if data == 2: print('Short term power')
            elif power_setting == GUID_BATTERY_PERCENTAGE_REMAINING:
                print('battery remaining: %s' % data)
            elif power_setting == GUID_MONITOR_POWER_ON:
                if data == 0: print('Monitor off')
                if data == 1: print('Monitor on')
            elif power_setting == GUID_SYSTEM_AWAYMODE:
                if data == 0: print('Exiting away mode')
                if data == 1: print('Entering away mode')
            else:
                print('unknown GUID')
        return True

    return False




if __name__ == "__main__":
    print("*** STARTING ***")
    hinst = win32api.GetModuleHandle(None)
    wndclass = win32gui.WNDCLASS()
    wndclass.hInstance = hinst
    wndclass.lpszClassName = "testWindowClass"
    CMPFUNC = CFUNCTYPE(c_bool, c_int, c_uint, c_uint, c_void_p)
    wndproc_pointer = CMPFUNC(wndproc)
    wndclass.lpfnWndProc = {win32con.WM_POWERBROADCAST : wndproc_pointer}
    try:
        myWindowClass = win32gui.RegisterClass(wndclass)
        hwnd = win32gui.CreateWindowEx(win32con.WS_EX_LEFT,
                                     myWindowClass, 
                                     "testMsgWindow", 
                                     0, 
                                     0, 
                                     0, 
                                     win32con.CW_USEDEFAULT, 
                                     win32con.CW_USEDEFAULT, 
                                     0, 
                                     0, 
                                     hinst, 
                                     None)
    except Exception as e:
        print("Exception: %s" % str(e))

    if hwnd is None:
        print("hwnd is none!")
    else:
        print("hwnd: %s" % hwnd)

    guids_info = {
                    'GUID_MONITOR_POWER_ON' : GUID_MONITOR_POWER_ON,
                    'GUID_SYSTEM_AWAYMODE' : GUID_SYSTEM_AWAYMODE,
                    'GUID_CONSOLE_DISPLAY_STATE' : GUID_CONSOLE_DISPLAY_STATE,
                    'GUID_ACDC_POWER_SOURCE' : GUID_ACDC_POWER_SOURCE,
                    'GUID_BATTERY_PERCENTAGE_REMAINING' : GUID_BATTERY_PERCENTAGE_REMAINING
                 }
    for name, guid_info in guids_info.items():
        result = windll.user32.RegisterPowerSettingNotification(HANDLE(hwnd), GUID(guid_info), DWORD(0))
        print('registering', name)
        print('result:', hex(result))
        print('lastError:', win32api.GetLastError())
        print()

    print('\nEntering loop')
    while True:
        win32gui.PumpWaitingMessages()
        time.sleep(1)
Victor Marcelino
  • 219
  • 3
  • 10
  • 2
    In this case you can create a [message-only window](https://msdn.microsoft.com/en-us/library/windows/desktop/ms632599.aspx#message_only). It is only used for messaging after all, not for its visual representation. – IInspectable Feb 12 '18 at 11:17