хорошо. вот мой финальный код программ. нужно добавить функционал

import json
import ctypes
import ctypes.wintypes
import time
import traceback
import pythoncom
import win32gui
import win32process
import psutil
import comtypes
import comtypes.client
import queue  # Для передачи данных из колбэка в основной поток
import redis

from logger_setup import setup_logger
from constants import *
from hooks import HookManager
from redis_conn import redis_sync_conn

# UIAutomation Client import после инициализации модуля
try:
    from comtypes.gen.UIAutomationClient import CUIAutomation
except ImportError:
    # Если он не сгенерен, можно сгенерировать вручную, или оставить как есть
    pass

# Переменные из win32api для SendMessage
user32 = ctypes.windll.user32

logger = setup_logger(__name__)

# Установка "статических" значений Redis
def init_redis_keys():
    redis_sync_conn.set('st_1d::', json.dumps(list(range(560, 980))))
    redis_sync_conn.set('st_1e::', json.dumps(list(range(999, 2500))))
    redis_sync_conn.set('st_1f::', json.dumps(list(range(2600, 3100))))

# ---- UIAutomation и поиск окна/элементов ----
def load_uia_module():
    try:
        comtypes.client.GetModule("UIAutomationCore.dll")
    except Exception as e:
        logger.error(f"Ошибка загрузки UIAutomationCore.dll: {e}")
        raise e

def find_target_process_and_window(target_process_name, target_window_substr):
    """
    Возвращает (find_window_hwnd, target_edit_hwnd, target_pid, target_thread_id) или (None, None, None, None)
    """
    windows = []

    def enum_windows_callback(hwnd, extra):
        if win32gui.IsWindowVisible(hwnd):
            title = win32gui.GetWindowText(hwnd)
            if target_window_substr in title:
                try:
                    thread_id, proc_id = win32process.GetWindowThreadProcessId(hwnd)
                    p = psutil.Process(proc_id)
                    if p.name().lower() == target_process_name.lower():
                        windows.append((hwnd, proc_id, thread_id))
                except (psutil.NoSuchProcess, psutil.AccessDenied, win32process.error):
                    pass
        return True

    win32gui.EnumWindows(enum_windows_callback, None)
    if not windows:
        return None, None, None, None

    find_window_hwnd, target_pid, target_thread_id = windows[0]

    # Используем UI Automation для получения HWND поля Edit
    try:
        uia = comtypes.client.CreateObject(CUIAutomation)
        elem_dialog = uia.ElementFromHandle(find_window_hwnd)
        if not elem_dialog:
            logger.error("Не удалось получить UIA-элемент окна.")
            return None, None, None, None

        condition = uia.CreatePropertyCondition(UIA_ControlTypePropertyId, UIA_EditControlTypeId)
        elem_edit = elem_dialog.FindFirst(UIA_STANDARD_TREESCOPE_DESCENDANTS, condition)
        if not elem_edit:
            logger.error("Не найден элемент Edit в окне.")
            return None, None, None, None

        hwnd_edit = elem_edit.CurrentNativeWindowHandle

        if not hwnd_edit:
            logger.error("Не удалось получить HWND для Edit-элемента.")
            return None, None, None, None

        logger.info(f"Найден элемент Edit с HWND: {hwnd_edit}")
        return find_window_hwnd, hwnd_edit, target_pid, target_thread_id
    except Exception as e:
        logger.error(f"Ошибка UI Automation: {e}")
        return None, None, None, None

def get_text_from_hwnd(hwnd):
    if hwnd is None or hwnd == 0:
        return ""

    length = user32.SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0)
    if length == 0:
        return ""

    buf = ctypes.create_unicode_buffer(length + 1)
    user32.SendMessageW(hwnd, WM_GETTEXT, length + 1, ctypes.byref(buf))
    return buf.value

# --- Вспомогательные функции для UI ---
def get_hwnd_of_button(parent_hwnd, button_text):
    target_text = button_text.replace("&", "").strip()
    found_hwnds = []

    def enum_child(hwnd, lParam):
        class_name = win32gui.GetClassName(hwnd)
        text = win32gui.GetWindowText(hwnd)
        if class_name.lower() == "button" and text.replace("&", "").strip() == target_text:
            found_hwnds.append(hwnd)
            return False
        return True

    win32gui.EnumChildWindows(parent_hwnd, enum_child, None)
    return found_hwnds[0] if found_hwnds else None

def enable_window_and_refresh(hwnd_button, enable=True):
    win32gui.EnableWindow(hwnd_button, enable)
    parent_hwnd = win32gui.GetParent(hwnd_button)
    if parent_hwnd:
        user32.InvalidateRect(parent_hwnd, None, True)
        user32.UpdateWindow(parent_hwnd)
    else:
        user32.InvalidateRect(hwnd_button, None, True)
        user32.UpdateWindow(hwnd_button)

