控制vm虚拟机.py 55 KB

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