控制vm虚拟机.py 42 KB

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