0

I am trying to create a callback to call when the effective power mode changes, especially for the game mode in Windows, but I couldn't find a way to do it with Python.

More information in

https://learn.microsoft.com/en-us/windows/win32/api/powersetting/nf-powersetting-powerregisterforeffectivepowermodenotifications

I tried the pywin32 but it seems that nothing is realted to this.

Jason
  • 13
  • 3

1 Answers1

0

Apparently, PyWin32 doesn't wrap PowerRegisterForEffectivePowerModeNotifications (as a side note, a little while ago I added support for GetSystemPowerStatus: [GitHub]: mhammond/pywin32 - Add GetSystemPowerStatus wrapper).
The (an) alternative is using [Python.Docs]: ctypes - A foreign function library for Python (there's a lot of boilerplate code required).

code00.py:

#!/usr/bin/env python

import ctypes as cts
import ctypes.wintypes as wts
import enum
import sys
import time


HRESULT = cts.c_long
IntPtr = cts.POINTER(cts.c_int)
VoidPtrPtr = cts.POINTER(cts.c_void_p)

EffectivePowerModeCallback = cts.WINFUNCTYPE(None, cts.c_int, cts.c_void_p)

EFFECTIVE_POWER_MODE_V1 = 0x00000001
EFFECTIVE_POWER_MODE_V2 = 0x00000002

ERROR_SUCCESS = 0


class EffectivePowerMode(enum.IntEnum):
    BatterySaver = 0
    BetterBattery = enum.auto()
    Balanced = enum.auto()
    HighPerformance = enum.auto()
    MaxPerformance = enum.auto()
    GameMode = enum.auto()
    MixedReality = enum.auto()


powerprof = cts.WinDLL("Powrprof.dll")
PowerRegisterForEffectivePowerModeNotifications = powerprof.PowerRegisterForEffectivePowerModeNotifications
PowerRegisterForEffectivePowerModeNotifications.argtypes = (wts.ULONG, EffectivePowerModeCallback, cts.c_void_p, VoidPtrPtr)
PowerRegisterForEffectivePowerModeNotifications.restype = HRESULT
PowerUnregisterFromEffectivePowerModeNotifications = powerprof.PowerUnregisterFromEffectivePowerModeNotifications
PowerUnregisterFromEffectivePowerModeNotifications.argtypes = (cts.c_void_p,)
PowerUnregisterFromEffectivePowerModeNotifications.restype = HRESULT


@EffectivePowerModeCallback
def cb(mode, ctx):
    print("\n  Callback: {:s} ({:d}), {:}".format(EffectivePowerMode(mode).name, mode, ctx))
    if not ctx:
        return
    parg = cts.cast(ctx, IntPtr)
    print("  Callback: {:} {:}".format(parg, parg.contents))
    if mode == EffectivePowerMode.BetterBattery.value:  # e.g.: Plug the power cable out
        print("  Callback - signaling exit")
        parg.contents.value = 0


def main(*argv):
    ctx = cts.pointer(cts.c_int(3141593))  # Dummy initial value
    print("Context ({:}) value: {:d}".format(ctx, ctx.contents.value))
    handle = cts.c_void_p()
    print("Handle: {:}".format(handle))
    res = PowerRegisterForEffectivePowerModeNotifications(EFFECTIVE_POWER_MODE_V2, cb, ctx, cts.byref(handle))
    print("Handle (after registration): {:}".format(handle))
    if res != ERROR_SUCCESS:
        print("PowerRegisterForEffectivePowerModeNotifications returned 0x{:08X}".format(res))
        return -1
    print("Waiting for BetterBattery to be activated...")
    while True:
        if ctx and ctx.contents.value == 0:
            break
        time.sleep(0.5)
    PowerUnregisterFromEffectivePowerModeNotifications(handle)


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q075799861]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py
Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec  6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] 064bit on win32

Context (<ctypes.wintypes.LP_c_long object at 0x000001C7731FCBC0>) value: 3141593
Handle: c_void_p(None)
Handle (after registration): c_void_p(1956130359280)
Waiting for BetterBattery to be activated...

  Callback: MaxPerformance (4), 1956141584904
  Callback: <ctypes.wintypes.LP_c_long object at 0x000001C7731FD440> c_long(3141593)

  Callback: BetterBattery (1), 1956141584904
  Callback: <ctypes.wintypes.LP_c_long object at 0x000001C7731FD440> c_long(3141593)
  Callback - signaling exit

Done.

Needless to say that the program stopped when I plugged out the power cord on my laptop (don't try this on a regular PC).

Might also want to check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for a common pitfall when working with CTypes (calling functions).

CristiFati
  • 38,250
  • 9
  • 50
  • 87