Ver Fonte

定时按某键

PUGE há 3 semanas atrás
pai
commit
dda5ddbe97
3 ficheiros alterados com 824 adições e 26 exclusões
  1. 37 26
      MuMu.py
  2. 552 0
      MuMuClear.py
  3. 235 0
      定时按某键.py

+ 37 - 26
MuMu.py

@@ -94,25 +94,29 @@ class MuMuEmulatorManager:
         result = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8')
         return result.returncode == 0
     
-    def clear_app_data(self, index, package_name, log_callback=None):
+    def clear_app_data(self, index, manager, log_callback):
         """清除指定应用的数据"""
-        adb_port = self.get_adb_port(index)
-        target_device = f"127.0.0.1:{adb_port}"
-        
-        subprocess.run(f"adb connect {target_device}", shell=True, capture_output=True)
-        time.sleep(1)
-        
-        cmd = f"adb -s {target_device} shell pm clear {package_name}"
-        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
-        
-        if "Success" in result.stdout:
-            if log_callback:
-                log_callback(f"✅ 模拟器 {index} 已清除 {package_name} 数据")
-            return True
-        else:
-            if log_callback:
-                log_callback(f"⚠️ 模拟器 {index} 清除数据失败: {result.stdout}")
-            return False
+        manager.tap(index, 59, 90, log_callback)
+        time.sleep(2)
+        manager.tap(index, 59, 90, log_callback)
+        time.sleep(2)
+        manager.tap(index, 59, 90, log_callback)
+        time.sleep(2)
+        manager.tap(index, 59, 90, log_callback)
+        time.sleep(2)
+        color5990 = manager.get_pixel_color(index, 59, 90, log_callback=log_callback)
+        log_callback(color5990)
+        if color5990 == "#F3F3F3":
+            manager.tap(index, 645, 1240, log_callback)
+            time.sleep(2)
+            manager.tap(index, 663, 91, log_callback)
+            time.sleep(2)
+            manager.tap(index, 663, 91, log_callback)
+            time.sleep(2)
+            manager.tap(index, 144, 239, log_callback)
+            time.sleep(2)
+            manager.tap(index, 592, 618, log_callback)
+            time.sleep(2)
     
     def install_apk(self, index, apk_path, log_callback=None):
         """安装APK"""
@@ -801,7 +805,8 @@ class MuMuAutoGUI:
                 self.log_message(f"模拟器 {index} 尝试重新点击搜索...")
             elif color56065 == "#5E635E":
                 self.log_message(f"模拟器 {index} 需要关闭红包弹窗...")
-                manager.tap(index, 640, 260, self.log_message)
+                # manager.tap(index, 640, 260, self.log_message)
+                manager.tap(index, 360, 1035, self.log_message)
                 time.sleep(20)
                 manager.tap(index, 57, 193, self.log_message)
             elif color56065 in ["#EBE8E4", "#CDD0D1"]:
@@ -989,7 +994,8 @@ class MuMuAutoGUI:
                             break
                         elif color560130 in ["#5F635F"]:
                             self.log_message(f"模拟器 {index} 需要关闭红包弹窗...")
-                            manager.tap(index, 640, 260, self.log_message)
+                            # manager.tap(index, 640, 260, self.log_message)
+                            manager.tap(index, 360, 1035, self.log_message)
                             time.sleep(20)
                             manager.tap(index, 57, 193, self.log_message)
                         elif color560130 in ["#CED0D2"]:
@@ -1051,6 +1057,9 @@ class MuMuAutoGUI:
                                 manager.tap(index, 700, 300, self.log_message)
                             else:
                                 errNumber = errNumber + 1
+                                if errNumber > 5:
+                                    self.log_message(f"尝试滑动是否能跳过!")
+                                    manager.swipe(index, 700, 700, 200, 700, 500, self.log_message)
                                 if errNumber > 10:
                                     self.log_message(f"模拟器 {index} 连续多次未检测到目录界面,退出...")
                                     manager.stop_emulator(index)
@@ -1090,7 +1099,7 @@ class MuMuAutoGUI:
 
                     # 清除应用数据
                     self.log_message(f"正在清除模拟器 {index} 应用数据...")
-                    manager.clear_app_data(index, self.package_name_var.get(), self.log_message)
+                    manager.clear_app_data(index, manager, self.log_message)
                     time.sleep(2)
                     
                     # 关闭模拟器
@@ -1131,6 +1140,8 @@ class MuMuAutoGUI:
             
             success_count = sum(1 for v in self.results.values() if v)
             self.log_message(f"\n🎉 任务执行完毕!成功: {success_count}/{len(self.selected_emulators)}")
