вот мой код. нужно следующее для find_target_process_and_window. мы находим, например окно класса "AfxControlBar42u", после мы проваливаемся в этот окно ниже и находим поле ввода


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
import threading
import tkinter as tk

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

# UIAutomation Client import после инициализации модуля
try:
    from comtypes.gen.UIAutomationClient import CUIAutomation

    uia = comtypes.client.CreateObject(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():
    """
    Возвращает (find_window_hwnd, target_edit_hwnd, target_pid, target_thread_id) или (None, None, None, None)
    """

    # target_process_name = "notepad.exe"
    # target_window_substr = "Найти"
    target_process_name = "EzCad2.exe"
    target_window_substr = "Текст"
    target_class_substr = "AfxControlBar42u"

    windows = []

    def enum_windows_callback(hwnd, extra):
        if win32gui.IsWindowVisible(hwnd) and win32gui.IsWindowEnabled(hwnd):
                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))
                        return
                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]

    # Теперь ищем нужное дочернее окно внутри main_hwnd
    # предположим, что целевой элемент — это Edit с заголовком 'Текст' (часть текста)

    edit_hwnd = None

    def enum_child_callback(hwnd_child, extra):
        class_name = win32gui.GetClassName(hwnd_child)
        window_text = win32gui.GetWindowText(hwnd_child)

        # По вашему описанию ищем окно "Текст" внутри
        # Здесь можно фильтровать по классу (например 'Edit') и тексту
        if class_name.lower() == target_class_substr.lower() and target_window_substr.lower() in window_text.lower():
            nonlocal edit_hwnd
            edit_hwnd = hwnd_child
            return False  # нашли нужное окно, прекращаем перечисление
        return True

    win32gui.EnumChildWindows(find_window_hwnd, enum_child_callback, None)

    hwnd_edit = None

    def enum_child_edit_callback(hwnd_child, extra):
        class_name = win32gui.GetClassName(hwnd_child)
        window_text = win32gui.GetWindowText(hwnd_child)

        textE = get_text_from_hwnd(hwnd_child)

        # По вашему описанию ищем окно "Текст" внутри
        # Здесь можно фильтровать по классу (например 'Edit') и тексту
        if class_name.lower() == "edit":
            nonlocal hwnd_edit
            hwnd_edit = hwnd_child
            return False  # нашли нужное окно, прекращаем перечисление
        return True



    win32gui.EnumChildWindows(edit_hwnd, enum_child_edit_callback, None)

    # Используем UI Automation для получения HWND поля Edit
    try:
        # elem_dialog = uia.ElementFromHandle(find_window_hwnd)
        # if not elem_dialog:
        #     logger.error("Не удалось получить UIA-элемент окна.")
        #     return None, None, None, None
        #
        # elem_edit = None
        # # condition = uia.CreatePropertyCondition(UIA_ControlTypePropertyId, 50033)
        # # elem_edit = elem_dialog.FindAll(UIA_STANDARD_TREESCOPE_DESCENDANTS, condition)
        #
        # # Условие по ControlType = Group (панель)
        # condition_controltype = uia.CreatePropertyCondition(UIA_ControlTypePropertyId, 50033)
        #
        # # Условие по имени (NameProperty) = "Текст"
        # UIA_NamePropertyId = 30005
        # condition_name = uia.CreatePropertyCondition(UIA_NamePropertyId, "Текст")
        #
        # # Объединяем условия "И"
        # condition_combined = uia.CreateAndCondition(condition_controltype, condition_name)
        #
        # # Ищем все элементы-панели с именем "Текст" в потомках
        # elements = elem_dialog.FindAll(UIA_STANDARD_TREESCOPE_DESCENDANTS, condition_combined)



        #
        # count = elements.Length
        #
        # for i in range(count):
        #     elem = elements.GetElement(i)  # получить i-й элемент из коллекции
        #
        #     condition = uia.CreatePropertyCondition(UIA_ControlTypePropertyId, UIA_EditControlTypeId)
        #     elem_edit  = elem.FindFirst(UIA_STANDARD_TREESCOPE_DESCENDANTS, condition)
        #
        #
        #     hwnd = elem.CurrentNativeWindowHandle
        #     name = elem.CurrentName  # название элемента (NameProperty)
        #     class_name = elem.CurrentClassName  # класс окна, если доступен
        #
        #     print(f"Элемент #{i}: HWND={hwnd}, Name='{name}', ClassName='{class_name}'")


        # 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 is_process_alive(pid):
    try:
        p = psutil.Process(pid)
        return p.is_running()
    except Exception:
        return False


