Загрузка данных
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()