+            self.select_failed_emulators()
+            self.start_task()
             
         except Exception as e:
             self.log_message(f"❌ 任务执行出错: {e}")
@@ -1226,7 +1237,8 @@ class MuMuAutoGUI:
                             break
                         elif color560130 in ["#5F635F"]:
                             self.log_message(f"模拟器 {index} 需要关闭红包弹窗...")
-                            manager.tap(index, 640, 260, self.log_message)
+                            # manager.tap(index, 640, 260, self.log_message)
+                            manager.tap(index, 360, 1035, self.log_message)
                             time.sleep(20)
                             manager.tap(index, 57, 193, self.log_message)
                         elif color560130 in ["#CED0D2"]:
@@ -1269,7 +1281,7 @@ class MuMuAutoGUI:
 
                     # 清除应用数据
                     self.log_message(f"正在清除模拟器 {index} 应用数据...")
-                    manager.clear_app_data(index, self.package_name_var.get(), self.log_message)
+                    manager.clear_app_data(index, manager, self.log_message)
                     time.sleep(2)
                     
                     # 关闭模拟器
@@ -1338,6 +1350,5 @@ class MuMuAutoGUI:
 
 
 if __name__ == "__main__":
-    if datetime.now() < datetime(2026, 5, 25):
-        app = MuMuAutoGUI()
-        app.run()
+    app = MuMuAutoGUI()
+    app.run()

+ 552 - 0
MuMuClear.py