##################################
# Здесь основные изменения: разделяем GUI и логику хуков

command_queue = queue.Queue()  # из GUI в хук
event_queue = queue.Queue()    # из хука в GUI

def hook_worker_main(stop_event):
    """
    Запускается в отдельном потоке, внутри HooksManager.
    Коммуникация с GUI — через очереди.
    """
    pythoncom.CoInitialize()
    hooks = None

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

    duplicate_confirmed = False
    duplicate_warning_sent = False

    def validate_text(text):
        nonlocal duplicate_confirmed, duplicate_warning_sent

        is_valid = len(text.strip()) > 0 and not any(c.isdigit() for c in text)

        if hooks is None:
            # Возможно, хуки ещё не установлены, просто игнорируем
            return

        try:
            if is_valid or duplicate_confirmed:
                if not win32gui.IsWindowEnabled(button_hwnd):
                    enable_window_and_refresh(button_hwnd, True)

                hooks.block_enter = False
                duplicate_warning_sent = False  # сброс при исправлении
                logger.info(f"Текст валиден: '{text}'. Кнопка и Enter разблокированы.")
            else:
                if win32gui.IsWindowEnabled(button_hwnd):
                    enable_window_and_refresh(button_hwnd, False)

                hooks.block_enter = True
                if not duplicate_warning_sent:
                    event_queue.put({'warning': 'duplicate'})
                    duplicate_warning_sent = True  # отправляем предупреждение один раз
                logger.info(f"Текст НЕ валиден: '{text}'. Кнопка и Enter заблокированы.")

        except Exception as e:
            logger.error(f"Ошибка в validate_text: {e}")

    try:
        while not stop_event.is_set():
            # Проверяем валидность окна/поля кнопки
            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()

                if not all([find_window_hwnd, target_edit_hwnd, target_pid]):
                    duplicate_confirmed = False
                    duplicate_warning_sent = False
                    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, lambda: find_window_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("Хуки установлены. Отслеживаем изменения.")

                event_queue.put({
                    'target_edit_hwnd': target_edit_hwnd,
                    'find_window_hwnd': find_window_hwnd,
                     'target_pid': target_pid
                     })

                try:
                    current_text = get_text_from_hwnd(target_edit_hwnd)
                    logger.debug(f"Начальная валидация текста: '{current_text}'")
                    validate_text(current_text)
                    event_queue.put(current_text)
                except Exception as e:
                    logger.error(f"Ошибка при получении текста для начальной валидации: {e}")

            # Обработка сообщений хуков
            if hooks:
                pythoncom.PumpWaitingMessages()

                # Обработка текстовых событий из hooks.text_queue
                try:
                    prev_text = ""

                    while True:
                        new_text = hooks.text_queue.get_nowait()
                        logger.debug(f"Получен текст из очереди: '{new_text}'")

                        if new_text != prev_text:
                            logger.debug("Текст изменился, сбрасываем подтверждение дубля")
                            duplicate_confirmed = False
                            prev_text = new_text  # обновляем

                        validate_text(new_text)
                        # Отправим текст в GUI (например, для обновления статуса)
                        event_queue.put(new_text)

                except queue.Empty:
                    pass

            # Обработка команд из GUI
            try:
                while True:
                    cmd = command_queue.get_nowait()
                    if cmd == "STOP":
                        logger.info("Получена команда STOP из GUI, выходим из хука.")
                        stop_event.set()
                        break
                    elif cmd == "CONFIRM_DUPLICATE":
                        logger.info("Получена команда подтверждения дубликата из GUI.")
                        duplicate_confirmed = True
                        duplicate_warning_sent = False

                        # Разблокируем кнопку и Enter
                        if hooks and button_hwnd:
                            if not win32gui.IsWindowEnabled(button_hwnd):
                                enable_window_and_refresh(button_hwnd, True)
                            hooks.block_enter = False
            except queue.Empty:
                pass

            time.sleep(0.05)

    except Exception as e:
        logger.critical(f"Ошибка в hook_worker_main: {e}")
        traceback.print_exc()
    finally:
        if hooks:
            hooks.remove_win_event_hook()
            hooks.remove_keyboard_hook()
        pythoncom.CoUninitialize()
        logger.info("hook_worker_main завершился")


