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


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
from PIL import Image, ImageTk, ImageGrab

user32 = ctypes.windll.user32

try:
    user32.SetProcessDpiAwarenessContext(ctypes.c_void_p(-4))
except Exception:
    try:
        ctypes.windll.shcore.SetProcessDpiAwareness(2)
    except Exception:
        user32.SetProcessDPIAware()


class GameOverlayApp:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("Настройки линии")
        self.root.geometry("320x190")
        self.root.attributes("-topmost", True)
        self.root.resizable(False, False)
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
        
        self.state = 'IDLE' 
        self.points = []
        self.hotkey = 'f4'
        
        self.line_thickness = tk.IntVar(value=3)
        self.line_color = '#00FF00' 
        self.line_id = None
        self.mouse_listener = None
        self.screenshot_image = None
        self.bg_image_id = None
        
        self.setup_ui()
        self.setup_overlay_win32()
        
        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_win32(self):
        self.overlay = tk.Toplevel(self.root)
        self.overlay.overrideredirect(True)
        self.overlay.attributes("-topmost", True)
        
        self.bg_color_key = '#000000' 
        self.overlay.config(bg=self.bg_color_key)
        
        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=self.bg_color_key, highlightthickness=0)
        self.canvas.pack(fill=tk.BOTH, expand=True)
        
        self.overlay.update_idletasks()
        self.hwnd = user32.GetParent(self.overlay.winfo_id())
        
        # По умолчанию включаем прозрачность для мыши (режим IDLE)
        self.set_click_through(True)

    def set_click_through(self, enabled):
        """Переключение режима окна: сквозь него (True) или кликабельное (False)."""
        ex_style = user32.GetWindowLongW(self.hwnd, win32con.GWL_EXSTYLE)
        if enabled:
            # Маска для пробития мыши + хромакей
            new_ex_style = ex_style | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT
            user32.SetWindowLongW(self.hwnd, win32con.GWL_EXSTYLE, new_ex_style)
            user32.SetLayeredWindowAttributes(self.hwnd, 0x000000, 0, win32con.LWA_COLORKEY)
        else:
            # Убираем WS_EX_TRANSPARENT, чтобы окно ловило мышь, но оставляем LAYERED, если нужно убрать прозрачный цвет
            new_ex_style = (ex_style | win32con.WS_EX_LAYERED) & ~win32con.WS_EX_TRANSPARENT
            user32.SetWindowLongW(self.hwnd, win32con.GWL_EXSTYLE, new_ex_style)
            # Отключаем скрытие по ColorKey на время выбора точек, чтобы видеть скриншот
            user32.SetLayeredWindowAttributes(self.hwnd, 0, 255, win32con.LWA_ALPHA)
            
        user32.SetWindowPos(self.hwnd, win32con.HWND_TOPMOST, 0,0,0,0, 
                             win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_FRAMECHANGED | win32con.SWP_SHOWWINDOW)

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

    def rebind_hotkey(self):
        self.btn_rebind.config(text="Нажмите клавишу...", state=tk.DISABLED)
        keyboard.unhook_all_hotkeys()
        threading.Thread(target=self._wait_for_new_hotkey, daemon=True).start()

    def _wait_for_new_hotkey(self):
        new_key = keyboard.read_key(suppress=False)
        time.sleep(0.3) 
        self.hotkey = new_key
        keyboard.add_hotkey(self.hotkey, self.on_hotkey_pressed)
        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):
        self.root.after(0, self.handle_state_machine)

    def handle_state_machine(self):
        if self.state == 'IDLE':
            self.start_screenshot_mode()
        elif self.state == 'LISTENING':
            self.stop_screenshot_mode()
        elif self.state == 'DRAWN':
            self.clear_line()

    def start_screenshot_mode(self):
        self.state = 'LISTENING'
        self.points = []
        self.lbl_status.config(text="Режим прицеливания! Кликните 2 точки", fg="blue")
        
        # 1. Делаем мгновенный снимок экрана
        # bbox захватывает область вирт. экрана (учитывая мультимониторы)
        screenshot = ImageGrab.grab(bbox=(self.v_x, self.v_y, self.v_x + self.v_width, self.v_y + self.v_height))
        self.screenshot_image = ImageTk.PhotoImage(screenshot)
        
        # 2. Выводим снимок на Canvas
        self.bg_image_id = self.canvas.create_image(0, 0, image=self.screenshot_image, anchor=tk.NW)
        
        # 3. Делаем оверлей непроницаемым для мыши (теперь клики идут в программу, а игра "замерла")
        self.set_click_through(False)
        
        # 4. Локально вешаем клики мыши на Canvas (хуки win32 больше не нужны, кликаем стандартно)
        self.canvas.bind("<Button-1>", self.on_canvas_click)

    def stop_screenshot_mode(self):
        self.state = 'IDLE'
        self.canvas.unbind("<Button-1>")
        if self.bg_image_id:
            self.canvas.delete(self.bg_image_id)
            self.bg_image_id = None
        self.screenshot_image = None
        
        # Возвращаем режим сквозного клика (окно исчезает, клики идут в Roblox)
        self.set_click_through(True)
        self.lbl_status.config(text="Статус: Ожидание", fg="black")

    def on_canvas_click(self, event):
        # Получаем глобальные координаты клика
        global_x = event.x + self.v_x
        global_y = event.y + self.v_y
        
        self.points.append((global_x, global_y))
        
        # Визуальный маркер первой точки (небольшой крестик), чтобы видеть, куда кликнули
        self.canvas.create_line(event.x-3, event.y, event.x+3, event.y, fill=self.line_color, tags="temp_marker")
        self.canvas.create_line(event.x, event.y-3, event.x, event.y+3, fill=self.line_color, tags="temp_marker")
        
        if len(self.points) == 2:
            self.canvas.delete("temp_marker")
            self.draw_line()

    def draw_line(self):
        self.state = 'DRAWN'
        if self.line_id:
            self.canvas.delete(self.line_id)
            
        p1, p2 = self.points
        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

        # Удаляем фоновый рисунок перед переходом в режим оверлея
        if self.bg_image_id:
            self.canvas.delete(self.bg_image_id)
            self.bg_image_id = None
        self.screenshot_image = None

        # Рисуем саму целевую линию
        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.set_click_through(True)
        self.lbl_status.config(text="Статус: Линия ОК", fg="green")

    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 on_closing(self):
        keyboard.unhook_all_hotkeys()
        self.root.destroy()

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

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