@@ -0,0 +1,552 @@
+import tkinter as tk
+from tkinter import ttk, scrolledtext, messagebox
+import subprocess
+import threading
+import time
+import json
+import os
+from datetime import datetime
+
+class MumuManager:
+    def __init__(self, root):
+        self.root = root
+        self.root.title("Mumu模拟器批量启动工具")
+        self.root.geometry("800x600")
+        
+        # 控制变量
+        self.is_running = False
+        self.is_paused = False
+        self.should_stop = False
+        self.current_thread = None
+        
+        # 创建界面
+        self.create_widgets()
+        
+    def create_widgets(self):
+        # 主框架
+        main_frame = ttk.Frame(self.root, padding="10")
+        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+        
+        # Mumu安装路径
+        ttk.Label(main_frame, text="MuMu安装路径:").grid(row=0, column=0, sticky=tk.W, pady=5)
+        self.mumu_install_path = tk.StringVar(value=r"D:\MuMuPlayer")
+        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)
+        
+        # MuMuManager路径(自动查找)
+        self.manager_path = tk.StringVar()
+        
+        # 同时启动模拟器数量
+        ttk.Label(main_frame, text="同时启动数量:").grid(row=1, column=0, sticky=tk.W, pady=5)
+        self.batch_size = tk.IntVar(value=5)
+        ttk.Spinbox(main_frame, from_=1, to=20, textvariable=self.batch_size, width=10).grid(row=1, column=1, sticky=tk.W, pady=5)
+        
+        # 启动后等待时间
+        ttk.Label(main_frame, text="启动后等待(秒):").grid(row=2, column=0, sticky=tk.W, pady=5)
+        self.wait_time = tk.IntVar(value=60)
+        ttk.Spinbox(main_frame, from_=10, to=180, textvariable=self.wait_time, width=10).grid(row=2, column=1, sticky=tk.W, pady=5)
+        
+        # 模拟器列表显示
+        ttk.Label(main_frame, text="模拟器列表:").grid(row=3, column=0, sticky=tk.W, pady=5)
+        
+        # 模拟器列表框架
+        list_frame = ttk.Frame(main_frame)
+        list_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
+        
+        # 创建Treeview
+        columns = ("选择", "索引", "名称", "状态")
+        self.emulator_tree = ttk.Treeview(list_frame, columns=columns, show='headings', height=10)
+        
+        self.emulator_tree.heading("选择", text="选择")
+        self.emulator_tree.heading("索引", text="索引")
+        self.emulator_tree.heading("名称", text="名称")
+        self.emulator_tree.heading("状态", text="状态")
+        
+        self.emulator_tree.column("选择", width=50)
+        self.emulator_tree.column("索引", width=80)
+        self.emulator_tree.column("名称", width=200)
+        self.emulator_tree.column("状态", width=100)
+        
+        # 滚动条
+        vsb = ttk.Scrollbar(list_frame, orient="vertical", command=self.emulator_tree.yview)
+        self.emulator_tree.configure(yscrollcommand=vsb.set)
+        
+        self.emulator_tree.pack(side='left', fill='both', expand=True)
+        vsb.pack(side='right', fill='y')
+        
+        # 绑定点击事件
+        self.emulator_tree.bind('<Button-1>', self.on_tree_click)
+        
+        # 按钮框架
+        button_frame = ttk.Frame(main_frame)
+        button_frame.grid(row=5, column=0, columnspan=3, pady=10)
+        
+        ttk.Button(button_frame, text="刷新列表", command=self.load_emulators).pack(side=tk.LEFT, padx=5)
+        ttk.Button(button_frame, text="全选", command=self.select_all).pack(side=tk.LEFT, padx=5)
+        ttk.Button(button_frame, text="全不选", command=self.select_none).pack(side=tk.LEFT, padx=5)
+        
+        self.start_btn = ttk.Button(button_frame, text="启动", command=self.start_process)
+        self.start_btn.pack(side=tk.LEFT, padx=5)
+        
+        self.pause_btn = ttk.Button(button_frame, text="暂停", command=self.pause_process, state=tk.DISABLED)
+        self.pause_btn.pack(side=tk.LEFT, padx=5)
+        
+        self.stop_btn = ttk.Button(button_frame, text="停止", command=self.stop_process, state=tk.DISABLED)
+        self.stop_btn.pack(side=tk.LEFT, padx=5)
+        
+        # 进度条
+        self.progress = ttk.Progressbar(main_frame, mode='determinate')
+        self.progress.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=5)
+        
+        # 日志框
+        ttk.Label(main_frame, text="日志信息:").grid(row=7, column=0, sticky=tk.W, pady=5)
+        self.log_text = scrolledtext.ScrolledText(main_frame, height=18, width=90)
+        self.log_text.grid(row=8, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=5)
+        
+        # 配置grid权重
+        self.root.columnconfigure(0, weight=1)
+        self.root.rowconfigure(0, weight=1)
+        main_frame.columnconfigure(1, weight=1)
+        main_frame.rowconfigure(8, weight=1)
+        
+        # 启动时自动加载模拟器列表
+        self.root.after(100, self.load_emulators)
+        
+    def find_manager_path(self):
+        """查找 MuMuManager.exe 的完整路径"""
+        install_path = self.mumu_install_path.get()
+        
+        # 可能的路径
+        possible_paths = [
+            os.path.join(install_path, "nx_main", "MuMuManager.exe"),
+            os.path.join(install_path, "MuMuPlayer", "nx_main", "MuMuManager.exe"),
+            os.path.join(install_path, "shell", "MuMuManager.exe"),
+            r"D:\MuMuPlayer\nx_main\MuMuManager.exe",
+            r"D:\Program Files\MuMuPlayer\nx_main\MuMuManager.exe",
+            r"C:\Program Files\MuMuPlayer\nx_main\MuMuManager.exe"
+        ]
+        
+        for path in possible_paths:
+            if os.path.exists(path):
+                self.log(f"找到 MuMuManager: {path}")
+                return path
+                
+        return None
+        
+    def run_mumu_command(self, args):
+        """运行 MuMuManager 命令,自动处理 Qt 环境变量"""
+        manager_path = self.find_manager_path()
+        if not manager_path:
+            self.log("❌ 找不到 MuMuManager.exe")
+            return None
+            
+        try:
+            # 获取 MuMuManager.exe 所在目录
+            manager_dir = os.path.dirname(manager_path)
+            
+            # 设置环境变量
+            env = os.environ.copy()
+            env['QT_PLUGIN_PATH'] = manager_dir
+            env['PATH'] = manager_dir + os.pathsep + env.get('PATH', '')
+            
+            # 运行命令
+            cmd = [manager_path] + args
+            result = subprocess.run(
+                cmd,
+                capture_output=True,
+                text=True,
+                encoding='utf-8',
+                env=env,
+                cwd=manager_dir
+            )
+            
+            return result
+            
+        except Exception as e:
+            self.log(f"运行命令出错: {e}")
+            return None
+            
+    def log(self, message):
+        """添加日志"""
+        timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        log_message = f"[{timestamp}] {message}\n"
+        self.root.after(0, lambda: self._update_log(log_message))
+        
+    def _update_log(self, message):
+        """更新日志框"""
+        self.log_text.insert(tk.END, message)
+        self.log_text.see(tk.END)
+        
+    def on_tree_click(self, event):
+        """处理列表点击选择"""
+        region = self.emulator_tree.identify_region(event.x, event.y)
+        if region == "cell":
+            column = self.emulator_tree.identify_column(event.x)
+            if column == "#1":  # 选择列
+                item = self.emulator_tree.identify_row(event.y)
+                if item:
+                    values = self.emulator_tree.item(item, 'values')
+                    current = values[0]
+                    new_value = "☑" if current == "□" else "□"
+                    self.emulator_tree.item(item, values=(new_value, values[1], values[2], values[3]))
+                    
+    def select_all(self):
+        """全选"""
+        for item in self.emulator_tree.get_children():
+            values = self.emulator_tree.item(item, 'values')
+            self.emulator_tree.item(item, values=("☑", values[1], values[2], values[3]))
+        self.log("已全选所有模拟器")
+        
+    def select_none(self):
+        """全不选"""
+        for item in self.emulator_tree.get_children():
+            values = self.emulator_tree.item(item, 'values')
+            self.emulator_tree.item(item, values=("□", values[1], values[2], values[3]))
+        self.log("已取消全选")
+        
+    def get_selected_emulators(self):
+        """获取选中的模拟器列表"""
+        selected = []
+        for item in self.emulator_tree.get_children():
+            values = self.emulator_tree.item(item, 'values')
+            if values[0] == "☑":
+                selected.append({
+                    'index': values[1],
+                    'name': values[2]
+                })
+        return selected
+        
+    def load_emulators(self):
+        """加载模拟器列表"""
+        if not self.find_manager_path():
+            self.log(f"❌ 找不到 MuMuManager.exe,请检查 MuMu安装路径")
+            return
+            
+        result = self.run_mumu_command(["info", "-v", "all"])
+        
+        if not result or result.returncode != 0:
+            self.log(f"获取模拟器列表失败")
+            if result and result.stderr:
+                self.log(f"错误信息: {result.stderr}")
+            return
+            
+        # 清空现有列表
+        for item in self.emulator_tree.get_children():
+            self.emulator_tree.delete(item)
+            
+        # 解析JSON
+        try:
+            # 尝试清理输出(可能包含非JSON内容)
+            output = result.stdout.strip()
+            # 查找JSON开始的位置
+            json_start = output.find('{')
+            if json_start != -1:
+                output = output[json_start:]
+                
+            data = json.loads(output)
+            for key, value in data.items():
+                if isinstance(value, dict):
+                    index = value.get('index', key)
+                    name = value.get('name', f"模拟器_{index}")
+                    status = "运行中" if value.get('is_process_started') else "未运行"
+                    self.emulator_tree.insert('', 'end', values=("□", index, name, status))
+                    
+            count = len(self.emulator_tree.get_children())
+            self.log(f"✅ 已加载 {count} 个模拟器")
+            
+            if count == 0:
+                self.log("提示: 没有找到模拟器,请检查 MuMu 模拟器是否已安装")
+                
+        except json.JSONDecodeError as e:
+            self.log(f"解析模拟器列表失败: {e}")
+            self.log(f"原始输出: {result.stdout[:200]}...")
+            
+    def start_emulator(self, index):
+        """启动单个模拟器"""
+        result = self.run_mumu_command(["control", "-v", str(index), "launch"])
+        
+        if result and result.returncode == 0:
+            self.log(f"✅ 模拟器 {index} 启动命令已发送")
+            return True
+        else:
+            self.log(f"❌ 模拟器 {index} 启动失败")
+            if result and result.stderr:
+                self.log(f"错误: {result.stderr}")
+            return False
+            
+    def wait_for_emulator_ready(self, index, timeout=120):
+        """等待模拟器启动完成"""
+        start_time = time.time()
+        
+        while time.time() - start_time < timeout:
+            if self.should_stop or self.is_paused:
+                return False
+                
+            result = self.run_mumu_command(["info", "-v", str(index)])
+            
+            if result and result.returncode == 0:
+                try:
+                    # 清理输出
+                    output = result.stdout.strip()
+                    json_start = output.find('{')
+                    if json_start != -1:
+                        output = output[json_start:]
+                        
+                    data = json.loads(output)
+                    if data.get('is_android_started') == True:
+                        self.log(f"✅ 模拟器 {index} 已就绪")
+                        return True
+                except:
+                    pass
+                    
+            time.sleep(3)
+            
+        self.log(f"❌ 模拟器 {index} 启动超时")
+        return False
+        
+    def perform_click_actions(self):
+        """执行点击操作"""
+        try:
+            import pyautogui
+            import win32gui
+            import win32con
+            
+            # 查找MuMu模拟器窗口
+            def find_mumu_window():
+                def enum_callback(hwnd, windows):
+                    if win32gui.IsWindowVisible(hwnd):
+                        title = win32gui.GetWindowText(hwnd)
+                        if "MuMu模拟器" in title or (title and "MuMu" in title and "模拟器" in title):
+                            windows.append((hwnd, title))
+                    return True
+                    
+                windows = []
+                win32gui.EnumWindows(enum_callback, windows)
+                return windows[0] if windows else None
+                
+            # 查找窗口
+            self.log("正在查找MuMu模拟器窗口...")
+            for i in range(15):  # 最多尝试15次
+                window_info = find_mumu_window()
+                if window_info:
+                    hwnd, title = window_info
+                    self.log(f"找到窗口: {title}")
+                    break
+                self.log(f"等待窗口出现... ({i+1}/15)")
+                time.sleep(3)
+            else:
+                self.log("❌ 未找到MuMu模拟器窗口")
+                return False
+                
+            # 激活窗口
+            win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
+            win32gui.SetForegroundWindow(hwnd)
+            time.sleep(1)
+            
+            # 获取窗口位置
+            rect = win32gui.GetWindowRect(hwnd)
+            x, y = rect[0], rect[1]
+            self.log(f"窗口位置: ({x}, {y})")
+            self.log(f"窗口大小: {rect[2]-rect[0]} x {rect[3]-rect[1]}")
+            
+            # 执行点击操作
+            clicks = [
+                (272, 86, "第一个点"),
+                (111, 80, "第二个点"),
+                (447, 82, "第三个点"),
+                (476, 164, "第四个点"),
+                (352, 339, "第五个点"), (778, 88, "第六个点")
+            ]
+            
+            for dx, dy, description in clicks:
+                if self.should_stop or self.is_paused:
+                    self.log("操作被中断")
+                    return False
+                    
+                click_x = x + dx
+                click_y = y + dy
+                self.log(f"点击{description}: ({click_x}, {click_y})")
+                pyautogui.click(click_x, click_y)
+                time.sleep(1)
+                
+            self.log("✅ 点击操作完成")
+            return True
+            
+        except ImportError as e:
+            self.log(f"缺少必要的库: {e}")
+            self.log("请安装: pip install pyautogui pywin32")
+            return False
+        except Exception as e:
+            self.log(f"执行点击操作出错: {e}")
+            import traceback
+            self.log(traceback.format_exc())
+            return False
+            
+    def process_batch(self, emulators_batch):
+        """处理一批模拟器"""
+        # 启动本批模拟器
+        for emu in emulators_batch:
+            if self.should_stop:
+                return False
+                
+            self.start_emulator(emu['index'])
+            time.sleep(3)  # 间隔启动
+            
+        # 等待模拟器就绪
+        wait_seconds = self.wait_time.get()
+        self.log(f"等待 {wait_seconds} 秒,让模拟器完全启动...")
+        
+        for i in range(wait_seconds):
+            if self.should_stop:
+                return False
+                
+            # 处理暂停
+            while self.is_paused and not self.should_stop:
+                time.sleep(1)
+                
+            if self.should_stop:
+                return False
+                
+            if (i + 1) % 10 == 0:
+                self.log(f"已等待 {i+1}/{wait_seconds} 秒...")
+            time.sleep(1)
+            
+        # 执行点击操作
+        self.log("开始执行点击操作...")
+        return self.perform_click_actions()
+        
+    def run_automation(self):
+        """运行自动化流程"""
+        try:
+            selected = self.get_selected_emulators()
+            if not selected:
+                self.log("请至少选择一个模拟器")
+                return
+                
+            batch_size = self.batch_size.get()
+            total = len(selected)
+            batches = (total + batch_size - 1) // batch_size
+            
+            self.log(f"开始处理 {total} 个模拟器,分 {batches} 批,每批 {batch_size} 个")
+            
+            # 设置进度条
+            self.progress['maximum'] = batches
+            self.progress['value'] = 0
+            
+            # 分批处理
+            for i in range(0, total, batch_size):
+                if self.should_stop:
+                    self.log("流程已停止")
+                    break
+                    
+                # 处理暂停
+                while self.is_paused and not self.should_stop:
+                    time.sleep(1)
+                    
+                if self.should_stop:
+                    break
+                    
+                batch = selected[i:i+batch_size]
+                batch_num = i // batch_size + 1
+                
+                self.log(f"\n{'='*50}")
+                self.log(f"开始处理第 {batch_num}/{batches} 批")
+                self.log(f"本批模拟器: {[e['index'] for e in batch]}")
+                self.log(f"{'='*50}")
+                
+                success = self.process_batch(batch)
+                
+                # 更新进度
+                self.progress['value'] = batch_num
+                
+                if not success and not self.is_paused:
+                    self.log("流程中断")
+                    break
+                    
+                # 批次间等待(除了最后一批)
+                if i + batch_size < total and not self.should_stop:
+                    self.log("等待30秒后处理下一批...")
+                    for _ in range(30):
+                        if self.should_stop:
+                            break
+                        time.sleep(1)
+                        
+            if not self.should_stop:
+                self.log("\n🎉 所有任务执行完毕!")
+            
+        except Exception as e:
+            self.log(f"运行出错: {e}")
+            import traceback
+            self.log(traceback.format_exc())
+        finally:
+            self.stop_process()
+            
+    def start_process(self):
+        """启动流程"""
+        if self.is_running:
+            self.log("流程已在运行中")
+            return
+            
+        # 检查MuMuManager.exe
+        if not self.find_manager_path():
+            messagebox.showerror("错误", f"找不到 MuMuManager.exe\n请检查 MuMu安装路径: {self.mumu_install_path.get()}")
+            return
+            
+        # 检查选中的模拟器
+        selected = self.get_selected_emulators()
+        if not selected:
+            messagebox.showwarning("警告", "请至少选择一个模拟器")
+            return
+            
+        self.is_running = True
+        self.is_paused = False
+        self.should_stop = False
+        
+        # 更新按钮状态
+        self.start_btn.config(state=tk.DISABLED)
+        self.pause_btn.config(state=tk.NORMAL)
+        self.stop_btn.config(state=tk.NORMAL)
+        
+        # 清空进度条
+        self.progress['value'] = 0
+        
+        # 在新线程中运行
+        self.current_thread = threading.Thread(target=self.run_automation)
+        self.current_thread.daemon = True
+        self.current_thread.start()
+        
+        self.log("🚀 自动化流程已启动")
+        
+    def pause_process(self):
+        """暂停/继续流程"""
+        if not self.is_running:
+            return
+            
+        if self.is_paused:
+            self.is_paused = False
+            self.pause_btn.config(text="暂停")
+            self.log("▶️ 流程已继续")
+        else:
+            self.is_paused = True
+            self.pause_btn.config(text="继续")
+            self.log("⏸ 流程已暂停")
+            
+    def stop_process(self):
+        """停止流程"""
+        self.should_stop = True
+        self.is_running = False
+        self.is_paused = False
+        
+        # 更新按钮状态
+        self.start_btn.config(state=tk.NORMAL)
+        self.pause_btn.config(state=tk.DISABLED, text="暂停")
+        self.stop_btn.config(state=tk.DISABLED)
+        
+        self.log("⏹ 流程已停止")
+        
+def main():
+    root = tk.Tk()
+    app = MumuManager(root)
+    root.mainloop()
+    
+if __name__ == "__main__":
+    main()

