Загрузка данных


import tkinter as tk
from tkinter import colorchooser
import ctypes
import threading
import time

import win32api
import win32gui
import win32con

import keyboard
from pynput import mouse

# Включаем DPI Awareness, чтобы координаты мыши (физические пиксели) 
# точно совпадали с координатами оконного менеджера (логические пиксели)
try:
    ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception:
    ctypes.windll.user32.SetProcessDPIAware()


class GameOverlayApp:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Оверлей")
        self.root.geometry("320x180")
        self.root.attributes("-topmost", True)
        self.root.resizable(False, False)
        
        # Состояния: IDLE (ожидание), LISTENING (ждем клики), DRAWN (линия нарисована)
        self.state = 'IDLE' 
        self.points = []
        self.hotkey = 'f4'
        
        # Настройки линии
        self.line_thickness = tk.IntVar(value=3)
        self.line_color = '#ff0000'
        self.line_id = None
        self.mouse_listener = None
        
        self.setup_ui()
        self.setup_overlay()
        
        # Регистрация глобального хоткея
        keyboard.add_hotkey(self.hotkey, self.on_hotkey_pressed)

    def setup_ui(self):
        """Интерфейс настроек."""
        frame = tk.Frame(self.root, padx=10, pady=10)
        frame.pack(fill=tk.BOTH, expand=True)

        # Толщина
        tk.Label(frame, text="Толщина линии:").grid(row=0, column=0, sticky="w", pady=5)
        tk.Spinbox(frame, from_=1, to=20, textvariable=self.line_thickness, width=5).grid(row=0, column=1, sticky="w", pady=5)

        # Цвет
        tk.Label(frame, text="Цвет линии:").grid(row=1, column=0, sticky="w", pady=5)
        self.btn_color = tk.Button(frame, bg=self.line_color, width=5, command=self.choose_color)
        self.btn_color.grid(row=1, column=1, sticky="w", pady=5)

        # Хоткей
        tk.Label(frame, text="Горячая клавиша:").grid(row=2, column=0, sticky="w", pady=5)
        self.btn_rebind = tk.Button(frame, text=f"Изменить ({self.hotkey})", command=self.rebind_hotkey)
        self.btn_rebind.grid(row=2, column=1, sticky="ew", pady=5)

        # Статус
        self.lbl_status = tk.Label(frame, text="Статус: Ожидание хоткея", fg="black", font=("Arial", 9, "bold"))
        self.lbl_status.grid(row=3, column=0, columnspan=2, pady=10)

    def setup_overlay(self):
        """Создание невидимого кликабельного (click-through) оверлея поверх всех экранов."""
        self.overlay = tk.Toplevel(self.root)
        self.overlay.overrideredirect(True)
        self.overlay.attributes("-topmost", True)
        
        # Задаем хромакей-цвет для прозрачности (используем мадженту, чтобы не конфликтовать с черными линиями)
        trans_color = '#ff00ff'
        self.overlay.config(bg=trans_color)
        self.overlay.attributes("-transparentcolor", trans_color)
        
        # Захватываем весь виртуальный экран (для поддержки нескольких мониторов)
        self.v_width = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
        self.v_height = win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREEN)
        self.v_x = win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN)
        self.v_y = win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN)
        
        self.overlay.geometry(f"{self.v_width}x{self.v_height}+{self.v_x}+{self.v_y}")
        
        self.canvas = tk.Canvas(self.overlay, width=self.v_width, height=self.v_height, 
                                bg=trans_color, highlightthickness=0)
        self.canvas.pack(fill=tk.BOTH, expand=True)
        
        # === МАГИЯ WIN32 API ===
        # Делаем окно полностью проницаемым для мыши (Click-Through)
        self.overlay.update()
        hwnd = self.overlay.winfo_id()
        ex_style = win32gui.GetWindowLong(hwnd, win32con.GWL_EXSTYLE)
        win32gui.SetWindowLong(hwnd, win32con.GWL_EXSTYLE, 
                               ex_style | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT)

    def choose_color(self):
        color = colorchooser.askcolor(initialcolor=self.line_color, title="Выберите цвет")
        if color[1]:
            self.line_color = color[1]
            self.btn_color.config(bg=self.line_color)

    def rebind_hotkey(self):
        """Интерактивное переназначение клавиши без блокировки GUI."""
        self.btn_rebind.config(text="Нажмите клавишу...", state=tk.DISABLED)
        threading.Thread(target=self._wait_for_new_hotkey, daemon=True).start()

    def _wait_for_new_hotkey(self):
        keyboard.unhook_all_hotkeys()
        new_key = keyboard.read_key()
        self.hotkey = new_key
        # Небольшой слип, чтобы отжатие клавиши не вызвало мгновенный триггер
        time.sleep(0.2)
        keyboard.add_hotkey(self.hotkey, self.on_hotkey_pressed)
        # Возвращаем выполнение в основной поток tkinter
        self.root.after(0, self._finish_rebind, new_key)

    def _finish_rebind(self, new_key):
        self.btn_rebind.config(text=f"Изменить ({new_key})", state=tk.NORMAL)

    def on_hotkey_pressed(self):
        """Обработчик хоткея. Вызывается из потока keyboard, поэтому делегируем в main loop."""
        self.root.after(0, self.handle_state_machine)

    def handle_state_machine(self):
        """Логика переключения состояний по хоткею."""
        if self.state == 'IDLE':
            self.start_listening()
        elif self.state == 'LISTENING':
            # Отмена выбора, если передумали
            self.stop_listening()
        elif self.state == 'DRAWN':
            # Стираем линию при повторном нажатии
            self.clear_line()

    def start_listening(self):
        self.state = 'LISTENING'
        self.points = []
        self.lbl_status.config(text="Статус: Ожидание 2-х кликов ЛКМ...", fg="blue")
        
        # Запускаем пассивный хук мыши (не блокирует оригинальные клики)
        self.mouse_listener = mouse.Listener(on_click=self.on_mouse_click)
        self.mouse_listener.start()

    def stop_listening(self):
        self.state = 'IDLE'
        if self.mouse_listener:
            self.mouse_listener.stop()
            self.mouse_listener = None
        self.lbl_status.config(text="Статус: Ожидание хоткея", fg="black")

    def on_mouse_click(self, x, y, button, pressed):
        """Коллбэк хука мыши. Запускается в фоновом потоке pynput."""
        if button == mouse.Button.left and pressed:
            self.points.append((x, y))
            if len(self.points) == 2:
                # Отрисовка в основном потоке GUI
                self.root.after(0, self.draw_line)
                return False  # Остановка хука pynput

    def draw_line(self):
        self.state = 'DRAWN'
        if self.line_id:
            self.canvas.delete(self.line_id)
            
        p1, p2 = self.points
        
        # Корректируем глобальные координаты под локальные координаты Canvas
        # (Особенно важно для мультимониторных систем с отрицательными координатами)
        c_x1, c_y1 = p1[0] - self.v_x, p1[1] - self.v_y
        c_x2, c_y2 = p2[0] - self.v_x, p2[1] - self.v_y

        self.line_id = self.canvas.create_line(
            c_x1, c_y1, c_x2, c_y2, 
            fill=self.line_color, 
            width=self.line_thickness.get(),
            capstyle=tk.ROUND
        )
        
        self.lbl_status.config(text="Статус: Линия нарисована", fg="green")
        if self.mouse_listener:
            self.mouse_listener.stop()
            self.mouse_listener = None

    def clear_line(self):
        self.state = 'IDLE'
        if self.line_id:
            self.canvas.delete(self.line_id)
            self.line_id = None
        self.lbl_status.config(text="Статус: Ожидание хоткея", fg="black")

    def run(self):
        self.root.mainloop()


if __name__ == "__main__":
    app = GameOverlayApp()
    app.run()