控制vm虚拟机.py 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306
  1. import tkinter as tk
  2. from tkinter import ttk, messagebox
  3. import time
  4. import threading
  5. import random
  6. import win32gui
  7. import win32con
  8. import win32api
  9. import ctypes
  10. from ctypes import wintypes
  11. import platform
  12. import os
  13. import string
  14. import sys
  15. import json
  16. import websocket
  17. # ========== 幽灵键鼠加载 ==========
  18. def load_ghost_key_mouse():
  19. """加载幽灵键鼠DLL"""
  20. script_dir = os.path.dirname(os.path.abspath(__file__))
  21. # 根据系统架构选择DLL
  22. if platform.architecture()[0] == "64bit":
  23. dll_path = os.path.join(script_dir, "gbild64.dll")
  24. else:
  25. dll_path = os.path.join(script_dir, "gbild32.dll")
  26. if not os.path.exists(dll_path):
  27. # 如果当前目录没有,尝试从exe所在目录加载
  28. if getattr(sys, 'frozen', False):
  29. script_dir = os.path.dirname(sys.executable)
  30. if platform.architecture()[0] == "64bit":
  31. dll_path = os.path.join(script_dir, "gbild64.dll")
  32. else:
  33. dll_path = os.path.join(script_dir, "gbild32.dll")
  34. try:
  35. dll = ctypes.windll.LoadLibrary(dll_path)
  36. print(f"幽灵键鼠DLL加载成功: {dll_path}")
  37. return dll
  38. except Exception as e:
  39. print(f"幽灵键鼠DLL加载失败: {e}")
  40. return None
  41. # 加载DLL
  42. ghost_dll = load_ghost_key_mouse()
  43. if ghost_dll:
  44. # 设置接口返回值类型
  45. ghost_dll.getmodel.restype = ctypes.c_char_p
  46. ghost_dll.getserialnumber.restype = ctypes.c_char_p
  47. ghost_dll.getproductiondate.restype = ctypes.c_char_p
  48. ghost_dll.getfirmwareversion.restype = ctypes.c_char_p
  49. ghost_dll.getclientscreenresolution.restype = ctypes.c_char_p
  50. ghost_dll.readstring.restype = ctypes.c_char_p
  51. ghost_dll.encryptstring.restype = ctypes.c_char_p
  52. ghost_dll.decryptstring.restype = ctypes.c_char_p
  53. ghost_dll.getproductname.restype = ctypes.c_char_p
  54. ghost_dll.sdktype.restype = ctypes.c_char_p
  55. ghost_dll.sdkversion.restype = ctypes.c_char_p
  56. # ================ 设备操作 ================
  57. def opendevice(index=0):
  58. """打开设备(根据设备序号)"""
  59. return ghost_dll.opendevice(index)
  60. def closedevice():
  61. """关闭设备"""
  62. return ghost_dll.closedevice()
  63. def isconnected():
  64. """检查设备是否连接"""
  65. return ghost_dll.isconnected()
  66. # ================ 鼠标操作 ================
  67. def movemouseto(x, y):
  68. """移动鼠标到指定坐标"""
  69. return ghost_dll.movemouseto(x, y)
  70. def pressandreleasemousebutton(mbtn):
  71. """按下并释放鼠标键 (1:左键, 2:右键, 3:中键)"""
  72. return ghost_dll.pressandreleasemousebutton(mbtn)
  73. def pressmousebutton(mbtn):
  74. """按下鼠标键"""
  75. return ghost_dll.pressmousebutton(mbtn)
  76. def releasemousebutton(mbtn):
  77. """释放鼠标键"""
  78. return ghost_dll.releasemousebutton(mbtn)
  79. def getmousex():
  80. """获取鼠标当前X坐标"""
  81. return ghost_dll.getmousex()
  82. def getmousey():
  83. """获取鼠标当前Y坐标"""
  84. return ghost_dll.getmousey()
  85. def setmousemovementdelay(maxd, mind):
  86. """设置鼠标移动延时"""
  87. return ghost_dll.setmousemovementdelay(maxd, mind)
  88. def setmousemovementspeed(speedvalue):
  89. """设置鼠标移动速度"""
  90. return ghost_dll.setmousemovementspeed(speedvalue)
  91. # ================ 键盘操作 ================
  92. def presskeybyname(key_name):
  93. """按下键(通过键名)"""
  94. return ghost_dll.presskeybyname(key_name)
  95. def releasekeybyname(key_name):
  96. """释放键(通过键名)"""
  97. return ghost_dll.releasekeybyname(key_name)
  98. def combinationkey(key_sequence):
  99. """组合键(如 b"ctrl+c")"""
  100. return ghost_dll.combinationkey(key_sequence)
  101. def pressandreleasekeybyname(key_name):
  102. """按下并释放键"""
  103. return ghost_dll.pressandreleasekeybyname(key_name)
  104. def presskeybycode(key_code):
  105. """按下键(通过键码)"""
  106. return ghost_dll.presskeybycode(key_code)
  107. def releasekeybycode(key_code):
  108. """释放键(通过键码)"""
  109. return ghost_dll.releasekeybycode(key_code)
  110. def pressandreleasekeybycode(key_code):
  111. """按下并释放键(通过键码)"""
  112. return ghost_dll.pressandreleasekeybycode(key_code)
  113. def clearkeys():
  114. """清除所有按下的键"""
  115. return ghost_dll.clearkeys()
  116. # 尝试打开设备
  117. device_id = opendevice(0)
  118. if device_id == 0:
  119. print("幽灵键鼠设备连接失败!")
  120. ghost_available = False
  121. else:
  122. print("幽灵键鼠设备连接成功")
  123. ghost_available = True
  124. else:
  125. ghost_available = False
  126. print("幽灵键鼠不可用,将使用系统API")
  127. class WebSocketClient:
  128. """WebSocket客户端"""
  129. def __init__(self, channel_type, message_callback, log_callback):
  130. self.channel_type = channel_type
  131. self.message_callback = message_callback
  132. self.log_callback = log_callback
  133. self.ws = None
  134. self.is_connected = False
  135. self.stop_flag = False
  136. self.thread = None
  137. self.user_id = None
  138. def connect(self):
  139. """连接WebSocket服务器"""
  140. def on_message(ws, message):
  141. try:
  142. data = json.loads(message)
  143. msg_type = data.get("type")
  144. # 处理用户信息(登录成功)
  145. if msg_type == "userInfo":
  146. self.is_connected = True
  147. self.user_id = data.get("id")
  148. if self.log_callback:
  149. self.log_callback(f"WS登录成功,用户ID: {self.user_id}")
  150. return
  151. # 处理其他消息
  152. if self.message_callback:
  153. self.message_callback(data)
  154. except Exception as e:
  155. if self.log_callback:
  156. self.log_callback(f"WebSocket消息解析失败: {e}")
  157. def on_error(ws, error):
  158. if self.log_callback:
  159. self.log_callback(f"WebSocket错误: {error}")
  160. def on_close(ws, close_status_code, close_msg):
  161. self.is_connected = False
  162. if self.log_callback:
  163. self.log_callback("WebSocket连接已关闭")
  164. # 自动重连
  165. if not self.stop_flag:
  166. if self.log_callback:
  167. self.log_callback("3秒后尝试重新连接...")
  168. time.sleep(3)
  169. self.connect()
  170. def on_open(ws):
  171. if self.log_callback:
  172. self.log_callback(f"WebSocket连接成功,信道: {self.channel_type}")
  173. # 发送登录信息
  174. login_msg = {
  175. "route": "login",
  176. "type": self.channel_type,
  177. "admin": True
  178. }
  179. ws.send(json.dumps(login_msg))
  180. try:
  181. # 连接到本地服务器
  182. self.ws = websocket.WebSocketApp(
  183. 'wss://ws.lamp.run', # 你的服务器地址
  184. on_open=on_open,
  185. on_message=on_message,
  186. on_error=on_error,
  187. on_close=on_close
  188. )
  189. # 在新线程中运行
  190. self.thread = threading.Thread(target=self.ws.run_forever, daemon=True)
  191. self.thread.start()
  192. except Exception as e:
  193. if self.log_callback:
  194. self.log_callback(f"WebSocket连接失败: {e}")
  195. def disconnect(self):
  196. """断开WebSocket连接"""
  197. self.stop_flag = True
  198. if self.ws:
  199. self.ws.close()
  200. self.is_connected = False
  201. def send_message(self, route, value):
  202. """发送消息 - 群发模式(不指定id)"""
  203. if not self.ws or not self.is_connected:
  204. if self.log_callback:
  205. self.log_callback(f"⚠️ WS未连接,无法发送消息")
  206. return False
  207. if not self.user_id:
  208. if self.log_callback:
  209. self.log_callback(f"⚠️ 未获取到用户ID,无法发送消息")
  210. return False
  211. try:
  212. # 构建消息格式 - 不包含id字段,实现群发
  213. msg = {
  214. "route": route,
  215. "type": self.channel_type,
  216. "userID": self.user_id,
  217. "value": value
  218. }
  219. # ===== 打印原始消息 =====
  220. msg_json = json.dumps(msg)
  221. if self.log_callback:
  222. self.log_callback(f"📤 发送原始消息: {msg_json}")
  223. self.ws.send(msg_json)
  224. if self.log_callback:
  225. self.log_callback(f"📤 群发WS消息: route={route}, value={value}")
  226. return True
  227. except Exception as e:
  228. if self.log_callback:
  229. self.log_callback(f"发送消息失败: {e}")
  230. return False
  231. def generate_random_code(length=8):
  232. """生成指定长度的随机数字字母组合"""
  233. # 数字 + 大小写字母
  234. chars = string.ascii_letters + string.digits
  235. return ''.join(random.choice(chars) for _ in range(length))
  236. class VmControlGUI:
  237. def __init__(self, root):
  238. self.root = root
  239. self.root.title("虚拟机按键循环工具 - 双模式")
  240. self.root.geometry("850x750")
  241. self.root.resizable(True, True)
  242. # ===== 脚本选择 =====
  243. self.script_mode = tk.StringVar(value="A")
  244. # ===== 脚本A变量 =====
  245. self.vm_window_title = tk.StringVar()
  246. self.vm_windows = []
  247. self.vm_hwnd = None
  248. self.window_rect = None
  249. # ===== 脚本A运行状态 =====
  250. self.is_running = False
  251. self.stop_flag = False
  252. self.thread = None
  253. # ===== 脚本A参数 =====
  254. self.key_interval_min = tk.StringVar(value="300")
  255. self.key_interval_max = tk.StringVar(value="800")
  256. self.round_interval_min = tk.StringVar(value="3")
  257. self.round_interval_max = tk.StringVar(value="5")
  258. self.pause_interval_min = tk.StringVar(value="30")
  259. self.pause_interval_max = tk.StringVar(value="50")
  260. self.pause_duration = tk.StringVar(value="60")
  261. # ===== 脚本B变量 =====
  262. self.channel_type = tk.StringVar(value=generate_random_code())
  263. self.ws_client = None
  264. self.is_b_running = False
  265. self.b_stop_flag = False
  266. self.b_thread = None
  267. # 脚本B参数
  268. self.b_key_interval_min = tk.StringVar(value="30")
  269. self.b_key_interval_max = tk.StringVar(value="50")
  270. self.b_stop_delay_min = tk.StringVar(value="10")
  271. self.b_stop_delay_max = tk.StringVar(value="30")
  272. self.b_wait_after_stop_min = tk.StringVar(value="200")
  273. self.b_wait_after_stop_max = tk.StringVar(value="500")
  274. self.b_pause_duration = tk.StringVar(value="20")
  275. # ===== 激活坐标 =====
  276. self.ACTIVATE_X = 300
  277. self.ACTIVATE_Y = 300
  278. # ===== 鼠标移动延迟 =====
  279. self.MOUSE_MOVE_DELAY = 0.1
  280. # ===== 幽灵键鼠状态 =====
  281. self.ghost_available = ghost_available
  282. if self.ghost_available:
  283. setmousemovementspeed(5)
  284. setmousemovementdelay(10, 5)
  285. self.create_widgets()
  286. self.scan_vm_windows()
  287. # 显示幽灵键鼠状态
  288. if self.ghost_available:
  289. self.log("✅ 幽灵键鼠已连接")
  290. else:
  291. self.log("⚠️ 幽灵键鼠未连接,使用系统API")
  292. def create_widgets(self):
  293. main_frame = ttk.Frame(self.root, padding="10")
  294. main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  295. # ===== 脚本模式选择 =====
  296. mode_frame = ttk.LabelFrame(main_frame, text="脚本模式", padding="10")
  297. mode_frame.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=5)
  298. ttk.Radiobutton(mode_frame, text="脚本A - 主控 (按键循环)", variable=self.script_mode,
  299. value="A", command=self.on_mode_change).pack(side=tk.LEFT, padx=10)
  300. ttk.Radiobutton(mode_frame, text="脚本B - 分机 (WS监听)", variable=self.script_mode,
  301. value="B", command=self.on_mode_change).pack(side=tk.LEFT, padx=10)
  302. # 信道输入框
  303. ttk.Label(mode_frame, text="信道:").pack(side=tk.LEFT, padx=(20, 5))
  304. ttk.Entry(mode_frame, textvariable=self.channel_type, width=15).pack(side=tk.LEFT, padx=5)
  305. ttk.Button(mode_frame, text="连接WS", command=self.connect_websocket, width=8).pack(side=tk.LEFT, padx=5)
  306. ttk.Button(mode_frame, text="断开WS", command=self.disconnect_websocket, width=8).pack(side=tk.LEFT, padx=5)
  307. # WS状态
  308. self.ws_status_label = ttk.Label(mode_frame, text="WS: 未连接", foreground="gray")
  309. self.ws_status_label.pack(side=tk.LEFT, padx=10)
  310. # 发送指令按钮(群发)
  311. ttk.Button(mode_frame, text="📤 群发开始", command=self.send_start_command, width=10).pack(side=tk.LEFT, padx=5)
  312. ttk.Button(mode_frame, text="📤 群发停止", command=self.send_stop_command, width=10).pack(side=tk.LEFT, padx=5)
  313. # ===== 脚本A界面 =====
  314. self.frame_a = ttk.Frame(main_frame)
  315. self.frame_a.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E, tk.N, tk.S))
  316. # 窗口搜索
  317. ttk.Button(self.frame_a, text="🔍 搜索 VMware 窗口", command=self.scan_vm_windows, width=20).grid(
  318. row=0, column=0, sticky=tk.W, pady=5
  319. )
  320. ttk.Label(self.frame_a, text="选择虚拟机窗口:").grid(row=0, column=1, sticky=tk.W, pady=5, padx=10)
  321. self.vm_combo = ttk.Combobox(self.frame_a, textvariable=self.vm_window_title, width=45)
  322. self.vm_combo.grid(row=0, column=2, sticky=(tk.W, tk.E), pady=5, padx=5)
  323. self.window_info_label = ttk.Label(self.frame_a, text="窗口状态: 未选择", foreground="gray")
  324. self.window_info_label.grid(row=1, column=0, columnspan=4, sticky=tk.W, pady=5)
  325. # 脚本A参数
  326. param_frame_a = ttk.LabelFrame(self.frame_a, text="脚本A参数设置", padding="10")
  327. param_frame_a.grid(row=2, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=10)
  328. ttk.Label(param_frame_a, text="按键间隔 (毫秒):").grid(row=0, column=0, sticky=tk.W, pady=3)
  329. ttk.Label(param_frame_a, text="最小:").grid(row=0, column=1, sticky=tk.W, pady=3, padx=(10, 0))
  330. ttk.Entry(param_frame_a, textvariable=self.key_interval_min, width=8).grid(row=0, column=1, sticky=tk.W, pady=3, padx=(30, 0))
  331. ttk.Label(param_frame_a, text="最大:").grid(row=0, column=2, sticky=tk.W, pady=3, padx=(10, 0))
  332. ttk.Entry(param_frame_a, textvariable=self.key_interval_max, width=8).grid(row=0, column=2, sticky=tk.W, pady=3, padx=(30, 0))
  333. ttk.Label(param_frame_a, text="轮次间隔 (秒):").grid(row=1, column=0, sticky=tk.W, pady=3)
  334. ttk.Label(param_frame_a, text="最小:").grid(row=1, column=1, sticky=tk.W, pady=3, padx=(10, 0))
  335. ttk.Entry(param_frame_a, textvariable=self.round_interval_min, width=8).grid(row=1, column=1, sticky=tk.W, pady=3, padx=(30, 0))
  336. ttk.Label(param_frame_a, text="最大:").grid(row=1, column=2, sticky=tk.W, pady=3, padx=(10, 0))
  337. ttk.Entry(param_frame_a, textvariable=self.round_interval_max, width=8).grid(row=1, column=2, sticky=tk.W, pady=3, padx=(30, 0))
  338. ttk.Label(param_frame_a, text="暂停间隔 (分钟):").grid(row=2, column=0, sticky=tk.W, pady=3)
  339. ttk.Label(param_frame_a, text="最小:").grid(row=2, column=1, sticky=tk.W, pady=3, padx=(10, 0))
  340. ttk.Entry(param_frame_a, textvariable=self.pause_interval_min, width=8).grid(row=2, column=1, sticky=tk.W, pady=3, padx=(30, 0))
  341. ttk.Label(param_frame_a, text="最大:").grid(row=2, column=2, sticky=tk.W, pady=3, padx=(10, 0))
  342. ttk.Entry(param_frame_a, textvariable=self.pause_interval_max, width=8).grid(row=2, column=2, sticky=tk.W, pady=3, padx=(30, 0))
  343. ttk.Label(param_frame_a, text="暂停时长(秒):").grid(row=2, column=3, sticky=tk.W, pady=3, padx=(10, 0))
  344. ttk.Entry(param_frame_a, textvariable=self.pause_duration, width=8).grid(row=2, column=3, sticky=tk.W, pady=3, padx=(5, 0))
  345. # 脚本A按钮
  346. btn_frame_a = ttk.Frame(self.frame_a)
  347. btn_frame_a.grid(row=3, column=0, columnspan=4, pady=10)
  348. self.start_btn = ttk.Button(btn_frame_a, text="▶ 开始循环", command=self.start_loop, width=15)
  349. self.start_btn.pack(side=tk.LEFT, padx=5)
  350. self.stop_btn = ttk.Button(btn_frame_a, text="⏹ 停止循环", command=self.stop_loop, width=15, state=tk.DISABLED)
  351. self.stop_btn.pack(side=tk.LEFT, padx=5)
  352. ttk.Button(btn_frame_a, text="测试点击", command=self.test_click, width=15).pack(side=tk.LEFT, padx=5)
  353. ttk.Button(btn_frame_a, text="测试按键", command=self.test_keys, width=15).pack(side=tk.LEFT, padx=5)
  354. # ===== 脚本B界面 =====
  355. self.frame_b = ttk.Frame(main_frame)
  356. self.frame_b.grid(row=1, column=0, columnspan=4, sticky=(tk.W, tk.E, tk.N, tk.S))
  357. self.frame_b.grid_remove()
  358. # 脚本B参数
  359. param_frame_b = ttk.LabelFrame(self.frame_b, text="脚本B参数设置", padding="10")
  360. param_frame_b.grid(row=0, column=0, columnspan=4, sticky=(tk.W, tk.E), pady=10)
  361. ttk.Label(param_frame_b, text="按键间隔 (秒):").grid(row=0, column=0, sticky=tk.W, pady=3)
  362. ttk.Label(param_frame_b, text="最小:").grid(row=0, column=1, sticky=tk.W, pady=3, padx=(10, 0))
  363. ttk.Entry(param_frame_b, textvariable=self.b_key_interval_min, width=8).grid(row=0, column=1, sticky=tk.W, pady=3, padx=(30, 0))
  364. ttk.Label(param_frame_b, text="最大:").grid(row=0, column=2, sticky=tk.W, pady=3, padx=(10, 0))
  365. ttk.Entry(param_frame_b, textvariable=self.b_key_interval_max, width=8).grid(row=0, column=2, sticky=tk.W, pady=3, padx=(30, 0))
  366. ttk.Label(param_frame_b, text="停止延迟 (秒):").grid(row=1, column=0, sticky=tk.W, pady=3)
  367. ttk.Label(param_frame_b, text="最小:").grid(row=1, column=1, sticky=tk.W, pady=3, padx=(10, 0))
  368. ttk.Entry(param_frame_b, textvariable=self.b_stop_delay_min, width=8).grid(row=1, column=1, sticky=tk.W, pady=3, padx=(30, 0))
  369. ttk.Label(param_frame_b, text="最大:").grid(row=1, column=2, sticky=tk.W, pady=3, padx=(10, 0))
  370. ttk.Entry(param_frame_b, textvariable=self.b_stop_delay_max, width=8).grid(row=1, column=2, sticky=tk.W, pady=3, padx=(30, 0))
  371. ttk.Label(param_frame_b, text="停止后延迟 (毫秒):").grid(row=2, column=0, sticky=tk.W, pady=3)
  372. ttk.Label(param_frame_b, text="最小:").grid(row=2, column=1, sticky=tk.W, pady=3, padx=(10, 0))
  373. ttk.Entry(param_frame_b, textvariable=self.b_wait_after_stop_min, width=8).grid(row=2, column=1, sticky=tk.W, pady=3, padx=(30, 0))
  374. ttk.Label(param_frame_b, text="最大:").grid(row=2, column=2, sticky=tk.W, pady=3, padx=(10, 0))
  375. ttk.Entry(param_frame_b, textvariable=self.b_wait_after_stop_max, width=8).grid(row=2, column=2, sticky=tk.W, pady=3, padx=(30, 0))
  376. ttk.Label(param_frame_b, text="暂停延续(秒):").grid(row=3, column=0, sticky=tk.W, pady=3)
  377. ttk.Label(param_frame_b, text="最小:").grid(row=3, column=1, sticky=tk.W, pady=3, padx=(10, 0))
  378. ttk.Entry(param_frame_b, textvariable=self.b_pause_duration, width=8).grid(row=3, column=1, sticky=tk.W, pady=3, padx=(30, 0))
  379. # 脚本B状态
  380. self.b_status_label = ttk.Label(self.frame_b, text="脚本B状态: 等待启动信号", foreground="gray")
  381. self.b_status_label.grid(row=1, column=0, columnspan=4, sticky=tk.W, pady=5)
  382. # ===== 公共区域 =====
  383. self.status_label = ttk.Label(main_frame, text="状态: 停止", foreground="gray")
  384. self.status_label.grid(row=2, column=0, columnspan=4, sticky=tk.W, pady=5)
  385. ttk.Label(main_frame, text="执行日志:").grid(row=3, column=0, sticky=tk.W, pady=5)
  386. self.log_text = tk.Text(main_frame, height=14, width=100, font=("Consolas", 9))
  387. self.log_text.grid(row=4, column=0, columnspan=4, pady=5, sticky=(tk.W, tk.E, tk.N, tk.S))
  388. scrollbar = ttk.Scrollbar(main_frame, orient=tk.VERTICAL, command=self.log_text.yview)
  389. scrollbar.grid(row=4, column=4, sticky=(tk.N, tk.S))
  390. self.log_text.config(yscrollcommand=scrollbar.set)
  391. ttk.Button(main_frame, text="清空日志", command=self.clear_log, width=15).grid(row=5, column=0, columnspan=4, pady=5)
  392. main_frame.columnconfigure(2, weight=1)
  393. main_frame.rowconfigure(4, weight=1)
  394. self.root.columnconfigure(0, weight=1)
  395. self.root.rowconfigure(0, weight=1)
  396. def on_mode_change(self):
  397. """切换脚本模式"""
  398. mode = self.script_mode.get()
  399. if mode == "A":
  400. self.frame_a.grid()
  401. self.frame_b.grid_remove()
  402. else:
  403. self.frame_a.grid_remove()
  404. self.frame_b.grid()
  405. def log(self, message):
  406. import datetime
  407. timestamp = datetime.datetime.now().strftime("%H:%M:%S")
  408. self.log_text.insert(tk.END, f"[{timestamp}] {message}\n")
  409. self.log_text.see(tk.END)
  410. self.root.update()
  411. def clear_log(self):
  412. self.log_text.delete(1.0, tk.END)
  413. # ========== WebSocket相关 ==========
  414. def connect_websocket(self):
  415. """连接WebSocket"""
  416. channel = self.channel_type.get().strip()
  417. if not channel:
  418. messagebox.showerror("错误", "请输入信道名称!")
  419. return
  420. if self.ws_client and self.ws_client.is_connected:
  421. self.log("WebSocket已连接")
  422. return
  423. self.ws_client = WebSocketClient(
  424. channel_type=channel,
  425. message_callback=self.on_ws_message,
  426. log_callback=self.log
  427. )
  428. self.ws_client.connect()
  429. self.ws_status_label.config(text="WS: 连接中...", foreground="orange")
  430. def disconnect_websocket(self):
  431. """断开WebSocket"""
  432. if self.ws_client:
  433. self.ws_client.disconnect()
  434. self.ws_client = None
  435. self.ws_status_label.config(text="WS: 已断开", foreground="gray")
  436. self.log("WebSocket已断开")
  437. def send_start_command(self):
  438. """群发开始指令"""
  439. if not self.ws_client or not self.ws_client.is_connected:
  440. self.log("⚠️ WS未连接,无法发送指令")
  441. messagebox.showerror("错误", "请先连接WebSocket!")
  442. return
  443. # 群发 - 不指定id
  444. self.ws_client.send_message("start", "开始执行")
  445. self.log("📤 已群发开始指令")
  446. def send_stop_command(self):
  447. """群发停止指令"""
  448. if not self.ws_client or not self.ws_client.is_connected:
  449. self.log("⚠️ WS未连接,无法发送指令")
  450. messagebox.showerror("错误", "请先连接WebSocket!")
  451. return
  452. # 群发 - 不指定id
  453. self.ws_client.send_message("stop", "停止执行")
  454. self.log("📤 已群发停止指令")
  455. def send_pause_command(self):
  456. """群发暂停指令(让B执行停止序列)"""
  457. if not self.ws_client or not self.ws_client.is_connected:
  458. self.log("⚠️ WS未连接,无法发送指令")
  459. return
  460. self.ws_client.send_message("pause", "暂停执行")
  461. self.log("📤 已群发暂停指令")
  462. def on_ws_message(self, data):
  463. """处理WebSocket消息"""
  464. try:
  465. self.log(f"📨 收到原始消息: {json.dumps(data)}")
  466. msg_type = data.get("type")
  467. value = data.get("value")
  468. user_id = data.get("userID")
  469. self.log(f"📨 解析结果: type={msg_type}, value={value}, from={user_id}")
  470. if msg_type == "start":
  471. self.log(f"📨 收到开始指令")
  472. self.start_script_b()
  473. elif msg_type == "stop":
  474. self.log(f"📨 收到停止指令")
  475. self.stop_script_b()
  476. elif msg_type == "pause":
  477. self.log(f"📨 收到暂停指令")
  478. # 执行停止序列(2次),但不结束脚本B
  479. self.execute_stop_sequence()
  480. elif msg_type == "close":
  481. self.log(f"📨 收到关闭指令,可能是ID冲突")
  482. else:
  483. self.log(f"收到其他消息: {msg_type}")
  484. except Exception as e:
  485. self.log(f"处理WS消息失败: {e}")
  486. def execute_stop_sequence(self):
  487. """执行停止序列(2次),不结束脚本B"""
  488. try:
  489. # 暂停时继续执行的秒数(默认20秒)
  490. pause_duration = int(self.b_pause_duration.get())
  491. # ===== 1. 继续执行20秒(按- 和 2) =====
  492. self.log(f"⏳ 暂停指令,继续执行 {pause_duration} 秒(按- 和 2)")
  493. # 按键间隔(30-50秒)
  494. interval_min = int(self.b_key_interval_min.get())
  495. interval_max = int(self.b_key_interval_max.get())
  496. # 记录上次按2的时间
  497. last_key2_time = time.time()
  498. pause_start_time = time.time()
  499. while time.time() - pause_start_time < pause_duration:
  500. # 按 - (减号键) - 每300-800ms一次
  501. self.log("发送: - (减号) [暂停延续中]")
  502. self.press_key(0xBD, False)
  503. # 减号间隔 300-800ms
  504. key_delay = random.uniform(0.3, 0.8)
  505. time.sleep(key_delay)
  506. # 检查是否该按 2(30-50秒一次)
  507. current_time = time.time()
  508. if current_time - last_key2_time >= random.uniform(interval_min, interval_max):
  509. self.log("发送: 2 (数字2) [暂停延续中]")
  510. self.press_key(0x32, False)
  511. last_key2_time = current_time
  512. self.log(f"⏳ 延续结束,执行停止序列(2次)")
  513. # ===== 2. 执行停止序列(2次) =====
  514. for i in range(2):
  515. self.log(f"停止序列 {i+1}/2")
  516. # 按 = 等待2-2.5秒
  517. wait_time = random.uniform(2.5, 3.0)
  518. self.log(f" 按 = (延迟 {wait_time:.1f}秒)")
  519. self.press_key(0xBB, False)
  520. time.sleep(wait_time)
  521. # 按 3 等待300-500ms
  522. wait_time = random.uniform(0.3, 0.5)
  523. self.log(f" 按 3 (延迟 {wait_time*1000:.0f}ms)")
  524. self.press_key(0x33, False)
  525. time.sleep(wait_time)
  526. # 按 3 等待2-2.5秒
  527. wait_time = random.uniform(2.0, 2.5)
  528. self.log(f" 按 3 (延迟 {wait_time:.1f}秒)")
  529. self.press_key(0x33, False)
  530. time.sleep(wait_time)
  531. # 按 4 等待300-500ms
  532. wait_time = random.uniform(0.3, 0.5)
  533. self.log(f" 按 4 (延迟 {wait_time*1000:.0f}ms)")
  534. self.press_key(0x34, False)
  535. time.sleep(wait_time)
  536. # 按 4 等待2-2.5秒
  537. wait_time = random.uniform(2.0, 2.5)
  538. self.log(f" 按 4 (延迟 {wait_time:.1f}秒)")
  539. self.press_key(0x34, False)
  540. time.sleep(wait_time)
  541. # 按 5 等待300-500ms
  542. wait_time = random.uniform(0.3, 0.5)
  543. self.log(f" 按 5 (延迟 {wait_time*1000:.0f}ms)")
  544. self.press_key(0x35, False)
  545. time.sleep(wait_time)
  546. # 按 5 等待5-10秒
  547. wait_time = random.uniform(5.0, 10.0)
  548. self.log(f" 按 5 (延迟 {wait_time:.1f}秒)")
  549. self.press_key(0x35, False)
  550. time.sleep(wait_time)
  551. # 按 ESC
  552. # self.log(f" 按 ESC")
  553. # self.press_key(0x1B, False)
  554. # 每次之间等待200-800ms(最后一次不等待)
  555. if i < 1:
  556. between_delay = random.uniform(0.2, 0.8)
  557. self.log(f" 等待 {between_delay*1000:.0f}ms 后执行下一次")
  558. time.sleep(between_delay)
  559. self.log("✅ 暂停停止序列执行完成")
  560. except Exception as e:
  561. self.log(f"执行暂停停止序列失败: {e}")
  562. # ========== 脚本B相关 ==========
  563. def start_script_b(self):
  564. """启动脚本B"""
  565. if self.is_b_running:
  566. self.log("脚本B已在运行中")
  567. return
  568. self.b_stop_flag = False
  569. self.is_b_running = True
  570. self.b_status_label.config(text="脚本B状态: 运行中", foreground="green")
  571. self.b_thread = threading.Thread(target=self.script_b_worker, daemon=True)
  572. self.b_thread.start()
  573. self.log("🚀 脚本B已启动")
  574. def stop_script_b(self):
  575. """停止脚本B"""
  576. if not self.is_b_running:
  577. self.log("脚本B未运行")
  578. return
  579. self.b_stop_flag = True
  580. self.b_status_label.config(text="脚本B状态: 正在停止...", foreground="orange")
  581. self.log("⏹ 脚本B正在停止...")
  582. def script_b_worker(self):
  583. """脚本B工作线程 - 大键盘单键循环"""
  584. try:
  585. # 按键间隔(30-50秒)
  586. interval_min = int(self.b_key_interval_min.get())
  587. interval_max = int(self.b_key_interval_max.get())
  588. # 停止延迟(10-30秒)- 现在表示继续执行的时间
  589. stop_delay_min = int(self.b_stop_delay_min.get())
  590. stop_delay_max = int(self.b_stop_delay_max.get())
  591. # 停止序列中的延迟(200-500毫秒)
  592. wait_min = int(self.b_wait_after_stop_min.get()) / 1000.0
  593. wait_max = int(self.b_wait_after_stop_max.get()) / 1000.0
  594. self.log("=" * 50)
  595. self.log("🚀 脚本B - 大键盘单键循环开始")
  596. self.log(f"按键1: - (减号) 每300-800ms一次")
  597. self.log(f"按键2: 2 (数字2) 每{interval_min}-{interval_max}秒一次")
  598. self.log("=" * 50)
  599. # 记录上次按2的时间
  600. last_key2_time = time.time()
  601. # ===== 正常运行 =====
  602. while not self.b_stop_flag:
  603. # 按 - (减号键) - 每300-800ms一次
  604. self.log("发送: - (减号)")
  605. self.press_key(0xBD, False)
  606. # 减号间隔 300-800ms
  607. key_delay = random.uniform(0.3, 0.8)
  608. time.sleep(key_delay)
  609. # 检查是否该按 2(30-50秒一次)
  610. current_time = time.time()
  611. if current_time - last_key2_time >= random.uniform(interval_min, interval_max):
  612. self.log("发送: 2 (数字2)")
  613. self.press_key(0x62, False)
  614. last_key2_time = current_time
  615. # ===== 收到停止指令后,继续执行10-30秒 =====
  616. stop_duration = random.uniform(stop_delay_min, stop_delay_max)
  617. self.log(f"⏳ 收到停止指令,继续执行 {stop_duration:.1f} 秒后停止")
  618. stop_start_time = time.time()
  619. while time.time() - stop_start_time < stop_duration:
  620. # 继续按 - (减号键)
  621. self.log("发送: - (减号) [停止延迟中]")
  622. self.press_key(0xBD, False)
  623. # 减号间隔 300-800ms
  624. key_delay = random.uniform(0.3, 0.8)
  625. time.sleep(key_delay)
  626. # 检查是否该按 2(30-50秒一次)
  627. current_time = time.time()
  628. if current_time - last_key2_time >= random.uniform(interval_min, interval_max):
  629. self.log("发送: 2 (数字2) [停止延迟中]")
  630. self.press_key(0x62, False)
  631. last_key2_time = current_time
  632. self.log(f"⏳ 延迟结束,开始执行停止序列")
  633. # ===== 执行停止序列(共3次) =====
  634. self.log("脚本B停止序列开始")
  635. # 执行3次停止序列
  636. for i in range(1):
  637. self.log(f"停止序列 {i+1}/3")
  638. # 按 = 等待2-2.5秒
  639. wait_time = random.uniform(2.5, 3.0)
  640. self.log(f" 按 = (延迟 {wait_time:.1f}秒)")
  641. self.press_key(0xBB, False)
  642. time.sleep(wait_time)
  643. # 按 3 等待300-500ms
  644. wait_time = random.uniform(0.3, 0.5)
  645. self.log(f" 按 3 (延迟 {wait_time*1000:.0f}ms)")
  646. self.press_key(0x33, False)
  647. time.sleep(wait_time)
  648. # 按 3 等待2-2.5秒
  649. wait_time = random.uniform(2.0, 2.5)
  650. self.log(f" 按 3 (延迟 {wait_time:.1f}秒)")
  651. self.press_key(0x33, False)
  652. time.sleep(wait_time)
  653. # 按 4 等待300-500ms
  654. wait_time = random.uniform(0.3, 0.5)
  655. self.log(f" 按 4 (延迟 {wait_time*1000:.0f}ms)")
  656. self.press_key(0x34, False)
  657. time.sleep(wait_time)
  658. # 按 4 等待2-2.5秒
  659. wait_time = random.uniform(2.0, 2.5)
  660. self.log(f" 按 4 (延迟 {wait_time:.1f}秒)")
  661. self.press_key(0x34, False)
  662. time.sleep(wait_time)
  663. # 按 5 等待300-500ms
  664. wait_time = random.uniform(0.3, 0.5)
  665. self.log(f" 按 5 (延迟 {wait_time*1000:.0f}ms)")
  666. self.press_key(0x35, False)
  667. time.sleep(wait_time)
  668. # 按 5 等待5-10秒
  669. wait_time = random.uniform(5.0, 10.0)
  670. self.log(f" 按 5 (延迟 {wait_time:.1f}秒)")
  671. self.press_key(0x35, False)
  672. time.sleep(wait_time)
  673. # 按 ESC
  674. # self.log(f" 按 ESC")
  675. # self.press_key(0x1B, False)
  676. # 每次之间等待200-800ms(最后一次不等待)
  677. if i < 2:
  678. between_delay = random.uniform(0.2, 0.8)
  679. self.log(f" 等待 {between_delay*1000:.0f}ms 后执行下一次")
  680. time.sleep(between_delay)
  681. self.log("✅ 脚本B停止序列完成")
  682. self.b_status_label.config(text="脚本B状态: 已停止", foreground="gray")
  683. except Exception as e:
  684. self.log(f"脚本B出错: {e}")
  685. finally:
  686. self.is_b_running = False
  687. self.b_status_label.config(text="脚本B状态: 已停止", foreground="gray")
  688. self.log("脚本B已结束")
  689. # ========== 窗口扫描 ==========
  690. def scan_vm_windows(self):
  691. self.log("正在扫描 VMware 窗口...")
  692. self.vm_windows = []
  693. def enum_callback(hwnd, _):
  694. if win32gui.IsWindowVisible(hwnd):
  695. title = win32gui.GetWindowText(hwnd)
  696. if "VMware Workstation" in title and title.strip():
  697. self.vm_windows.append((title, hwnd))
  698. return True
  699. win32gui.EnumWindows(enum_callback, None)
  700. if self.vm_windows:
  701. # 限制最多4个
  702. # self.vm_windows = self.vm_windows[:2]
  703. titles = [f"{i+1}. {t[0]}" for i, t in enumerate(self.vm_windows)]
  704. self.vm_combo["values"] = titles
  705. self.vm_combo.current(0)
  706. self.vm_window_title.set(titles[0])
  707. self.log(f"找到 {len(self.vm_windows)} 个 VMware 窗口")
  708. self.window_info_label.config(text=f"找到 {len(self.vm_windows)} 个窗口", foreground="green")
  709. self.select_window(0)
  710. else:
  711. self.log("未找到 VMware 窗口")
  712. self.window_info_label.config(text="未找到 VMware 窗口", foreground="red")
  713. self.vm_combo["values"] = []
  714. self.vm_combo.bind("<<ComboboxSelected>>", self.on_window_selected)
  715. def on_window_selected(self, event=None):
  716. selection = self.vm_combo.current()
  717. if selection >= 0:
  718. self.select_window(selection)
  719. def select_window(self, index):
  720. if index < len(self.vm_windows):
  721. title, hwnd = self.vm_windows[index]
  722. self.vm_hwnd = hwnd
  723. self.vm_window_title.set(f"{index+1}. {title}")
  724. try:
  725. rect = win32gui.GetWindowRect(hwnd)
  726. self.window_rect = rect
  727. self.window_info_label.config(
  728. text=f"已选择: {title} (大小: {rect[2]-rect[0]}x{rect[3]-rect[1]})",
  729. foreground="blue"
  730. )
  731. self.log(f"已选择窗口: {title}")
  732. except Exception as e:
  733. self.log(f"获取窗口信息失败: {e}")
  734. # ========== 坐标转换 ==========
  735. def get_absolute_coords(self, x, y):
  736. if self.window_rect is None:
  737. return None, None
  738. left, top, right, bottom = self.window_rect
  739. offset_x = 8
  740. offset_y = 30
  741. return left + offset_x + x, top + offset_y + y
  742. def activate_window(self):
  743. if self.vm_hwnd is None:
  744. return False
  745. try:
  746. if win32gui.IsIconic(self.vm_hwnd):
  747. win32gui.ShowWindow(self.vm_hwnd, win32con.SW_RESTORE)
  748. win32gui.SetForegroundWindow(self.vm_hwnd)
  749. win32gui.BringWindowToTop(self.vm_hwnd)
  750. for _ in range(5):
  751. if win32gui.GetForegroundWindow() == self.vm_hwnd:
  752. break
  753. time.sleep(0.1)
  754. win32gui.SetForegroundWindow(self.vm_hwnd)
  755. rect = win32gui.GetWindowRect(self.vm_hwnd)
  756. self.window_rect = rect
  757. return True
  758. except Exception as e:
  759. self.log(f"激活窗口失败: {e}")
  760. return False
  761. def click_at(self, x, y, button="left"):
  762. """使用幽灵键鼠在指定屏幕坐标点击"""
  763. try:
  764. if self.ghost_available:
  765. ret = movemouseto(x, y)
  766. if ret != 1:
  767. self.log(f"幽灵键鼠移动失败: {ret}")
  768. return False
  769. time.sleep(0.05)
  770. if button == "left":
  771. ret = pressandreleasemousebutton(1)
  772. else:
  773. ret = pressandreleasemousebutton(2)
  774. if ret != 1:
  775. self.log(f"幽灵键鼠点击失败: {ret}")
  776. return False
  777. return True
  778. else:
  779. win32api.SetCursorPos((x, y))
  780. time.sleep(0.05)
  781. if button == "left":
  782. win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
  783. time.sleep(0.05)
  784. win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)
  785. else:
  786. win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTDOWN, x, y, 0, 0)
  787. time.sleep(0.05)
  788. win32api.mouse_event(win32con.MOUSEEVENTF_RIGHTUP, x, y, 0, 0)
  789. return True
  790. except Exception as e:
  791. self.log(f"点击失败: {e}")
  792. return False
  793. def press_key(self, key_code, extended=False):
  794. """使用幽灵键鼠模拟按键"""
  795. try:
  796. # 按键名称映射
  797. key_name_map = {
  798. 0x68: b"num8", # 小键盘8
  799. 0x62: b"num2", # 小键盘2
  800. 0x25: b"left", # 左箭头
  801. 0x20: b"space", # 空格
  802. 0x26: b"up", # 上箭头
  803. 0x28: b"down", # 下箭头
  804. 0x27: b"right", # 右箭头
  805. 0x1B: b"esc", # ESC
  806. 0xBB: b"=", # = 号
  807. 0xBD: b"-", # - 号
  808. 0x32: b"2", # 主键盘2(如果确实需要主键盘的)
  809. 0x33: b"num3", # 数字3 ← 新增
  810. 0x34: b"num4", # 数字4 ← 新增
  811. 0x35: b"num5", # 数字5 ← 新增
  812. }
  813. if self.ghost_available:
  814. if key_code in key_name_map:
  815. pressandreleasekeybyname(key_name_map[key_code])
  816. else:
  817. if extended:
  818. win32api.keybd_event(key_code, 0, win32con.KEYEVENTF_EXTENDEDKEY, 0)
  819. time.sleep(0.03)
  820. win32api.keybd_event(key_code, 0, win32con.KEYEVENTF_EXTENDEDKEY | win32con.KEYEVENTF_KEYUP, 0)
  821. else:
  822. win32api.keybd_event(key_code, 0, 0, 0)
  823. time.sleep(0.03)
  824. win32api.keybd_event(key_code, 0, win32con.KEYEVENTF_KEYUP, 0)
  825. return True
  826. except Exception as e:
  827. self.log(f"按键失败: {e}")
  828. return False
  829. # ========== 脚本A函数 ==========
  830. def test_click(self):
  831. self.log("=" * 50)
  832. self.log("测试点击 (300, 300)...")
  833. if self.vm_hwnd is None:
  834. self.log("错误: 未选择窗口")
  835. return
  836. if not self.activate_window():
  837. self.log("激活窗口失败")
  838. return
  839. abs_x, abs_y = self.get_absolute_coords(self.ACTIVATE_X, self.ACTIVATE_Y)
  840. if abs_x is None:
  841. return
  842. self.log(f"屏幕坐标: ({abs_x}, {abs_y})")
  843. self.log(f"使用: {'幽灵键鼠' if self.ghost_available else '系统API'}")
  844. if self.click_at(abs_x, abs_y):
  845. self.log("点击成功!")
  846. else:
  847. self.log("点击失败")
  848. self.log("=" * 50)
  849. def test_keys(self):
  850. self.log("=" * 50)
  851. self.log("测试按键序列")
  852. if self.vm_hwnd is None:
  853. self.log("错误: 未选择窗口")
  854. return
  855. if not self.activate_window():
  856. self.log("激活窗口失败")
  857. return
  858. abs_x, abs_y = self.get_absolute_coords(self.ACTIVATE_X, self.ACTIVATE_Y)
  859. if abs_x is None:
  860. return
  861. self.log("点击激活虚拟机...")
  862. self.click_at(abs_x, abs_y)
  863. time.sleep(0.5)
  864. keys = [
  865. (0x68, True, "小键盘8"),
  866. (0x62, True, "小键盘2"),
  867. (0x25, False, "左键"),
  868. (0x20, False, "空格"),
  869. ]
  870. self.log(f"使用: {'幽灵键鼠' if self.ghost_available else '系统API'}")
  871. for key_code, extended, name in keys:
  872. self.log(f"发送: {name}")
  873. self.press_key(key_code, extended)
  874. time.sleep(0.3)
  875. self.log("测试完成")
  876. self.log("=" * 50)
  877. def click_at_current_position(self):
  878. """在当前鼠标位置点击左键"""
  879. try:
  880. if self.ghost_available:
  881. # 幽灵键鼠:获取当前坐标并点击
  882. x = getmousex()
  883. y = getmousey()
  884. ret = movemouseto(x, y)
  885. if ret == 1:
  886. ret = pressandreleasemousebutton(1) # 左键
  887. return ret == 1
  888. return False
  889. else:
  890. # 系统API:获取当前位置并点击
  891. x, y = win32api.GetCursorPos()
  892. win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
  893. time.sleep(0.05)
  894. win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)
  895. return True
  896. except Exception as e:
  897. self.log(f"鼠标左键点击失败: {e}")
  898. return False
  899. def loop_worker(self):
  900. # 按键定义:(类型, 键码/标识, 扩展标志, 名称)
  901. keys = [
  902. ("keyboard", 0x68, True, "小键盘8"), # 小键盘8
  903. ("keyboard", 0x62, True, "小键盘2"), # 小键盘2
  904. ("mouse", None, None, "鼠标左键"), # 鼠标左键(原来的0x25左箭头改成鼠标左键)
  905. ("keyboard", 0x20, False, "空格"), # 空格
  906. ]
  907. key_min = int(self.key_interval_min.get()) / 1000.0
  908. key_max = int(self.key_interval_max.get()) / 1000.0
  909. round_min = int(self.round_interval_min.get())
  910. round_max = int(self.round_interval_max.get())
  911. pause_min = int(self.pause_interval_min.get()) * 60
  912. pause_max = int(self.pause_interval_max.get()) * 60
  913. pause_dur = int(self.pause_duration.get())
  914. round_count = 0
  915. total_keys = 0
  916. last_pause_time = time.time()
  917. self.log("=" * 50)
  918. self.log("🚀 脚本A循环开始!")
  919. self.log(f"使用: {'幽灵键鼠' if self.ghost_available else '系统API'}")
  920. self.log(f"按键: 小键盘8 -> 小键盘2 -> 鼠标左键 -> 空格")
  921. self.log("=" * 50)
  922. # 只在开始前执行一次鼠标点击激活窗口
  923. if not self.stop_flag:
  924. try:
  925. if win32gui.IsWindow(self.vm_hwnd):
  926. self.activate_window()
  927. abs_x, abs_y = self.get_absolute_coords(self.ACTIVATE_X, self.ACTIVATE_Y)
  928. if abs_x is not None:
  929. self.log(f"初始点击激活虚拟机 ({abs_x}, {abs_y})")
  930. self.click_at(abs_x, abs_y)
  931. time.sleep(0.3)
  932. except Exception as e:
  933. self.log(f"初始点击失败: {e}")
  934. while not self.stop_flag:
  935. try:
  936. if not win32gui.IsWindow(self.vm_hwnd):
  937. self.log("窗口已关闭,停止循环")
  938. break
  939. # 执行一轮按键
  940. for key_type, key_code, extended, key_name in keys:
  941. if self.stop_flag:
  942. break
  943. self.log(f"发送: {key_name}")
  944. if key_type == "mouse":
  945. self.click_at_current_position()
  946. else:
  947. self.press_key(key_code, extended)
  948. total_keys += 1
  949. interval = random.uniform(key_min, key_max)
  950. time.sleep(interval)
  951. round_count += 1
  952. if round_count % 10 == 0:
  953. self.log(f"📊 已执行 {round_count} 轮")
  954. # ===== 暂停检查 =====
  955. current_time = time.time()
  956. if current_time - last_pause_time >= random.uniform(pause_min, pause_max):
  957. self.log(f"⏸ 到达暂停时间,通知B执行停止序列")
  958. # 通知B执行停止序列(2次)
  959. if self.ws_client and self.ws_client.is_connected:
  960. self.ws_client.send_message("pause", "暂停执行,执行停止序列")
  961. self.log("📤 已群发暂停指令到同信道脚本B")
  962. else:
  963. self.log("⚠️ WebSocket未连接,无法通知脚本B")
  964. # A自己暂停
  965. self.log(f"⏸ A暂停 {pause_dur} 秒")
  966. self.status_label.config(text=f"状态: 暂停中 ({pause_dur}s)", foreground="orange")
  967. for _ in range(int(pause_dur / 0.5)):
  968. if self.stop_flag:
  969. break
  970. time.sleep(0.5)
  971. self.log("▶ A暂停结束,继续循环")
  972. self.status_label.config(text="状态: 运行中", foreground="green")
  973. # 通知B继续(发送start)
  974. if self.ws_client and self.ws_client.is_connected:
  975. self.ws_client.send_message("start", "继续执行")
  976. self.log("📤 已群发继续指令到同信道脚本B")
  977. else:
  978. self.log("⚠️ WebSocket未连接,无法通知脚本B")
  979. last_pause_time = time.time()
  980. continue
  981. # 轮次间隔
  982. round_interval = random.uniform(round_min, round_max)
  983. sleep_chunks = max(1, int(round_interval / 0.5))
  984. for _ in range(sleep_chunks):
  985. if self.stop_flag:
  986. break
  987. time.sleep(0.5)
  988. except Exception as e:
  989. self.log(f"循环出错: {e}")
  990. time.sleep(2)
  991. self.log(f"🏁 脚本A结束!共执行 {round_count} 轮")
  992. self.status_label.config(text="状态: 已停止", foreground="gray")
  993. self.root.after(0, self.on_loop_stopped)
  994. def start_loop(self):
  995. if self.vm_hwnd is None:
  996. messagebox.showerror("错误", "请先选择虚拟机窗口!")
  997. return
  998. if self.is_running:
  999. return
  1000. if not self.ghost_available:
  1001. if not messagebox.askyesno("提示", "幽灵键鼠未连接,将使用系统API模拟按键。\n继续吗?"):
  1002. return
  1003. # ===== 新增:通过WebSocket通知同信道的脚本B =====
  1004. if self.ws_client and self.ws_client.is_connected:
  1005. self.ws_client.send_message("start", "开始执行")
  1006. self.log("📤 已群发开始指令到同信道脚本B")
  1007. else:
  1008. self.log("⚠️ WebSocket未连接,无法通知脚本B")
  1009. self.is_running = True
  1010. self.stop_flag = False
  1011. self.status_label.config(text="状态: 运行中", foreground="green")
  1012. self.start_btn.config(state=tk.DISABLED)
  1013. self.stop_btn.config(state=tk.NORMAL)
  1014. self.thread = threading.Thread(target=self.loop_worker, daemon=True)
  1015. self.thread.start()
  1016. def stop_loop(self):
  1017. self.log("⏹ 正在停止脚本A...")
  1018. # ===== 新增:通过WebSocket通知同信道的脚本B停止 =====
  1019. if self.ws_client and self.ws_client.is_connected:
  1020. self.ws_client.send_message("stop", "停止执行")
  1021. self.log("📤 已群发停止指令到同信道脚本B")
  1022. else:
  1023. self.log("⚠️ WebSocket未连接,无法通知脚本B")
  1024. self.stop_flag = True
  1025. self.status_label.config(text="状态: 正在停止...", foreground="orange")
  1026. self.start_btn.config(state=tk.NORMAL)
  1027. self.stop_btn.config(state=tk.DISABLED)
  1028. def on_loop_stopped(self):
  1029. self.is_running = False
  1030. self.start_btn.config(state=tk.NORMAL)
  1031. self.stop_btn.config(state=tk.DISABLED)
  1032. self.status_label.config(text="状态: 已停止", foreground="gray")
  1033. def __del__(self):
  1034. if self.ws_client:
  1035. self.ws_client.disconnect()
  1036. if self.ghost_available:
  1037. try:
  1038. closedevice()
  1039. except:
  1040. pass
  1041. if __name__ == "__main__":
  1042. # 先创建 root 但不显示
  1043. root = tk.Tk()
  1044. root.withdraw()
  1045. # 许可证验证(直接在 root 上显示)
  1046. try:
  1047. from license_manager import verify_license
  1048. if not verify_license(root):
  1049. sys.exit(1)
  1050. except Exception as e:
  1051. from tkinter import messagebox
  1052. messagebox.showerror("错误", f"许可证验证出错: {e}")
  1053. sys.exit(1)
  1054. # 验证通过,恢复主窗口
  1055. root.deiconify()
  1056. # 检查依赖
  1057. if not ghost_available:
  1058. try:
  1059. import win32api
  1060. except ImportError:
  1061. print("需要安装 pywin32: pip install pywin32")
  1062. sys.exit(1)
  1063. try:
  1064. import websocket
  1065. except ImportError:
  1066. print("需要安装 websocket-client: pip install websocket-client")
  1067. sys.exit(1)
  1068. app = VmControlGUI(root)
  1069. root.mainloop()