MuMuClear.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. import tkinter as tk
  2. from tkinter import ttk, scrolledtext, messagebox
  3. import subprocess
  4. import threading
  5. import time
  6. import json
  7. import os
  8. from datetime import datetime
  9. class MumuManager:
  10. def __init__(self, root):
  11. self.root = root
  12. self.root.title("Mumu模拟器批量启动工具")
  13. self.root.geometry("800x600")
  14. # 控制变量
  15. self.is_running = False
  16. self.is_paused = False
  17. self.should_stop = False
  18. self.current_thread = None
  19. # 创建界面
  20. self.create_widgets()
  21. def create_widgets(self):
  22. # 主框架
  23. main_frame = ttk.Frame(self.root, padding="10")
  24. main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
  25. # Mumu安装路径
  26. ttk.Label(main_frame, text="MuMu安装路径:").grid(row=0, column=0, sticky=tk.W, pady=5)
  27. self.mumu_install_path = tk.StringVar(value=r"D:\MuMuPlayer")
  28. ttk.Entry(main_frame, textvariable=self.mumu_install_path, width=70).grid(row=0, column=1, columnspan=2, sticky=(tk.W, tk.E), pady=5)
  29. # MuMuManager路径(自动查找)
  30. self.manager_path = tk.StringVar()
  31. # 同时启动模拟器数量
  32. ttk.Label(main_frame, text="同时启动数量:").grid(row=1, column=0, sticky=tk.W, pady=5)
  33. self.batch_size = tk.IntVar(value=5)
  34. ttk.Spinbox(main_frame, from_=1, to=20, textvariable=self.batch_size, width=10).grid(row=1, column=1, sticky=tk.W, pady=5)
  35. # 启动后等待时间
  36. ttk.Label(main_frame, text="启动后等待(秒):").grid(row=2, column=0, sticky=tk.W, pady=5)
  37. self.wait_time = tk.IntVar(value=60)
  38. ttk.Spinbox(main_frame, from_=10, to=180, textvariable=self.wait_time, width=10).grid(row=2, column=1, sticky=tk.W, pady=5)
  39. # 模拟器列表显示
  40. ttk.Label(main_frame, text="模拟器列表:").grid(row=3, column=0, sticky=tk.W, pady=5)
  41. # 模拟器列表框架
  42. list_frame = ttk.Frame(main_frame)
  43. list_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
  44. # 创建Treeview
  45. columns = ("选择", "索引", "名称", "状态")
  46. self.emulator_tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=10)
  47. self.emulator_tree.heading("选择", text="选择")
  48. self.emulator_tree.heading("索引", text="索引")
  49. self.emulator_tree.heading("名称", text="名称")
  50. self.emulator_tree.heading("状态", text="状态")
  51. self.emulator_tree.column("选择", width=50)
  52. self.emulator_tree.column("索引", width=80)
  53. self.emulator_tree.column("名称", width=200)
  54. self.emulator_tree.column("状态", width=100)
  55. # 滚动条
  56. vsb = ttk.Scrollbar(list_frame, orient="vertical", command=self.emulator_tree.yview)
  57. self.emulator_tree.configure(yscrollcommand=vsb.set)
  58. self.emulator_tree.pack(side='left', fill='both', expand=True)
  59. vsb.pack(side='right', fill='y')
  60. # 绑定点击事件
  61. self.emulator_tree.bind('<Button-1>', self.on_tree_click)
  62. # 按钮框架
  63. button_frame = ttk.Frame(main_frame)
  64. button_frame.grid(row=5, column=0, columnspan=3, pady=10)
  65. ttk.Button(button_frame, text="刷新列表", command=self.load_emulators).pack(side=tk.LEFT, padx=5)
  66. ttk.Button(button_frame, text="全选", command=self.select_all).pack(side=tk.LEFT, padx=5)
  67. ttk.Button(button_frame, text="全不选", command=self.select_none).pack(side=tk.LEFT, padx=5)
  68. self.start_btn = ttk.Button(button_frame, text="启动", command=self.start_process)
  69. self.start_btn.pack(side=tk.LEFT, padx=5)
  70. self.pause_btn = ttk.Button(button_frame, text="暂停", command=self.pause_process, state=tk.DISABLED)
  71. self.pause_btn.pack(side=tk.LEFT, padx=5)
  72. self.stop_btn = ttk.Button(button_frame, text="停止", command=self.stop_process, state=tk.DISABLED)
  73. self.stop_btn.pack(side=tk.LEFT, padx=5)
  74. # 进度条
  75. self.progress = ttk.Progressbar(main_frame, mode='determinate')
  76. self.progress.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
  77. # 日志框
  78. ttk.Label(main_frame, text="日志信息:").grid(row=7, column=0, sticky=tk.W, pady=5)
  79. self.log_text = scrolledtext.ScrolledText(main_frame, height=18, width=90)
  80. self.log_text.grid(row=8, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
  81. # 配置grid权重
  82. self.root.columnconfigure(0, weight=1)
  83. self.root.rowconfigure(0, weight=1)
  84. main_frame.columnconfigure(1, weight=1)
  85. main_frame.rowconfigure(8, weight=1)
  86. # 启动时自动加载模拟器列表
  87. self.root.after(100, self.load_emulators)
  88. def find_manager_path(self):
  89. """查找 MuMuManager.exe 的完整路径"""
  90. install_path = self.mumu_install_path.get()
  91. # 可能的路径
  92. possible_paths = [
  93. os.path.join(install_path, "nx_main", "MuMuManager.exe"),
  94. os.path.join(install_path, "MuMuPlayer", "nx_main", "MuMuManager.exe"),
  95. os.path.join(install_path, "shell", "MuMuManager.exe"),
  96. r"D:\MuMuPlayer\nx_main\MuMuManager.exe",
  97. r"D:\Program Files\MuMuPlayer\nx_main\MuMuManager.exe",
  98. r"C:\Program Files\MuMuPlayer\nx_main\MuMuManager.exe"
  99. ]
  100. for path in possible_paths:
  101. if os.path.exists(path):
  102. self.log(f"找到 MuMuManager: {path}")
  103. return path
  104. return None
  105. def run_mumu_command(self, args):
  106. """运行 MuMuManager 命令,自动处理 Qt 环境变量"""
  107. manager_path = self.find_manager_path()
  108. if not manager_path:
  109. self.log("❌ 找不到 MuMuManager.exe")
  110. return None
  111. try:
  112. # 获取 MuMuManager.exe 所在目录
  113. manager_dir = os.path.dirname(manager_path)
  114. # 设置环境变量
  115. env = os.environ.copy()
  116. env['QT_PLUGIN_PATH'] = manager_dir
  117. env['PATH'] = manager_dir + os.pathsep + env.get('PATH', '')
  118. # 运行命令
  119. cmd = [manager_path] + args
  120. result = subprocess.run(
  121. cmd,
  122. capture_output=True,
  123. text=True,
  124. encoding='utf-8',
  125. env=env,
  126. cwd=manager_dir
  127. )
  128. return result
  129. except Exception as e:
  130. self.log(f"运行命令出错: {e}")
  131. return None
  132. def log(self, message):
  133. """添加日志"""
  134. timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
  135. log_message = f"[{timestamp}] {message}\n"
  136. self.root.after(0, lambda: self._update_log(log_message))
  137. def _update_log(self, message):
  138. """更新日志框"""
  139. self.log_text.insert(tk.END, message)
  140. self.log_text.see(tk.END)
  141. def on_tree_click(self, event):
  142. """处理列表点击选择"""
  143. region = self.emulator_tree.identify_region(event.x, event.y)
  144. if region == "cell":
  145. column = self.emulator_tree.identify_column(event.x)
  146. if column == "#1": # 选择列
  147. item = self.emulator_tree.identify_row(event.y)
  148. if item:
  149. values = self.emulator_tree.item(item, 'values')
  150. current = values[0]
  151. new_value = "☑" if current == "□" else "□"
  152. self.emulator_tree.item(item, values=(new_value, values[1], values[2], values[3]))
  153. def select_all(self):
  154. """全选"""
  155. for item in self.emulator_tree.get_children():
  156. values = self.emulator_tree.item(item, 'values')
  157. self.emulator_tree.item(item, values=("☑", values[1], values[2], values[3]))
  158. self.log("已全选所有模拟器")
  159. def select_none(self):
  160. """全不选"""
  161. for item in self.emulator_tree.get_children():
  162. values = self.emulator_tree.item(item, 'values')
  163. self.emulator_tree.item(item, values=("□", values[1], values[2], values[3]))
  164. self.log("已取消全选")
  165. def get_selected_emulators(self):
  166. """获取选中的模拟器列表"""
  167. selected = []
  168. for item in self.emulator_tree.get_children():
  169. values = self.emulator_tree.item(item, 'values')
  170. if values[0] == "☑":
  171. selected.append({
  172. 'index': values[1],
  173. 'name': values[2]
  174. })
  175. return selected
  176. def load_emulators(self):
  177. """加载模拟器列表"""
  178. if not self.find_manager_path():
  179. self.log(f"❌ 找不到 MuMuManager.exe,请检查 MuMu安装路径")
  180. return
  181. result = self.run_mumu_command(["info", "-v", "all"])
  182. if not result or result.returncode != 0:
  183. self.log(f"获取模拟器列表失败")
  184. if result and result.stderr:
  185. self.log(f"错误信息: {result.stderr}")
  186. return
  187. # 清空现有列表
  188. for item in self.emulator_tree.get_children():
  189. self.emulator_tree.delete(item)
  190. # 解析JSON
  191. try:
  192. # 尝试清理输出(可能包含非JSON内容)
  193. output = result.stdout.strip()
  194. # 查找JSON开始的位置
  195. json_start = output.find('{')
  196. if json_start != -1:
  197. output = output[json_start:]
  198. data = json.loads(output)
  199. for key, value in data.items():
  200. if isinstance(value, dict):
  201. index = value.get('index', key)
  202. name = value.get('name', f"模拟器_{index}")
  203. status = "运行中" if value.get('is_process_started') else "未运行"
  204. self.emulator_tree.insert('', 'end', values=("□", index, name, status))
  205. count = len(self.emulator_tree.get_children())
  206. self.log(f"✅ 已加载 {count} 个模拟器")
  207. if count == 0:
  208. self.log("提示: 没有找到模拟器,请检查 MuMu 模拟器是否已安装")
  209. except json.JSONDecodeError as e:
  210. self.log(f"解析模拟器列表失败: {e}")
  211. self.log(f"原始输出: {result.stdout[:200]}...")
  212. def start_emulator(self, index):
  213. """启动单个模拟器"""
  214. result = self.run_mumu_command(["control", "-v", str(index), "launch"])
  215. if result and result.returncode == 0:
  216. self.log(f"✅ 模拟器 {index} 启动命令已发送")
  217. return True
  218. else:
  219. self.log(f"❌ 模拟器 {index} 启动失败")
  220. if result and result.stderr:
  221. self.log(f"错误: {result.stderr}")
  222. return False
  223. def wait_for_emulator_ready(self, index, timeout=120):
  224. """等待模拟器启动完成"""
  225. start_time = time.time()
  226. while time.time() - start_time < timeout:
  227. if self.should_stop or self.is_paused:
  228. return False
  229. result = self.run_mumu_command(["info", "-v", str(index)])
  230. if result and result.returncode == 0:
  231. try:
  232. # 清理输出
  233. output = result.stdout.strip()
  234. json_start = output.find('{')
  235. if json_start != -1:
  236. output = output[json_start:]
  237. data = json.loads(output)
  238. if data.get('is_android_started') == True:
  239. self.log(f"✅ 模拟器 {index} 已就绪")
  240. return True
  241. except:
  242. pass
  243. time.sleep(3)
  244. self.log(f"❌ 模拟器 {index} 启动超时")
  245. return False
  246. def perform_click_actions(self):
  247. """执行点击操作"""
  248. try:
  249. import pyautogui
  250. import win32gui
  251. import win32con
  252. # 查找MuMu模拟器窗口
  253. def find_mumu_window():
  254. def enum_callback(hwnd, windows):
  255. if win32gui.IsWindowVisible(hwnd):
  256. title = win32gui.GetWindowText(hwnd)
  257. if "MuMu模拟器" in title or (title and "MuMu" in title and "模拟器" in title):
  258. windows.append((hwnd, title))
  259. return True
  260. windows = []
  261. win32gui.EnumWindows(enum_callback, windows)
  262. return windows[0] if windows else None
  263. # 查找窗口
  264. self.log("正在查找MuMu模拟器窗口...")
  265. for i in range(15): # 最多尝试15次
  266. window_info = find_mumu_window()
  267. if window_info:
  268. hwnd, title = window_info
  269. self.log(f"找到窗口: {title}")
  270. break
  271. self.log(f"等待窗口出现... ({i+1}/15)")
  272. time.sleep(3)
  273. else:
  274. self.log("❌ 未找到MuMu模拟器窗口")
  275. return False
  276. # 激活窗口
  277. win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
  278. win32gui.SetForegroundWindow(hwnd)
  279. time.sleep(1)
  280. # 获取窗口位置
  281. rect = win32gui.GetWindowRect(hwnd)
  282. x, y = rect[0], rect[1]
  283. self.log(f"窗口位置: ({x}, {y})")
  284. self.log(f"窗口大小: {rect[2]-rect[0]} x {rect[3]-rect[1]}")
  285. # 执行点击操作
  286. clicks = [
  287. (272, 86, "第一个点"),
  288. (111, 80, "第二个点"),
  289. (447, 82, "第三个点"),
  290. (476, 164, "第四个点"),
  291. (352, 339, "第五个点"), (778, 88, "第六个点")
  292. ]
  293. for dx, dy, description in clicks:
  294. if self.should_stop or self.is_paused:
  295. self.log("操作被中断")
  296. return False
  297. click_x = x + dx
  298. click_y = y + dy
  299. self.log(f"点击{description}: ({click_x}, {click_y})")
  300. pyautogui.click(click_x, click_y)
  301. time.sleep(1)
  302. self.log("✅ 点击操作完成")
  303. return True
  304. except ImportError as e:
  305. self.log(f"缺少必要的库: {e}")
  306. self.log("请安装: pip install pyautogui pywin32")
  307. return False
  308. except Exception as e:
  309. self.log(f"执行点击操作出错: {e}")
  310. import traceback
  311. self.log(traceback.format_exc())
  312. return False
  313. def process_batch(self, emulators_batch):
  314. """处理一批模拟器"""
  315. # 启动本批模拟器
  316. for emu in emulators_batch:
  317. if self.should_stop:
  318. return False
  319. self.start_emulator(emu['index'])
  320. time.sleep(3) # 间隔启动
  321. # 等待模拟器就绪
  322. wait_seconds = self.wait_time.get()
  323. self.log(f"等待 {wait_seconds} 秒,让模拟器完全启动...")
  324. for i in range(wait_seconds):
  325. if self.should_stop:
  326. return False
  327. # 处理暂停
  328. while self.is_paused and not self.should_stop:
  329. time.sleep(1)
  330. if self.should_stop:
  331. return False
  332. if (i + 1) % 10 == 0:
  333. self.log(f"已等待 {i+1}/{wait_seconds} 秒...")
  334. time.sleep(1)
  335. # 执行点击操作
  336. self.log("开始执行点击操作...")
  337. return self.perform_click_actions()
  338. def run_automation(self):
  339. """运行自动化流程"""
  340. try:
  341. selected = self.get_selected_emulators()
  342. if not selected:
  343. self.log("请至少选择一个模拟器")
  344. return
  345. batch_size = self.batch_size.get()
  346. total = len(selected)
  347. batches = (total + batch_size - 1) // batch_size
  348. self.log(f"开始处理 {total} 个模拟器,分 {batches} 批,每批 {batch_size} 个")
  349. # 设置进度条
  350. self.progress['maximum'] = batches
  351. self.progress['value'] = 0
  352. # 分批处理
  353. for i in range(0, total, batch_size):
  354. if self.should_stop:
  355. self.log("流程已停止")
  356. break
  357. # 处理暂停
  358. while self.is_paused and not self.should_stop:
  359. time.sleep(1)
  360. if self.should_stop:
  361. break
  362. batch = selected[i:i+batch_size]
  363. batch_num = i // batch_size + 1
  364. self.log(f"\n{'='*50}")
  365. self.log(f"开始处理第 {batch_num}/{batches} 批")
  366. self.log(f"本批模拟器: {[e['index'] for e in batch]}")
  367. self.log(f"{'='*50}")
  368. success = self.process_batch(batch)
  369. # 更新进度
  370. self.progress['value'] = batch_num
  371. if not success and not self.is_paused:
  372. self.log("流程中断")
  373. break
  374. # 批次间等待(除了最后一批)
  375. if i + batch_size < total and not self.should_stop:
  376. self.log("等待30秒后处理下一批...")
  377. for _ in range(30):
  378. if self.should_stop:
  379. break
  380. time.sleep(1)
  381. if not self.should_stop:
  382. self.log("\n🎉 所有任务执行完毕!")
  383. except Exception as e:
  384. self.log(f"运行出错: {e}")
  385. import traceback
  386. self.log(traceback.format_exc())
  387. finally:
  388. self.stop_process()
  389. def start_process(self):
  390. """启动流程"""
  391. if self.is_running:
  392. self.log("流程已在运行中")
  393. return
  394. # 检查MuMuManager.exe
  395. if not self.find_manager_path():
  396. messagebox.showerror("错误", f"找不到 MuMuManager.exe\n请检查 MuMu安装路径: {self.mumu_install_path.get()}")
  397. return
  398. # 检查选中的模拟器
  399. selected = self.get_selected_emulators()
  400. if not selected:
  401. messagebox.showwarning("警告", "请至少选择一个模拟器")
  402. return
  403. self.is_running = True
  404. self.is_paused = False
  405. self.should_stop = False
  406. # 更新按钮状态
  407. self.start_btn.config(state=tk.DISABLED)
  408. self.pause_btn.config(state=tk.NORMAL)
  409. self.stop_btn.config(state=tk.NORMAL)
  410. # 清空进度条
  411. self.progress['value'] = 0
  412. # 在新线程中运行
  413. self.current_thread = threading.Thread(target=self.run_automation)
  414. self.current_thread.daemon = True
  415. self.current_thread.start()
  416. self.log("🚀 自动化流程已启动")
  417. def pause_process(self):
  418. """暂停/继续流程"""
  419. if not self.is_running:
  420. return
  421. if self.is_paused:
  422. self.is_paused = False
  423. self.pause_btn.config(text="暂停")
  424. self.log("▶️ 流程已继续")
  425. else:
  426. self.is_paused = True
  427. self.pause_btn.config(text="继续")
  428. self.log("⏸ 流程已暂停")
  429. def stop_process(self):
  430. """停止流程"""
  431. self.should_stop = True
  432. self.is_running = False
  433. self.is_paused = False
  434. # 更新按钮状态
  435. self.start_btn.config(state=tk.NORMAL)
  436. self.pause_btn.config(state=tk.DISABLED, text="暂停")
  437. self.stop_btn.config(state=tk.DISABLED)
  438. self.log("⏹ 流程已停止")
  439. def main():
  440. root = tk.Tk()
  441. app = MumuManager(root)
  442. root.mainloop()
  443. if __name__ == "__main__":
  444. main()