import sys import time import threading import tkinter as tk from tkinter import ttk, messagebox from pynput.mouse import Button, Controller as MouseController from pynput import keyboard VERSION = "1.0" # 每次修改 +0.1;大改版 +1 需經小威同意 class AutoClickerApp: def __init__(self, root: tk.Tk): self.root = root self.root.title(f"滑鼠連點工具 v{VERSION}") self.root.geometry("360x280") self.root.resizable(False, False) # 視窗保持置頂 self.root.wm_attributes("-topmost", True) # 核心狀態控制 self.is_clicking = False self.interval_sec = 0.05 # 預設 50 ms self.stop_app_event = threading.Event() self.click_trigger_event = threading.Event() # 滑鼠控制器 self.mouse = MouseController() # 設定介面樣式與佈局 self._setup_style() self._create_widgets() # 啟動滑鼠點擊背景線程 self.click_thread = threading.Thread(target=self._click_worker, daemon=True) self.click_thread.start() # 啟動全域熱鍵監聽 (監聽 F10) self.listener = keyboard.Listener(on_release=self._on_key_release) self.listener.daemon = True self.listener.start() # 視窗關閉事件綁定 self.root.protocol("WM_DELETE_WINDOW", self._on_closing) # 置中視窗 self._center_window() def _setup_style(self): style = ttk.Style() try: style.theme_use("clam") except Exception: pass def _create_widgets(self): main_frame = tk.Frame(self.root, bg="#f8fafc", padx=20, pady=16) main_frame.pack(fill=tk.BOTH, expand=True) # 標題區 title_label = tk.Label( main_frame, text="🖱️ 高速滑鼠連點工具", font=("Microsoft JhengHei", 14, "bold"), bg="#f8fafc", fg="#0f172a" ) title_label.pack(anchor="w", pady=(0, 4)) subtitle_label = tk.Label( main_frame, text="全域熱鍵 [F10] 隨時啟動 / 停止連點", font=("Microsoft JhengHei", 9), bg="#f8fafc", fg="#64748b" ) subtitle_label.pack(anchor="w", pady=(0, 14)) # 連點間隔設定區 interval_frame = tk.Frame(main_frame, bg="#ffffff", bd=1, relief="solid", padx=12, pady=10) interval_frame.pack(fill=tk.X, pady=(0, 14)) # 輕微自訂框線顏色 (Windows 預設 solid 搭配深底色時柔和處理) interval_label = tk.Label( interval_frame, text="連點間隔 (毫秒 ms):", font=("Microsoft JhengHei", 10, "bold"), bg="#ffffff", fg="#334155" ) interval_label.pack(side=tk.LEFT) self.interval_var = tk.StringVar(value="50") self.interval_entry = tk.Entry( interval_frame, textvariable=self.interval_var, font=("Segoe UI", 11, "bold"), width=7, justify="center", bd=1, relief="solid", highlightthickness=1, highlightcolor="#3b82f6" ) self.interval_entry.pack(side=tk.RIGHT) # 狀態顯示標籤 self.status_frame = tk.Frame(main_frame, bg="#f1f5f9", padx=10, pady=8, bd=1, relief="groove") self.status_frame.pack(fill=tk.X, pady=(0, 14)) self.status_label = tk.Label( self.status_frame, text="狀態:待命中(按 F10 啟動)", font=("Microsoft JhengHei", 10, "bold"), bg="#f1f5f9", fg="#475569" ) self.status_label.pack(anchor="center") # 啟動 / 停止 按鈕 self.toggle_btn = tk.Button( main_frame, text="▶ 啟動連點 (F10)", font=("Microsoft JhengHei", 11, "bold"), bg="#2563eb", fg="#ffffff", activebackground="#1d4ed8", activeforeground="#ffffff", relief="flat", cursor="hand2", pady=8, command=self.toggle_clicking ) self.toggle_btn.pack(fill=tk.X) def _center_window(self): self.root.update_idletasks() w = self.root.winfo_width() h = self.root.winfo_height() ws = self.root.winfo_screenwidth() hs = self.root.winfo_screenheight() x = (ws // 2) - (w // 2) y = (hs // 2) - (h // 2) self.root.geometry(f"{w}x{h}+{x}+{y}") def _validate_interval(self) -> bool: raw_val = self.interval_var.get().strip() try: val = int(raw_val) if val < 1: raise ValueError("數值必須 >= 1") self.interval_sec = val / 1000.0 return True except ValueError: messagebox.showwarning( "輸入格式錯誤", "連點間隔僅接受大於或等於 1 的整數(毫秒)。\n系統已自動重置為預設值 50 ms。" ) self.interval_var.set("50") self.interval_sec = 0.05 return False def toggle_clicking(self): """由按鈕或全域熱鍵觸發""" if self.is_clicking: self._stop_clicking() else: if not self._validate_interval(): return self._start_clicking() def _start_clicking(self): self.is_clicking = True self.click_trigger_event.set() # UI 更新 self.interval_entry.config(state="disabled") self.status_frame.config(bg="#dcfce7") self.status_label.config( text="狀態:連點中(按 F10 停止)", bg="#dcfce7", fg="#15803d" ) self.toggle_btn.config( text="⏹ 停止連點 (F10)", bg="#dc2626", activebackground="#b91c1c" ) def _stop_clicking(self): self.is_clicking = False self.click_trigger_event.clear() # UI 更新 self.interval_entry.config(state="normal") self.status_frame.config(bg="#f1f5f9") self.status_label.config( text="狀態:待命中(按 F10 啟動)", bg="#f1f5f9", fg="#475569" ) self.toggle_btn.config( text="▶ 啟動連點 (F10)", bg="#2563eb", activebackground="#1d4ed8" ) def _on_key_release(self, key): """全域鍵盤釋放回呼,避免按住 F10 時連續觸發""" if key == keyboard.Key.f10: # 確保在 Tkinter 主執行緒更新 GUI 與狀態 self.root.after(0, self.toggle_clicking) def _click_worker(self): """背景守護線程:執行滑鼠左鍵點擊循環""" while not self.stop_app_event.is_set(): if self.is_clicking: try: self.mouse.click(Button.left) except Exception: pass time.sleep(self.interval_sec) else: # 待命中,等待喚醒事件或定時釋放 CPU self.click_trigger_event.wait(timeout=0.05) def _on_closing(self): """安全關閉程式並釋放所有執行緒與監聽器""" self.is_clicking = False self.click_trigger_event.clear() self.stop_app_event.set() try: if self.listener and self.listener.is_alive(): self.listener.stop() except Exception: pass self.root.destroy() sys.exit(0) def main(): root = tk.Tk() app = AutoClickerApp(root) root.mainloop() if __name__ == "__main__": main()