Загрузка данных
import tkinter as tk
from tkinter import colorchooser
import ctypes
from ctypes import wintypes
import threading
import time
import win32api
import win32gui
import win32con
import keyboard
from pynput import mouse
# --- Подключение Win32 функций через ctypes для надежности ---
user32 = ctypes.windll.user32
gdi32 = ctypes.windll.gdi32
# Установка DPI Awareness (важно для координат)
try:
user32.SetProcessDpiAwarenessContext(ctypes.c_void_p(-4)) # Per Monitor Awareness V2
except Exception:
try:
ctypes.windll.shcore.SetProcessDpiAwareness(2)
except Exception:
user32.SetProcessDPIAware()
class GameOverlayApp:
def __init__(self):
self.root = tk.Tk()
self.root.title("Settings")
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.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):
"""Создание оверлея с использованием хардкорного Win32 API для прозрачности."""
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)
# Ждем, пока Tkinter создаст окно реально в ОС
self.overlay.update_idletasks()
# Получаем HWND (дескриптор окна)
self.hwnd = user32.GetParent(self.overlay.winfo_id())
# --- ПРИМЕНЕНИЕ СТИЛЕЙ WIN32 (WS_EX_LAYERED и WS_EX_TRANSPARENT) ---
# Получаем текущие расширенные стили
ex_style = user32.GetWindowLongW(self.hwnd, win32con.GWL_EXSTYLE)
# Накладываем стиль Многослойного окна (LAYERED) и Прозрачного для мыши (TRANSPARENT)
new_ex_style = ex_style | win32con.WS_EX_LAYERED | win32con.WS_EX_TRANSPARENT
user32.SetWindowLongW(self.hwnd, win32con.GWL_EXSTYLE, new_ex_style)
# Устанавливаем Ключ Прозрачности (ColorKey).
# Все пиксели цвета self.bg_color_key (черные) станут на 100% прозрачными визуально и для мыши.
# RGB ключ для черного: 0, 0, 0
user32.SetLayeredWindowAttributes(self.hwnd, 0x000000, 0, win32con.LWA_COLORKEY)
# Принудительное обновление стилей
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]:
if color[1] == '#000000': # Запрет черного цвета
self.line_color = '#000001' # Почти черный, но не ключ
else:
self.line_color = 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_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):
if button == mouse.Button.left and pressed:
# Предотвращаем клик по самому окну настроек
try:
if user32.GetForegroundWindow() == self.root.winfo_id():
return
except: pass
self.points.append((x, y))
if len(self.points) == 2:
self.root.after(0, self.draw_line)
return False
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
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")
self.stop_listening()
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()