class AppGUI:
    def __init__(self, root):
        self.root = root
        self.root.title("Монитор окна 'Найти'")
        self.root.geometry("350x150")

        self.status_label = tk.Label(self.root, text="Статус хуков: Выключены")
        self.status_label.pack(pady=10)

        self.text_label = tk.Label(self.root, text="Последний текст: —")
        self.text_label.pack(pady=10)

        self.start_button = tk.Button(self.root, text="Запустить хуки", command=self.start_hooks)
        self.start_button.pack(pady=5)

        self.stop_button = tk.Button(self.root, text="Остановить хуки", command=self.stop_hooks, state='disabled')
        self.stop_button.pack(pady=5)

        self.stop_event = None
        self.hook_thread = None

        self.target_edit_hwnd = None
        self.find_window_hwnd = None
        self.target_pid = None
        self.showing_duplicate_warning = False

        # Периодическая проверка очереди событий от хуков
        self.check_events()

        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

    def start_hooks(self):
        if self.hook_thread and self.hook_thread.is_alive():
            messagebox.showinfo("Информация", "Хуки уже запущены.")
            return

        def clear_queue(q):
            try:
                while True:
                    q.get_nowait()
            except queue.Empty:
                pass

        clear_queue(command_queue)

        self.stop_event = threading.Event()
        self.hook_thread = threading.Thread(target=hook_worker_main, args=(self.stop_event,), daemon=True)
        self.hook_thread.start()

        self.status_label.config(text="Статус хуков: Запущены")
        self.start_button.config(state='disabled')
        self.stop_button.config(state='normal')
        logger.info("Хуки запущены из GUI")

    def stop_hooks(self):
        if not self.hook_thread or not self.hook_thread.is_alive():
            messagebox.showinfo("Информация", "Хуки не запущены.")
            return

        if messagebox.askyesno("Подтверждение", "Остановить хуки?"):
            command_queue.put("STOP")
            self.stop_event.set()

            self.status_label.config(text="Статус хуков: Остановка...")
            self.start_button.config(state='normal')
            self.stop_button.config(state='disabled')
            logger.info("Хуки остановлены из GUI")

    def check_events(self):
        try:
            while True:
                evt = event_queue.get_nowait()

                if isinstance(evt, dict):
                    if 'target_edit_hwnd' in evt:
                        self.target_edit_hwnd = evt['target_edit_hwnd']
                    if 'find_window_hwnd' in evt:
                        self.find_window_hwnd = evt['find_window_hwnd']
                    if 'target_pid' in evt:
                        self.target_pid = evt['target_pid']
                    elif evt.get("warning") == "duplicate":
                        # Показать предупреждение с кнопкой
                        if not self.showing_duplicate_warning:
                            self.showing_duplicate_warning = True
                            self.show_duplicate_warning()
                    else:
                        self.text_label.config(text=f"Последний текст: {evt}")
                else:
                    self.text_label.config(text=f"Последний текст: {evt}")
        except queue.Empty:
            pass

        if self.hook_thread and not self.hook_thread.is_alive():
            self.status_label.config(text="Статус хуков: Остановлен")
            self.start_button.config(state='normal')
            self.stop_button.config(state='disabled')

        self.root.after(100, self.check_events)

    def show_duplicate_warning(self):
        # Показываем модальное предупреждение с кнопкой "Подтвердить ввод дубля"
        if not self.target_edit_hwnd:
            res = messagebox.askyesno("Обнаружен дубль", "Обнаружен дубль.\nПодтвердить ввод дубля?")
        else:
            win32gui.EnableWindow(self.target_edit_hwnd, False)
            self.root.update()
            res = messagebox.askyesno("Обнаружен дубль", "Обнаружен дубль.\nПодтвердить ввод дубля?")
            win32gui.EnableWindow(self.target_edit_hwnd, True)
            self.root.update()

        check_process = (self.target_edit_hwnd and is_window_valid(self.target_edit_hwnd) and
                self.find_window_hwnd and is_window_valid(self.find_window_hwnd) and
                self.target_pid and is_process_alive(self.target_pid))

        if res and check_process:
            # Отправляем команду для снимания блокировки
            command_queue.put("CONFIRM_DUPLICATE")

        self.showing_duplicate_warning = False

    def on_closing(self):
        if messagebox.askokcancel("Выход", "Закрыть программу?"):
            if self.hook_thread and self.hook_thread.is_alive():
                command_queue.put("STOP")
                self.stop_event.set()
                self.hook_thread.join(timeout=5)
            self.root.destroy()

def main():
    pythoncom.CoInitializeEx(pythoncom.COINIT_APARTMENTTHREADED)
    init_redis_keys()
    load_uia_module()

    root = tk.Tk()

    app = AppGUI(root)
    app.start_hooks()

    root.mainloop()

    # CoUninitialize после закрытия GUI (хуки уже остановлены в on_closing)
    pythoncom.CoUninitialize()
    logger.info("Программа завершена")

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