pythonpywin32win32gui

win32gui.EnumWindows starting with invalid window handle?


I have been using this piece of code I made for a few months now to check if a certain group of windows I have open are stuck on a certain window title for a given amount of seconds, and then closes them if so. This code is continuously ran on my VPS.

import time
import datetime
import win32gui
import win32process

max_downtime = 90
pid_downtimes = {}
cur_pids = []
prev_pids = []

def winEnumHandler(hwnd, ctx):
    class_name = win32gui.GetClassName(hwnd)
    window_text = win32gui.GetWindowText(hwnd)

    if class_name != "ConsoleWindowClass":
        return

    if not window_text.startswith("Plutonium"):
        return

    if "Server restarter" in window_text:
        return

    if "Dedicated Server" in window_text:
        return

    tid, pid = win32process.GetWindowThreadProcessId(hwnd)

    if pid not in pid_downtimes:
        pid_downtimes[pid] = 0

    pid_downtimes[pid] += 1

    if pid_downtimes[pid] >= max_downtime:
        print("Server restarted at:", datetime.datetime.now())
        win32gui.SendMessage(hwnd, 16) # close window
        return

    cur_pids.append(pid)


while True:
    win32gui.EnumWindows(winEnumHandler, None)

    old_pids = [pid for pid in prev_pids if pid not in cur_pids]

    for pid in old_pids:
        if pid in pid_downtimes:
            del pid_downtimes[pid]

    prev_pids = cur_pids
    cur_pids = []

    time.sleep(1)

A few days ago, for the first time it crashed.

Traceback (most recent call last):
line 43, in <module>
    win32gui.EnumWindows(winEnumHandler, None)
line 12, in winEnumHandler
    class_name = win32gui.GetClassName(hwnd)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^
pywintypes.error: (1400, 'GetClassName', 'Invalid window handle.')

Anyone know what would cause this to crash after running smoothly for months? It seemed to have started the function winEnumHandler with an invalid window handle from win32gui.EnumWindows, which I didn't know was possible.


Solution

  • According to [MS.Learn]: GetClassName function (winuser.h) (emphasis is mine):

    If the function fails, the return value is zero. To get extended error information, call GetLastError function.

    so this function can fail for certain windows (maybe there are some that don't have a class?). So your code must handle this scenario. Could be something like:

    import pywintypes
    
    # ...
    
    def win_enum_handler(hwnd, ctx):
        window_text = win32gui.GetWindowText(hwnd)
        try:
            class_name = win32gui.GetClassName(hwnd)
        except pywintypes.error:
            class_name = "Could not be determined"
    
        # ...