+ 235 - 0
定时按某键.py

@@ -0,0 +1,235 @@
+import tkinter as tk
+from tkinter import ttk
+import time
+from datetime import datetime, timedelta
+import threading
+import queue
+import ctypes
+import sys
+
+class PreciseTimerGUI:
+    def __init__(self):
+        self.root = tk.Tk()
+        self.root.title("精准定时按键工具")
+        self.root.geometry("500x300")
+        self.root.resizable(False, False)
+        
+        # 设置窗口图标和置顶
+        self.root.attributes('-topmost', True)
+        
+        # 变量定义
+        self.target_time = None
+        self.is_running = False
+        self.update_queue = queue.Queue()
+        
+        # 按键映射(实际发送虚拟按键需要对应键盘码)
+        self.key_map = {
+            "1": 0x31, "2": 0x32, "3": 0x33, "4": 0x34,
+            "9": 0x39, "D": 0x44, "M": 0x4D, "O": 0x4F, 
+            "P": 0x50, "回车": 0x0D
+        }
+        
+        self.setup_ui()
+        self.update_time()
+        self.check_timer()
+        
+    def setup_ui(self):
+        """设置用户界面"""
+        # 主框架
+        main_frame = ttk.Frame(self.root, padding="20")
+        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
+        
+        # 当前时间显示
+        ttk.Label(main_frame, text="当前系统时间:", font=("Arial", 10)).grid(row=0, column=0, sticky=tk.W, pady=(0, 5))
+        
+        self.time_var = tk.StringVar()
+        self.time_label = ttk.Label(main_frame, textvariable=self.time_var, font=("Courier", 24, "bold"))
+        self.time_label.grid(row=1, column=0, columnspan=2, pady=(0, 20))
+        
+        # 分隔线
+        ttk.Separator(main_frame, orient='horizontal').grid(row=2, column=0, columnspan=2, sticky=(tk.W, tk.E), pady=(0, 20))
+        
+        # 定时设置区域
+        ttk.Label(main_frame, text="定时设置:", font=("Arial", 10, "bold")).grid(row=3, column=0, sticky=tk.W, pady=(0, 10))
+        
+        # 时间输入区域
+        time_frame = ttk.Frame(main_frame)
+        time_frame.grid(row=4, column=0, columnspan=2, pady=(0, 10))
+        
+        ttk.Label(time_frame, text="目标时间:").pack(side=tk.LEFT, padx=(0, 5))
+        
+        # 时分秒毫秒输入框
+        self.hour_var = tk.StringVar(value="00")
+        self.min_var = tk.StringVar(value="00")
+        self.sec_var = tk.StringVar(value="00")
+        self.ms_var = tk.StringVar(value="000")
+        
+        hour_spin = ttk.Spinbox(time_frame, from_=0, to=23, width=3, textvariable=self.hour_var, format="%02.0f")
+        hour_spin.pack(side=tk.LEFT, padx=2)
+        ttk.Label(time_frame, text=":").pack(side=tk.LEFT)
+        
+        min_spin = ttk.Spinbox(time_frame, from_=0, to=59, width=3, textvariable=self.min_var, format="%02.0f")
+        min_spin.pack(side=tk.LEFT, padx=2)
+        ttk.Label(time_frame, text=":").pack(side=tk.LEFT)
+        
+        sec_spin = ttk.Spinbox(time_frame, from_=0, to=59, width=3, textvariable=self.sec_var, format="%02.0f")
+        sec_spin.pack(side=tk.LEFT, padx=2)
+        ttk.Label(time_frame, text=".").pack(side=tk.LEFT)
+        
+        ms_spin = ttk.Spinbox(time_frame, from_=0, to=999, width=4, textvariable=self.ms_var, format="%03.0f")
+        ms_spin.pack(side=tk.LEFT, padx=2)
+        
+        # 按键选择
+        key_frame = ttk.Frame(main_frame)
+        key_frame.grid(row=5, column=0, columnspan=2, pady=(0, 10))
+        
+        ttk.Label(key_frame, text="触发按键:").pack(side=tk.LEFT, padx=(0, 10))
+        
+        self.key_var = tk.StringVar(value="回车")
+        key_combo = ttk.Combobox(key_frame, textvariable=self.key_var, values=list(self.key_map.keys()), width=10, state="readonly")
+        key_combo.pack(side=tk.LEFT)
+        
+        # 按钮区域
+        button_frame = ttk.Frame(main_frame)
+        button_frame.grid(row=6, column=0, columnspan=2, pady=(10, 0))
+        
+        self.start_button = ttk.Button(button_frame, text="启动定时", command=self.start_timer, width=12)
+        self.start_button.pack(side=tk.LEFT, padx=5)
+        
+        self.stop_button = ttk.Button(button_frame, text="停止定时", command=self.stop_timer, state=tk.DISABLED, width=12)
+        self.stop_button.pack(side=tk.LEFT, padx=5)
+        
+        ttk.Button(button_frame, text="设置当前时间", command=self.set_current_time, width=15).pack(side=tk.LEFT, padx=5)
+        
+        # 状态显示
+        self.status_var = tk.StringVar(value="就绪")
+        status_label = ttk.Label(main_frame, textvariable=self.status_var, font=("Arial", 9), foreground="gray")
+        status_label.grid(row=7, column=0, columnspan=2, pady=(20, 0))
+        
+    def update_time(self):
+        """更新时间显示(精确到毫秒)"""
+        now = datetime.now()
+        time_str = now.strftime("%Y-%m-%d %H:%M:%S") + f".{now.microsecond//1000:03d}"
+        self.time_var.set(time_str)
+        
+        # 使用after方法实现低CPU占用的定时更新
+        self.root.after(10, self.update_time)
+        
+    def set_current_time(self):
+        """将目标时间设置为当前时间"""
+        now = datetime.now()
+        self.hour_var.set(f"{now.hour:02d}")
+        self.min_var.set(f"{now.minute:02d}")
+        self.sec_var.set(f"{now.second:02d}")
+        self.ms_var.set(f"{(now.microsecond//1000):03d}")
+        self.status_var.set("已设置为当前时间")
+        self.root.after(2000, lambda: self.status_var.set("就绪"))
+        
+    def start_timer(self):
+        """启动定时器"""
+        try:
+            # 解析目标时间
+            hour = int(self.hour_var.get())
+            minute = int(self.min_var.get())
+            second = int(self.sec_var.get())
+            millisecond = int(self.ms_var.get())
+            
+            now = datetime.now()
+            target = now.replace(hour=hour, minute=minute, second=second, microsecond=millisecond*1000)
+            
+            # 如果目标时间小于当前时间,设置为明天
+            if target <= now:
+                target += timedelta(days=1)
+                
+            self.target_time = target
+            self.is_running = True
+            
+            self.start_button.config(state=tk.DISABLED)
+            self.stop_button.config(state=tk.NORMAL)
+            
+            # 显示目标时间和倒计时
+            time_remaining = (target - now).total_seconds()
+            self.status_var.set(f"定时已启动,目标时间: {target.strftime('%H:%M:%S')}.{millisecond:03d},剩余: {time_remaining:.2f}秒")
+            
+        except ValueError:
+            self.status_var.set("请输入有效的时间")
+            
+    def stop_timer(self):
+        """停止定时器"""
+        self.is_running = False
+        self.target_time = None
+        self.start_button.config(state=tk.NORMAL)
+        self.stop_button.config(state=tk.DISABLED)
+        self.status_var.set("定时已停止")
+        
+    def check_timer(self):
+        """检查定时是否到达"""
+        if self.is_running and self.target_time:
+            now = datetime.now()
+            if now >= self.target_time:
+                self.trigger_key()
+                self.stop_timer()
+                self.status_var.set("定时已触发!")
+                self.root.after(3000, lambda: self.status_var.set("就绪"))
+                
+        # 更新倒计时显示
+        if self.is_running and self.target_time:
+            now = datetime.now()
+            if now < self.target_time:
+                remaining = (self.target_time - now).total_seconds()
+                if remaining <= 5:  # 最后5秒显示毫秒
+                    self.status_var.set(f"即将触发!剩余: {remaining:.3f}秒")
+                elif remaining <= 60:
+                    self.status_var.set(f"剩余: {remaining:.1f}秒")
+                else:
+                    minutes = int(remaining // 60)
+                    seconds = int(remaining % 60)
+                    self.status_var.set(f"目标时间: {self.target_time.strftime('%H:%M:%S')},剩余: {minutes}分{seconds}秒")
+        
+        # 每10毫秒检查一次,确保毫秒级精度
+        self.root.after(10, self.check_timer)
+        
+    def trigger_key(self):
+        """触发按键(跨平台支持)"""
+        key_name = self.key_var.get()
+        key_code = self.key_map.get(key_name)
+        
+        if key_code:
+            # 在单独线程中执行按键操作,避免阻塞UI
+            threading.Thread(target=self._send_key, args=(key_code,), daemon=True).start()
+            
+    def _send_key(self, key_code):
+        """实际发送按键(Windows平台)"""
+        try:
+            if sys.platform == "win32":
+                # Windows平台使用keybd_event
+                import ctypes
+                from ctypes import wintypes
+                
+                # 定义常量
+                KEYEVENTF_KEYDOWN = 0x0000
+                KEYEVENTF_KEYUP = 0x0002
+                
+                # 发送按键按下
+                ctypes.windll.user32.keybd_event(key_code, 0, KEYEVENTF_KEYDOWN, 0)
+                time.sleep(0.02)  # 短暂延时模拟真实按键
+                # 发送按键释放
+                ctypes.windll.user32.keybd_event(key_code, 0, KEYEVENTF_KEYUP, 0)
+                
+                # 更新状态显示(通过队列跨线程)
+                self.root.after(0, lambda: self.status_var.set(f"已触发按键: {self.key_var.get()}"))
+            else:
+                print(f"触发按键: {self.key_var.get()} (仅Windows平台支持真实按键)")
+                self.root.after(0, lambda: self.status_var.set(f"模拟触发按键: {self.key_var.get()}"))
+                
+        except Exception as e:
+            print(f"按键触发失败: {e}")
+            self.root.after(0, lambda: self.status_var.set(f"按键触发失败: {str(e)}"))
+            
+    def run(self):
+        """运行应用"""
+        self.root.mainloop()
+
+if __name__ == "__main__":
+    app = PreciseTimerGUI()
+    app.run()