# ---- Основная логика ----
def is_window_valid(hwnd):
    """Проверить, что окно существует и видимо."""
    if not hwnd or hwnd == 0:
        return False
    return win32gui.IsWindow(hwnd) and win32gui.IsWindowVisible(hwnd)


def main():
    hooks = None

    pythoncom.CoInitializeEx(pythoncom.COINIT_APARTMENTTHREADED)
    init_redis_keys()
    load_uia_module()

    target_process_name = "notepad.exe"
    target_window_substr = "Найти"

    find_window_hwnd, target_edit_hwnd, target_pid, target_thread_id = (None, None, None, None)
    button_hwnd = None

    try:
        logger.info("Запуск мониторинга окна 'Найти'")

        def validate_text(text):
            is_valid = len(text.strip()) > 0 and not any(c.isdigit() for c in text)
            if is_valid:
                if not win32gui.IsWindowEnabled(button_hwnd):
                    enable_window_and_refresh(button_hwnd, True)
                hooks.block_enter = False
                logger.info(f"Текст валиден: '{text}'. Кнопка и Enter разблокированы.")
            else:
                if win32gui.IsWindowEnabled(button_hwnd):
                    enable_window_and_refresh(button_hwnd, False)
                hooks.block_enter = True
                logger.error(f"Текст НЕ валиден: '{text}'. Кнопка и Enter заблокированы.")

        while True:
            # Проверяем валидность окна и полей Edit и кнопки "Найти далее"
            if not is_window_valid(find_window_hwnd) or not is_window_valid(target_edit_hwnd) or not button_hwnd or not is_window_valid(button_hwnd):
                # Если хуки были установлены, очистить
                if hooks:
                    logger.info("Окно или кнопка закрыты/недоступны. Удаляем хуки.")
                    hooks.remove_win_event_hook()
                    hooks.remove_keyboard_hook()
                    hooks = None

                # Пытаемся найти окно и элементы заново
                logger.debug("Поиск окна 'Найти' и поле Edit...")
                find_window_hwnd, target_edit_hwnd, target_pid, target_thread_id = find_target_process_and_window(target_process_name, target_window_substr)

                if not all([find_window_hwnd, target_edit_hwnd, target_pid]):
                    logger.debug("Окно или поле Edit не найдены, пробуем позже...")
                    # сброс кнопки на None, чтобы в следующий раз искался заново
                    button_hwnd = None
                    time.sleep(1)
                    continue

                # Найти кнопку "Найти далее"
                button_hwnd = get_hwnd_of_button(find_window_hwnd, "Найти далее")
                if not button_hwnd:
                    logger.warning("Кнопка 'Найти далее' не найдена, пробуем позже...")
                    # сбросить окна, чтобы перезапустить поиск
                    find_window_hwnd = None
                    target_edit_hwnd = None
                    target_pid = None
                    time.sleep(1)
                    continue

                hooks = HookManager(lambda: target_edit_hwnd)

                # Устанавливаем хуки
                if not hooks.install_keyboard_hook():
                    logger.error("Не удалось установить клавиатурный хук. Пытаемся позже...")
                    hooks = None
                    time.sleep(1)
                    continue

                if not hooks.install_win_event_hook(target_pid):
                    logger.error("Не удалось установить WinEventHook. Пытаемся позже...")
                    hooks.remove_keyboard_hook()
                    hooks = None
                    time.sleep(1)
                    continue

                logger.info("Хуки установлены. Отслеживаем изменения.")

                # Вызов validate_text для текущего содержимого поля Edit (чтобы сразу обновить состояние UI)
                try:
                    current_text = get_text_from_hwnd(target_edit_hwnd)
                    logger.debug(f"Начальная валидация текста: '{current_text}'")
                    validate_text(current_text)
                except Exception as e:
                    logger.error(f"Ошибка при получении текста для начальной валидации: {e}")

            # Если хуки есть, обрабатываем время ожидания и поступающие события
            if hooks:
                pythoncom.PumpWaitingMessages()

                try:
                    while True:  # обработать всю очередь
                        new_text = hooks.text_queue.get_nowait()
                        logger.debug(f"Получен текст из очереди: '{new_text}'")
                        validate_text(new_text)
                except queue.Empty:
                    pass

            time.sleep(0.05)

    except KeyboardInterrupt:
        logger.info("Выход по Ctrl+C")
    except Exception as e:
        logger.critical(f"Ошибка в main: {e}")
        traceback.print_exc()
    finally:
        if hooks:
            hooks.remove_win_event_hook()
            hooks.remove_keyboard_hook()
        pythoncom.CoUninitialize()
        logger.info("Завершение работы и очистка ресурсов")

if __name__ == "__main__":
    try:
        main()
    finally:
        print("Вызов CoUninitialize из __main__ finally.")
        pythoncom.CoUninitialize()