返回列表 发布新帖
查看: 130|回复: 0

豆包新出炉的四窗口鼠标同步点击器适合网游四开

4658

主题

0

回帖

1万

积分

论坛元老

积分
15130
发表于 2026-9-27 00:38:57 | 查看全部 |阅读模式
小新活动线报实时推送群:594617924复制群号 (进群第一时间获取活动线报推送)
鞋服捡漏群:327839651复制群号

  
  1. [code]
  2. #!/usr/bin/env python3
  3. # -*- coding: utf-8 -*-
  4. “““
  5. 多窗口同步点击模拟器
  6. 功能:在主窗口点击时,同时向多个窗口后台发送点击,不抢焦点
  7. 版本:v1.0
  8. “““
  9. import tkinter as tk
  10. from tkinter import ttk, messagebox, scrolledtext
  11. import threading
  12. import time
  13. import sys
  14. # 尝试导入Windows相关库
  15. try:
  16.     import win32gui
  17.     import win32con
  18.     import win32api
  19.     HAS_WIN32 = True
  20. except ImportError:
  21.     HAS_WIN32 = False
  22. try:
  23.     from pynput import mouse
  24.     HAS_PYNPUT = True
  25. except ImportError:
  26.     HAS_PYNPUT = False
  27. class ClickSimulatorApp:
  28.     def __init__(self, root):
  29.         self.root = root
  30.         self.root.title(“多窗口同步点击模拟器 v1.0“)
  31.         self.root.geometry(“750x650“)
  32.         self.root.resizable(False, False)
  33.          
  34.         # 运行状态
  35.         self.running = False
  36.         self.mouse_listener = None
  37.         self.is_simulating = False
  38.          
  39.         # 窗口配置:[(hwnd, 标题, dx, dy, 启用), ...]
  40.         self.window_configs = []
  41.          
  42.         # 初始化UI
  43.         self._setup_ui()
  44.         self._check_dependencies()
  45.          
  46.         # 刷新窗口列表
  47.         self.refresh_windows()
  48.      
  49.     def _check_dependencies(self):
  50.         “““检查依赖是否安装“““
  51.         missing = []
  52.         if not HAS_WIN32:
  53.             missing.append(“pywin32“)
  54.         if not HAS_PYNPUT:
  55.             missing.append(“pynput“)
  56.          
  57.         if missing:
  58.             self.log(f“⚠ 缺少依赖库: {‘, ‘.join(missing)}“)
  59.             self.log(f“  请运行: pip install {‘ ‘.join(missing)}“)
  60.             messagebox.showwarning(
  61.                 “缺少依赖“,
  62.                 f“缺少以下依赖库:\n{‘, ‘.join(missing)}\n\n请运行:\npip install {‘ ‘.join(missing)}“
  63.             )
  64.      
  65.     def _setup_ui(self):
  66.         “““构建界面“““
  67.         # 主框架
  68.         main_frame = ttk.Frame(self.root, padding=“10“)
  69.         main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  70.          
  71.         # ===== 标题 =====
  72.         title_label = ttk.Label(
  73.             main_frame,
  74.             text=“多窗口同步点击模拟器“,
  75.             font=(“微软雅黑“, 16, “bold“)
  76.         )
  77.         title_label.grid(row=0, column=0, columnspan=4, pady=(0, 10))
  78.          
  79.         # ===== 窗口选择区 =====
  80.         window_frame = ttk.LabelFrame(main_frame, text=“窗口配置“, padding=“10“)
  81.         window_frame.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
  82.          
  83.         # 表头
  84.         headers = [“启用“, “窗口标题“, “偏移X“, “偏移Y“]
  85.         col_widths = [60, 300, 80, 80]
  86.          
  87.         for i, (header, width) in enumerate(zip(headers, col_widths)):
  88.             ttk.Label(window_frame, text=header, font=(“微软雅黑“, 9, “bold“)).grid(
  89.                 row=0, column=i, padx=5, pady=(0, 5)
  90.             )
  91.          
  92.         # 创建4行窗口配置
  93.         self.window_vars = []  # 启用复选框变量
  94.         self.window_hwnd_vars = []  # 窗口选择下拉框
  95.         self.offset_x_vars = []  # X偏移
  96.         self.offset_y_vars = []  # Y偏移
  97.          
  98.         self.all_windows = []  # 所有窗口列表 [(hwnd, title), ...]
  99.          
  100.         for row in range(4):
  101.             # 启用复选框
  102.             var_enabled = tk.BooleanVar(value=True if row == 0 else False)
  103.             self.window_vars.append(var_enabled)
  104.             ttk.Checkbutton(window_frame, variable=var_enabled).grid(
  105.                 row=row+1, column=0, padx=5, pady=2
  106.             )
  107.             
  108.             # 窗口选择下拉框
  109.             var_hwnd = tk.StringVar()
  110.             self.window_hwnd_vars.append(var_hwnd)
  111.             combo = ttk.Combobox(
  112.                 window_frame,
  113.                 textvariable=var_hwnd,
  114.                 width=40,
  115.                 state=“readonly“
  116.             )
  117.             combo.grid(row=row+1, column=1, padx=5, pady=2)
  118.             
  119.             # X偏移
  120.             var_x = tk.StringVar(value=“0“ if row == 0 else str(100 if row in [1, 3] else 0))
  121.             self.offset_x_vars.append(var_x)
  122.             ttk.Entry(window_frame, textvariable=var_x, width=8).grid(
  123.                 row=row+1, column=2, padx=5, pady=2
  124.             )
  125.             
  126.             # Y偏移
  127.             var_y = tk.StringVar(value=“0“ if row in [0, 1] else “100“)
  128.             self.offset_y_vars.append(var_y)
  129.             ttk.Entry(window_frame, textvariable=var_y, width=8).grid(
  130.                 row=row+1, column=3, padx=5, pady=2
  131.             )
  132.          
  133.         # 刷新窗口按钮
  134.         refresh_btn = ttk.Button(
  135.             window_frame,
  136.             text=“刷新窗口列表“,
  137.             command=self.refresh_windows
  138.         )
  139.         refresh_btn.grid(row=5, column=0, columnspan=4, pady=(8, 0))
  140.          
  141.         # ===== 控制区 =====
  142.         control_frame = ttk.Frame(main_frame)
  143.         control_frame.grid(row=2, column=0, columnspan=4, pady=(0, 10))
  144.          
  145.         self.start_btn = ttk.Button(
  146.             control_frame,
  147.             text=“▶ 启动监听“,
  148.             command=self.start_listening,
  149.             width=15
  150.         )
  151.         self.start_btn.grid(row=0, column=0, padx=5)
  152.          
  153.         self.stop_btn = ttk.Button(
  154.             control_frame,
  155.             text=“■ 停止监听“,
  156.             command=self.stop_listening,
  157.             state=“disabled“,
  158.             width=15
  159.         )
  160.         self.stop_btn.grid(row=0, column=1, padx=5)
  161.          
  162.         self.status_label = ttk.Label(
  163.             control_frame,
  164.             text=“● 未启动“,
  165.             foreground=“red“,
  166.             font=(“微软雅黑“, 10, “bold“)
  167.         )
  168.         self.status_label.grid(row=0, column=2, padx=20)
  169.          
  170.         # ===== 说明区 =====
  171.         help_frame = ttk.LabelFrame(main_frame, text=“使用说明“, padding=“10“)
  172.         help_frame.grid(row=3, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=(0, 10))
  173.          
  174.         help_text = (
  175.             “1. 点击「刷新窗口列表」获取当前所有打开的窗口\n“
  176.             “2. 在下拉框中选择要同步点击的窗口,设置X/Y偏移量\n“
  177.             “3. 勾选「启用」来决定该窗口是否参与同步\n“
  178.             “4. 点击「启动监听」后,在第一个勾选的窗口中点击即可同步到其他窗口\n“
  179.             “5. 偏移量表示:相对点击位置的横向/纵向偏移像素“
  180.         )
  181.         ttk.Label(help_frame, text=help_text, justify=tk.LEFT, font=(“微软雅黑“, 9)).grid(
  182.             row=0, column=0, sticky=tk.W
  183.         )
  184.          
  185.         # ===== 日志区 =====
  186.         log_frame = ttk.LabelFrame(main_frame, text=“运行日志“, padding=“10“)
  187.         log_frame.grid(row=4, column=0, columnspan=4, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 5))
  188.          
  189.         self.log_text = scrolledtext.ScrolledText(
  190.             log_frame,
  191.             width=85,
  192.             height=10,
  193.             font=(“Consolas“, 9),
  194.             state=“disabled“
  195.         )
  196.         self.log_text.grid(row=0, column=0)
  197.          
  198.         # 底部说明
  199.         ttk.Label(
  200.             main_frame,
  201.             text=“提示:本工具通过 PostMessage 后台发送点击,不会激活其他窗口“,
  202.             foreground=“gray“,
  203.             font=(“微软雅黑“, 8)
  204.         ).grid(row=5, column=0, columnspan=4)
  205.      
  206.     def log(self, message):
  207.         “““添加日志“““
  208.         timestamp = time.strftime(“%H:%M:%S“)
  209.         self.log_text.configure(state=“normal“)
  210.         self.log_text.insert(tk.END, f“[{timestamp}] {message}\n“)
  211.         self.log_text.see(tk.END)
  212.         self.log_text.configure(state=“disabled“)
  213.      
  214.     def refresh_windows(self):
  215.         “““刷新窗口列表“““
  216.         if not HAS_WIN32:
  217.             self.log(“✗ 未安装 pywin32,无法获取窗口列表“)
  218.             return
  219.          
  220.         self.all_windows = []
  221.          
  222.         def callback(hwnd, _):
  223.             if win32gui.IsWindowVisible(hwnd):
  224.                 title = win32gui.GetWindowText(hwnd)
  225.                 if title.strip() and len(title.strip()) > 1:
  226.                     self.all_windows.append((hwnd, title.strip()))
  227.          
  228.         win32gui.EnumWindows(callback, None)
  229.          
  230.         # 更新下拉框选项
  231.         window_titles = [f“[{hwnd}] {title[:40]}“ for hwnd, title in self.all_windows]
  232.          
  233.         for combo_var in self.window_hwnd_vars:
  234.             # 找到对应的下拉框控件
  235.             combo_index = self.window_hwnd_vars.index(combo_var)
  236.             # 遍历找到对应的combobox
  237.             for child in self.root.winfo_children():
  238.                 self._find_and_update_combo(child, combo_var, window_titles)
  239.          
  240.         self.log(f“✓ 刷新窗口列表,共找到 {len(self.all_windows)} 个可见窗口“)
  241.      
  242.     def _find_and_update_combo(self, widget, var, values):
  243.         “““递归查找并更新下拉框“““
  244.         if isinstance(widget, ttk.Combobox) and str(widget.cget(“textvariable“)) == str(var):
  245.             widget[‘values‘] = values
  246.             return True
  247.         for child in widget.winfo_children():
  248.             if self._find_and_update_combo(child, var, values):
  249.                 return True
  250.         return False
  251.      
  252.     def _get_selected_hwnd(self, index):
  253.         “““获取选中的窗口句柄“““
  254.         selection = self.window_hwnd_vars[index].get()
  255.         if not selection:
  256.             return None
  257.          
  258.         # 从 “[hwnd] title“ 格式中提取 hwnd
  259.         try:
  260.             hwnd_str = selection.split(“]“)[0].strip(“[“)
  261.             return int(hwnd_str)
  262.         except:
  263.             return None
  264.      
  265.     def start_listening(self):
  266.         “““启动鼠标监听“““
  267.         if not HAS_PYNPUT:
  268.             messagebox.showerror(“错误“, “未安装 pynput,请运行: pip install pynput“)
  269.             return
  270.          
  271.         if not HAS_WIN32:
  272.             messagebox.showerror(“错误“, “未安装 pywin32,请运行: pip install pywin32“)
  273.             return
  274.          
  275.         # 检查配置
  276.         enabled_count = sum(1 for v in self.window_vars if v.get())
  277.         if enabled_count == 0:
  278.             messagebox.showwarning(“提示“, “请至少启用一个窗口“)
  279.             return
  280.          
  281.         # 检查是否都选择了窗口
  282.         for i, var in enumerate(self.window_vars):
  283.             if var.get() and not self._get_selected_hwnd(i):
  284.                 messagebox.showwarning(“提示“, f“第 {i+1} 行已启用但未选择窗口“)
  285.                 return
  286.          
  287.         self.running = True
  288.         self.is_simulating = False
  289.          
  290.         # 更新按钮状态
  291.         self.start_btn.configure(state=“disabled“)
  292.         self.stop_btn.configure(state=“normal“)
  293.         self.status_label.configure(text=“● 监听中“, foreground=“green“)
  294.          
  295.         # 启动监听线程
  296.         self.mouse_listener = mouse.Listener(on_click=self._on_mouse_click)
  297.         self.mouse_listener.start()
  298.          
  299.         self.log(“=“ * 50)
  300.         self.log(“✓ 鼠标监听已启动“)
  301.          
  302.         # 显示配置信息
  303.         for i, var in enumerate(self.window_vars):
  304.             if var.get():
  305.                 hwnd = self._get_selected_hwnd(i)
  306.                 title = win32gui.GetWindowText(hwnd)[:30] if hwnd else “未知“
  307.                 dx = self.offset_x_vars[i].get()
  308.                 dy = self.offset_y_vars[i].get()
  309.                 self.log(f“  窗口{i+1}: [{title}] 偏移({dx}, {dy})“)
  310.          
  311.         self.log(“等待鼠标点击...“)
  312.      
  313.     def stop_listening(self):
  314.         “““停止鼠标监听“““
  315.         self.running = False
  316.          
  317.         if self.mouse_listener:
  318.             self.mouse_listener.stop()
  319.             self.mouse_listener = None
  320.          
  321.         # 更新按钮状态
  322.         self.start_btn.configure(state=“normal“)
  323.         self.stop_btn.configure(state=“disabled“)
  324.         self.status_label.configure(text=“● 未启动“, foreground=“red“)
  325.          
  326.         self.log(“✓ 鼠标监听已停止“)
  327.      
  328.     def _on_mouse_click(self, x, y, button, pressed):
  329.         “““鼠标点击回调“““
  330.         # 跳过模拟事件
  331.         if self.is_simulating:
  332.             return
  333.          
  334.         # 只响应左键按下
  335.         if not pressed or button != mouse.Button.left:
  336.             return
  337.          
  338.         # 在后台线程执行
  339.         threading.Thread(
  340.             target=self._do_sync_click,
  341.             args=(x, y),
  342.             daemon=True
  343.         ).start()
  344.      
  345.     def _do_sync_click(self, real_x, real_y):
  346.         “““执行同步点击“““
  347.         self.is_simulating = True
  348.          
  349.         try:
  350.             self.log(f“\n[点击] 屏幕坐标: ({real_x}, {real_y})“)
  351.             
  352.             for i, var in enumerate(self.window_vars):
  353.                 if not var.get():
  354.                     continue
  355.                  
  356.                 hwnd = self._get_selected_hwnd(i)
  357.                 if not hwnd:
  358.                     continue
  359.                  
  360.                 try:
  361.                     dx = int(self.offset_x_vars[i].get())
  362.                     dy = int(self.offset_y_vars[i].get())
  363.                 except ValueError:
  364.                     self.log(f“  ✗ 窗口{i+1} 偏移量格式错误“)
  365.                     continue
  366.                  
  367.                 click_x = real_x + dx
  368.                 click_y = real_y + dy
  369.                  
  370.                 # 发送后台点击
  371.                 self._post_click(hwnd, click_x, click_y, i+1)
  372.                  
  373.                 time.sleep(0.03)
  374.             
  375.             self.log(“  ✓ 同步点击完成“)
  376.          
  377.         finally:
  378.             time.sleep(0.1)
  379.             self.is_simulating = False
  380.      
  381.     def _post_click(self, hwnd, screen_x, screen_y, window_index):
  382.         “““向窗口后台发送点击“““
  383.         try:
  384.             # 转换为客户区坐标
  385.             rect = win32gui.GetWindowRect(hwnd)
  386.             client_x = screen_x - rect[0]
  387.             client_y = screen_y - rect[1]
  388.             
  389.             # 打包 lParam
  390.             lparam = win32api.MAKELONG(client_x, client_y)
  391.             
  392.             # 发送按下消息
  393.             win32gui.PostMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lparam)
  394.             time.sleep(0.02)
  395.             # 发送抬起消息
  396.             win32gui.PostMessage(hwnd, win32con.WM_LBUTTONUP, 0, lparam)
  397.             
  398.             title = win32gui.GetWindowText(hwnd)[:20]
  399.             self.log(f“  → 窗口{window_index}: [{title}] 点击({client_x}, {client_y})“)
  400.             
  401.         except Exception as e:
  402.             self.log(f“  ✗ 窗口{window_index} 发送失败: {e}“)
  403. def main():
  404.     root = tk.Tk()
  405.     app = ClickSimulatorApp(root)
  406.      
  407.     # 窗口关闭时清理
  408.     def on_closing():
  409.         app.stop_listening()
  410.         root.destroy()
  411.      
  412.     root.protocol(“WM_DELETE_WINDOW“, on_closing)
  413.     root.mainloop()
  414. if __name__ == “__main__“:
  415.     main()
  416. #(注:内容由AI生成)
  417. [i]复制代码[/i]
复制代码
[/code]
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2001-2026 52线报网 版权所有 All Rights Reserved. |网站地图 闽ICP备19006036号-4
关灯 在本版发帖
扫一扫添加微信客服
返回顶部
快速回复 返回顶部 返回列表